Skip to content

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

Customization

Conditions

Add your own named conditions, like _hover or dark, on top of Panda's built-in set.

A condition is a name you can use as a style key, _hover, _dark, md, that expands to a selector or at-rule. This page is about defining your own, on top of the built-in ones.

Creating a condition

Add a condition under conditions.extend. Here's a groupHover condition that styles a child when a parent with role="group" is hovered:

panda.config.ts

import { defineConfig } from '@pandacss/dev'
 
export default defineConfig({
  conditions: {
    extend: {
      groupHover: '[role=group]:where(:hover, [data-hover]) &'
    }
  }
})

The & is required. It's the placeholder for whatever selector the condition ends up applied to, and it has to sit at the start or the end of the string.

Run codegen to generate the _groupHover key:

pnpm panda codegen
import { css } from '../styled-system/css'
 
function App() {
  return (
    <div role="group">
      <span
        className={css({
          color: { base: 'blue.400', _groupHover: 'blue.600' }
        })}
      />
    </div>
  )
}

Overriding a built-in condition

conditions.extend also lets you replace what a built-in condition does. Here's dark redefined to also match a data-theme attribute, on top of the .dark class it already matches:

panda.config.ts

import { defineConfig } from '@pandacss/dev'
 
export default defineConfig({
  conditions: {
    extend: {
      dark: '.dark &, [data-theme="dark"] &'
    }
  }
})

Run codegen again after any change here, built-in or custom, since the condition's expansion is baked into the generated styled-system package.

Referencing tokens

A condition's selector string can call token(), and it resolves the same way it would inside a style object:

import { defineConfig } from '@pandacss/dev'
 
export default defineConfig({
  conditions: {
    extend: {
      mq: '@media (min-width: token(sizes.4xl))',
      size2: '&[data-size=token(spacing.2)]'
    }
  }
})

Combining multiple rules under one name

Give a condition an array instead of a string, and every entry nests inside the last, all under the one condition name:

import { defineConfig } from '@pandacss/dev'
 
export default defineConfig({
  conditions: {
    extend: {
      supportHover: ['@media (hover: hover) and (pointer: fine)', '&:hover']
    }
  }
})
import { css } from '../styled-system/css'
 
css({
  _supportHover: {
    color: 'red'
  }
})

generates:

@media (hover: hover) and (pointer: fine) {
  &:hover {
    color: red;
  }
}

One condition, several independent blocks

The array form above nests every rule inside the previous one. Sometimes you need the opposite: one condition that emits multiple separate CSS blocks, each with its own at-rule, not nested inside each other. A common case is hover feedback on pointer devices and press feedback on touch devices, two different media queries, same condition name.

Use the object form with @slot markers. Each path from the root down to an @slot becomes its own CSS block:

import { defineConfig } from '@pandacss/dev'
 
export default defineConfig({
  conditions: {
    extend: {
      hoverActive: {
        '@media (hover: hover)': {
          '&:is(:hover, [data-hover])': '@slot'
        },
        '@media (hover: none)': {
          '&:is(:active, [data-active])': '@slot'
        }
      }
    }
  }
})
import { css } from '../styled-system/css'
 
css({
  _hoverActive: {
    bg: 'red'
  }
})

generates two independent blocks:

@media (hover: hover) {
  .hoverActive\:bg_red:is(:hover, [data-hover]) {
    background: red;
  }
}
@media (hover: none) {
  .hoverActive\:bg_red:is(:active, [data-active]) {
    background: red;
  }
}

An object with just one @slot path behaves the same as the array form above, this is the general case, the array form is the shorthand for the common single-block one.

Container queries

Type-safe container queries are their own topic. See container queries.

Starting from zero

Everything here assumes you're extending Panda's defaults with conditions.extend. For no built-in conditions at all, see minimal setup.

Edit this page on GitHubView as markdown
Last updated on