Skip to content

Want to skip the docs? Check out pandamastery.com - the best way to learn Panda CSS

Installation

Linting

A Panda-aware lint rule set for ESLint and Oxlint, catching mistakes the compiler doesn't error on.

A style object can be valid TypeScript and still be wrong. color: '#ef4444' renders fine but ignores the red.500 token your theme already owns; color: 'red.1000' is quieter still: the compiler skips it, and you find out in the browser.

The lint rules catch these in real time. They run on Panda's own compiler, so they know your actual theme: which raw values have a token waiting, which token paths resolve. One package, two entry points, same rules:

@pandacss/eslint-plugin            # ESLint flat config
@pandacss/eslint-plugin/oxlint     # Oxlint entry, reuses the same rules

ESLint setup

The recommended config preloads your Panda project so rules can resolve tokens and recipes:

eslint.config.mjs

import panda from '@pandacss/eslint-plugin'
 
export default [
  await panda.configs.recommended({ configPath: './panda.config.ts' }),
]

recommended turns on the rules marked default in the table below. Add any other rule as a second config entry:

eslint.config.mjs

import panda from '@pandacss/eslint-plugin'
 
export default [
  await panda.configs.recommended({ configPath: './panda.config.ts' }),
  {
    rules: {
      '@pandacss/no-important': 'error',
      '@pandacss/no-margin-properties': 'warn'
    }
  }
]
If eslint.config.js isn't ESM, rename it to eslint.config.mjs.

Oxlint setup

Register the plugin in .oxlintrc.json. Oxlint has no recommended preset to import, so list every rule you want on, including the ones marked default below, oxlint won't turn them on for you:

.oxlintrc.json

{
  "jsPlugins": ["@pandacss/eslint-plugin/oxlint"],
  "rules": {
    "@pandacss/no-invalid-token-paths": "error",
    "@pandacss/no-invalid-nesting": "error",
    "@pandacss/file-not-included": "error",
    "@pandacss/no-deprecated": "warn",
    "@pandacss/no-debug": "warn",
    "@pandacss/extraction-diagnostics": "warn",
    "@pandacss/prefer-token": ["warn", { "categories": ["colors"] }]
  }
}

The Panda config is auto-discovered from the working directory. Point it elsewhere with PANDA_CONFIG_PATH:

PANDA_CONFIG_PATH=./apps/web/panda.config.ts oxlint

Alpha: needs oxlint and @oxlint/plugins, and binds one Panda project per run, a monorepo with several panda.config.ts files needs a small local plugin per project (see the package README (opens in a new tab)).

RuleESLint recommendedSeverity
no-invalid-token-pathsdefaulterror
no-invalid-nestingdefaulterror
file-not-includeddefaulterror
no-deprecateddefaultwarn
no-debugdefaultwarn
extraction-diagnosticsdefaultwarn
prefer-tokendefault, colors onlywarn
no-primitive-tokenopt-in—
no-importantopt-in—
no-margin-propertiesopt-in—
no-descendant-selectorsopt-in—
no-physical-propertiesopt-in—
no-shorthand-longhand-mixopt-in—
consistent-property-styleopt-in—
prefer-text-styleopt-in—

"Recommended" means panda.configs.recommended() enables it for ESLint. Oxlint doesn't read that config at all, so nothing is automatic there, every row in your .oxlintrc.json rules block is something you opted into.

Correctness rules

These catch code that silently produces no CSS, or the wrong CSS. The recommended config enables all of them, plus no-deprecated, no-debug, and prefer-token for colors.

no-invalid-token-paths

A token reference that doesn't resolve is emitted as a literal CSS value, not an error.

// ❌ 'red.1000' is not in the theme
css({ color: 'red.1000' })
 
// ✅
css({ color: 'red.500' })

no-invalid-nesting

A nested selector without & is silently ignored by the compiler.

// ❌ no "&", Panda skips the whole block
css({ ':hover': { color: 'red.500' } })
 
