Skip to content

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

Advanced

Virtual Color

Panda allows you to create a virtual color or color placeholder in your project.

The colorPalette property is how you create virtual colors.

import { css } from '../styled-system/css'
 
const className = css({
  colorPalette: 'blue',
  bg: 'colorPalette.100',
  _hover: {
    bg: 'colorPalette.500'
  }
})

This will translate to the blue.100 background color and blue.500 background color on hover.

Virtual colors are useful when creating easily customizable components.

How Palettes Work

Every color group in your theme is a palette. The examples on this page use this theme:

panda.config.ts

import { defineConfig } from '@pandacss/dev'
 
export default defineConfig({
  theme: {
    tokens: {
      colors: {
        blue: {
          100: { value: '#dbeafe' },
          500: { value: '#3b82f6' },
          700: { value: '#1d4ed8' }
        },
        gray: {
          100: { value: '#f3f4f6' },
          500: { value: '#6b7280' },
          700: { value: '#374151' }
        }
      }
    },
    semanticTokens: {
      colors: {
        blue: {
          solid: {
            bg: { value: '{colors.blue.500}' },
            fg: { value: 'white' }
          }
        },
        gray: {
          solid: {
            bg: { value: '{colors.gray.500}' },
            fg: { value: 'white' }
          }
        }
      }
    }
  }
})

Base and semantic tokens under the same name form one palette, so this theme produces four palettes. Each maps every token below it to a virtual colorPalette.* token:

  • blue and gray: colorPalette.100, colorPalette.500, colorPalette.700, colorPalette.solid.bg, colorPalette.solid.fg
  • blue.solid and gray.solid: colorPalette.bg, colorPalette.fg

Use a top-level palette to reach all of its tokens:

css({ colorPalette: 'blue', bg: 'colorPalette.solid.bg', color: 'colorPalette.solid.fg' })

Or use a nested group as the palette, so the virtual tokens start from that group:

css({ colorPalette: 'blue.solid', bg: 'colorPalette.bg', color: 'colorPalette.fg' })

Default Values

A DEFAULT token makes the bare colorPalette token resolve to it:

panda.config.ts

import { defineConfig } from '@pandacss/dev'
 
export default defineConfig({
  theme: {
    semanticTokens: {
      colors: {
        brand: {
          DEFAULT: { value: '{colors.blue.500}' },
          hover: { value: '{colors.blue.700}' }
        }
      }
    }
  }
})
css({ colorPalette: 'brand', bg: 'colorPalette', _hover: { bg: 'colorPalette.hover' } })

Usage

With Recipes

You can also use virtual colors with recipes.

import { cva } from '../styled-system/css'
 
const button = cva({
  base: {
    padding: 4
    // you can also specify a default colorPalette in the `base` recipe key
    // colorPalette: 'blue',
  },
  variants: {
    variant: {
      primary: { color: 'colorPalette.500' }
    }
  },
  defaultVariants: { variant: 'primary' }
})

With Color Modes

You can also use virtual colors with different conditions, such as color modes.

import { css, cva, cx } from '../styled-system/css'
 
const someButton = cva({
  base: { padding: 4 },
  variants: {
    variant: {
      primary: {
        bg: { base: 'colorPalette.500', _dark: 'colorPalette.100' },
        color: { base: 'white', _dark: 'gray.700' }
      }
    }
  },
  defaultVariants: { variant: 'primary' }
})
 
export const App = () => {
  return (
    <>
      <div className="light">
        <button className={cx(css({ colorPalette: 'blue' }), someButton())}>Click me</button>
        <button className={cx(css({ colorPalette: 'gray' }), someButton())}>Click me</button>
      </div>
      <div className="dark">
        <button className={cx(css({ colorPalette: 'blue' }), someButton())}>Click me</button>
        <button className={cx(css({ colorPalette: 'gray' }), someButton())}>Click me</button>
      </div>
    </>
  )
}

With Opacity Modifiers

Add /opacity to a virtual color in any color property:

import { css } from '../styled-system/css'
 
const className = css({
  colorPalette: 'blue',
  bg: 'colorPalette.500/50',
  color: 'colorPalette.700'
})

To use it inside another value, like a shadow or a gradient, store it in a CSS variable with the token reference syntax:

import { css } from '../styled-system/css'
 
const className = css({
  colorPalette: 'blue',
  '--ring': '{colors.colorPalette.500/50}',
  boxShadow: '0 0 0 2px var(--ring)'
})

Pregenerating Palettes

Use the staticCss option in the config to pre-generate values for the colorPalette property. This is useful when you want to use a color palette that can be changed at runtime (e.g. in Storybook knobs).

💡

Learn more about static css generation.

panda.config.ts

import { defineConfig } from '@pandacss/dev'
 
export default defineConfig({
  staticCss: {
    css: [
      {
        properties: { colorPalette: ['blue', 'gray'] }
      }
    ]
  }
})

Then in your code, you can design components that use the colorPalette property:

import { useState } from 'react'
import { css } from '../styled-system/css'
 
function ButtonShowcase() {
  const [colorPalette, setColorPalette] = useState('blue')
 
  return (
    <div>
      <select value={colorPalette} onChange={e => setColorPalette(e.currentTarget.value)}>
        <option value="blue">Blue</option>
        <option value="gray">Gray</option>
      </select>
 
      <button
        className={css({
          bg: 'colorPalette.100',
          color: 'colorPalette.700',
          colorPalette
        })}
      >
        Click me
      </button>
    </div>
  )
}

Configuration

Use the colorPalette property in your theme to choose which palettes are generated.

Include Palettes

Use include to list the palettes you want:

panda.config.ts

import { defineConfig } from '@pandacss/dev'
 
export default defineConfig({
  theme: {
    colorPalette: {
      include: ['gray']
    }
  }
})

Only gray is a palette now, and it keeps all of its tokens, including the nested ones:

css({ colorPalette: 'gray', bg: 'colorPalette.solid.bg', color: 'colorPalette.100' })

Nested groups are palettes too. Include them with a glob pattern:

panda.config.ts

import { defineConfig } from '@pandacss/dev'
 
export default defineConfig({
  theme: {
    colorPalette: {
      include: ['gray.*'] // gray and gray.solid
    }
  }
})

Exclude Palettes

Use exclude to drop palettes. Excluding a palette also drops its nested groups:

panda.config.ts

import { defineConfig } from '@pandacss/dev'
 
export default defineConfig({
  theme: {
    colorPalette: {
      exclude: ['blue'] // gray and gray.solid remain
    }
  }
})

exclude wins over include:

panda.config.ts

import { defineConfig } from '@pandacss/dev'
 
export default defineConfig({
  theme: {
    colorPalette: {
      include: ['gray', 'blue'],
      exclude: ['blue'] // only gray
    }
  }
})

Keep Only Top-Level Palettes

Every nested group is a palette, so a theme with many nested groups per color (gray.solid, gray.subtle, gray.surface, …) can produce hundreds of palette names. With strictTokens, that union can make TypeScript report "Expression produces a union type that is too complex to represent" on components with spread props.

Exclude every nested name to keep only the top-level palettes:

panda.config.ts

import { defineConfig } from '@pandacss/dev'
 
export default defineConfig({
  theme: {
    colorPalette: {
      exclude: ['*.*'] // blue and gray, but not blue.solid or gray.solid
    }
  }
})

Every virtual token still works through the top-level palette:

css({ colorPalette: 'gray', bg: 'colorPalette.solid.bg', color: 'colorPalette.100' })

Disable Palettes

To turn off color palette generation entirely, set enabled to false:

panda.config.ts

import { defineConfig } from '@pandacss/dev'
 
export default defineConfig({
  theme: {
    colorPalette: {
      enabled: false
    }
  }
})
Edit this page on GitHubView as markdown
Last updated on