// ✅ either form works
css({ '&:hover': { color: 'red.500' } })
css({ _hover: { color: 'red.500' } })

file-not-included

A file that calls Panda's style functions but sits outside the config's include globs extracts nothing. Every css() call in it is dead.

panda.config.ts

import { defineConfig } from '@pandacss/dev'
 
export default defineConfig({
  include: ['src/**/*.tsx']
  // ❌ lib/button.tsx calls css() but is not covered
})

extraction-diagnostics

Relays the compiler's own diagnostics into the editor: a typo'd condition key, a value extraction can't resolve statically, anything the build would warn about. This is the rule that surfaces dynamic styling mistakes at lint time instead of at runtime.

// ❌ '_hver' is not a condition, the compiler ignores it
css({ _hver: { color: 'red.500' } })

Deprecation

no-deprecated

Flags tokens, utilities, recipes, and patterns marked deprecated in the config. Scope it with kinds if you only care about some of them.

// theme: brand: { value: '#facc15', deprecated: 'use colors.accent instead' }
 
// ❌ deprecated token
css({ color: 'brand' })
 
// ✅
css({ color: 'accent' })

Consistency rules

These enforce house style across a team. prefer-token is the exception, it's already on by default for colors; everything else here is opt-in, turn on the ones that match yours.

prefer-token

A raw value where a token exists for that category. The suggestion names the token, so the fix is a copy-paste. On by default, scoped to colors; widen with categories to cover more.

// ❌ raw value, theme has red.500
css({ color: '#ef4444' })
 
// ✅
css({ color: 'red.500' })
'@pandacss/prefer-token': ['warn', { categories: ['colors', 'spacing', 'radii'] }]

no-primitive-token

Stricter than prefer-token: flags primitive palette tokens where a semantic token exists, so component code tracks intent instead of a palette.

// ❌ primitive, theme defines fg.error
css({ color: 'red.500' })
 
// ✅
css({ color: 'fg.error' })

prefer-text-style

Several typography properties set individually that should collapse into one textStyle token.

// ❌ ad-hoc typography
css({ fontSize: '2xl', fontWeight: 'bold', lineHeight: '1.2' })
 
// ✅
css({ textStyle: 'heading.lg' })

consistent-property-style

One spelling per property across the codebase. Configure style as 'shorthand' or 'longhand'.

// with { style: 'shorthand' }
 
// ❌
css({ marginLeft: '4' })
 
// ✅
css({ ml: '4' })

no-shorthand-longhand-mix

A shorthand and its longhand in the same object resolve in an order you probably didn't intend.

// ❌ does marginLeft win? don't make readers guess
css({ margin: '4', marginLeft: '5' })
 
// ✅
css({ marginTop: '4', marginLeft: '5' })

Hygiene rules

Opt-in, apart from no-debug.

no-debug

The debug property left in source. Useful locally, noise in a commit.

// ❌
css({ debug: true, color: 'red.500' })

no-important

!important in a style value, in either spelling.

// ❌ both forms flagged
css({ color: 'red.500!' })
css({ _hover: { color: 'red.500 !important' } })

no-margin-properties

Margins couple a component to its surroundings. Prefer gap and layout patterns, so spacing lives in the parent.

// ❌ child decides its own spacing
css({ mt: '4' })
 
// ✅ parent owns the spacing
css({ display: 'flex', flexDirection: 'column', gap: '4' })

no-descendant-selectors

A selector that styles another element (& > li, .foo &) couples components through the DOM tree. Off by default; turn it on for StyleX-style per-element scoping.

// ❌ styles the children, not the element
css({ '& > li': { marginBlock: '2' } })
 
// ✅ cross-element state through a condition
css({ _groupHover: { opacity: 1 } })

no-physical-properties

Physical properties break RTL layouts. Use logical equivalents.

// ❌
css({ left: '0', marginLeft: '4' })
 
// ✅
css({ insetInlineStart: '0', marginInlineStart: '4' })

See also

Edit this page on GitHubView as markdown
Last updated on