Skip to content

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

Customization

Config Functions

The define* helpers for authoring config, recipes, tokens, and themes in their own files with full type checking.

Every function on this page does exactly one thing at runtime: return its argument unchanged. defineRecipe is literally (config) => config. So why call them at all?

Because of what they do at the type level. Write a recipe as a plain object inside defineConfig({...}) and TypeScript infers its type from context, no wrapper needed. Move that same object into its own file, recipes/button.ts, and there's no longer any context to infer from, it's just an object literal with whatever type you happen to give it. Wrapping it in defineRecipe(...) restores that context: the function's parameter type constrains what you write, so your editor autocompletes and flags errors while you type, in a file that never imports defineConfig at all.

Style and config helpers

defineConfig

Wraps the config object itself.

panda.config.ts

import { defineConfig } from '@pandacss/dev'
 
export default defineConfig({
  theme: {},
  include: ['src/**/*.{js,jsx,ts,tsx}']
})

defineRecipe

For a config recipe authored outside defineConfig.

import { defineRecipe } from '@pandacss/dev'
 
export const buttonRecipe = defineRecipe({
  className: 'button',
  description: 'The styles for the Button component',
  base: {
    display: 'flex'
  },
  variants: {
    visual: {
      funky: { bg: 'red.200', color: 'white' },
      edgy: { border: '1px solid {colors.red.500}' }
    }
  },
  defaultVariants: {
    visual: 'funky',
    size: 'sm'
  }
})

defineSlotRecipe

Same idea, for a config slot recipe.

import { defineSlotRecipe } from '@pandacss/dev'
 
export const checkboxRecipe = defineSlotRecipe({
  className: 'checkbox',
  description: 'The styles for the Checkbox component',
  slots: ['root', 'control', 'label'],
  base: {
    root: { display: 'flex', alignItems: 'center', gap: '2' },
    control: { borderWidth: '1px', borderRadius: 'sm' },
    label: { marginStart: '2' }
  },
  variants: {
    size: {
      sm: {
        control: { width: '8', height: '8' },
        label: { fontSize: 'sm' }
      },
      md: {
        control: { width: '10', height: '10' },
        label: { fontSize: 'md' }
      }
    }
  },
  defaultVariants: {
    size: 'sm'
  }
})

defineParts

For when you want one class on one DOM element to carry every part's styles, instead of a slot recipe's separate class per part. Pass a map of part name to selector, and you get back a function that turns per-part style objects into one combined style object keyed by those selectors:

import { defineParts, defineRecipe } from '@pandacss/dev'
 
const parts = defineParts({
  root: { selector: '& [data-part="root"]' },
  control: { selector: '& [data-part="control"]' },
  label: { selector: '& [data-part="label"]' }
})
 
export const checkboxRecipe = defineRecipe({
  className: 'checkbox',
  description: 'A checkbox style',
  base: parts({
    root: { display: 'flex', alignItems: 'center', gap: '2' },
    control: { borderWidth: '1px', borderRadius: 'sm' },
    label: { marginStart: '2' }
  }),
  variants: {
    size: {
      sm: parts({
        control: { width: '8', height: '8' },
        label: { fontSize: 'sm' }
      }),
      md: parts({
        control: { width: '10', height: '10' },
        label: { fontSize: 'md' }
      })
    }
  },
  defaultVariants: {
    size: 'sm'
  }
})

This is the shape Zag.js (opens in a new tab) and Ark UI (opens in a new tab) render to, data-part attributes on a single root, rather than Panda's own slot convention.

definePattern

For a pattern authored outside defineConfig.

import { definePattern } from '@pandacss/dev'
 
const visuallyHidden = definePattern({
  transform(props) {
    return {
      srOnly: true,
      ...props
    }
  }
})

definePreset

For a preset.

import { definePreset } from '@pandacss/dev'
 
export const pandaPreset = definePreset({
  theme: {
    extend: {
      tokens: {
        colors: { primary: { value: 'blue.500' } }
      }
    }
  }
})

definePlugin

For a plugin, the object that carries a shareable hooks map.

import { definePlugin } from '@pandacss/dev'
 
export const plugin = definePlugin({
  name: 'css-report',
  hooks: {
    'cssgen:done': ({ path, content }) => {
      console.log(`generated ${content.length} bytes`, path)
    }
  }
})

defineKeyframes

For @keyframes authored outside defineConfig.

import { defineKeyframes } from '@pandacss/dev'
 
export const keyframes = defineKeyframes({
  fadeIn: {
    '0%': { opacity: '0' },
    '100%': { opacity: '1' }
  }
})

defineGlobalStyles

For global styles authored outside defineConfig.

import { defineGlobalStyles } from '@pandacss/dev'
 
const globalCss = defineGlobalStyles({
  'html, body': {
    color: 'gray.900',
    lineHeight: '1.5'
  }
})

defineUtility

For a custom utility authored outside defineConfig.

import { defineUtility } from '@pandacss/dev'
 
export const br = defineUtility({
  className: 'rounded',
  values: 'radii',
  transform(value) {
    return { borderRadius: value }
  }
})

defineStyles

For a chunk of SystemStyleObject you want to reuse across variants, spread it into each one instead of repeating the properties:

recipes/button.ts

import { defineRecipe, defineStyles } from '@pandacss/dev'
 
const buttonVisualStyles = defineStyles({
  borderRadius: 'lg',
  boxShadow: 'sm'
})
 
export const buttonRecipe = defineRecipe({
  // ...
  variants: {
    visual: {
      funky: {
        bg: 'red.200',
        color: 'white',
        ...buttonVisualStyles
      },
      edgy: {
        border: '1px solid {colors.red.500}',
        ...buttonVisualStyles
      }
    }
  }
})

Composite style helpers

Text styles, layer styles, and animation styles follow the same pattern as the helpers above, a typed wrapper for authoring them outside defineConfig. Each has its own full page, since the value objects have more shape to them than a plain style object:

Multi-brand theming helpers

defineThemeVariant wraps a single theme variant, the same { tokens, semanticTokens } shape used for multi-theme tokens:

import { defineThemeVariant } from '@pandacss/dev'
 
export const darkTheme = defineThemeVariant({
  tokens: {
    colors: { bg: { value: '{colors.gray.900}' } }
  }
})

defineThemeContract goes further: define the token shape every theme must implement once, and it returns a checked version of defineThemeVariant that rejects a theme missing one of those tokens. See Theme contract for the full example.

defineGlobalFontface

For globalFontface authored outside defineConfig:

import { defineGlobalFontface } from '@pandacss/dev'
 
export const fonts = defineGlobalFontface({
  Inter: {
    src: 'url(/fonts/inter.woff2) format("woff2")',
    fontWeight: 400,
    fontStyle: 'normal'
  }
})

Token helpers

defineTokens

import { defineTokens } from '@pandacss/dev'
 
const theme = {
  tokens: defineTokens({
    colors: {
      primary: { value: '#ff0000' }
    }
  })
}

Scope it to one token category when you're authoring that category in its own file, defineTokens.colors(...) type-checks against just the colors shape instead of the full token tree:

tokens/colors.ts

import { defineTokens } from '@pandacss/dev'
 
export const colors = defineTokens.colors({
  primary: { value: '#ff0000' }
})

defineSemanticTokens

Same idea, for tokens whose value depends on a condition like color mode:

import { defineSemanticTokens } from '@pandacss/dev'
 
const theme = {
  semanticTokens: defineSemanticTokens({
    colors: {
      primary: {
        value: { _light: '{colors.blue.400}', _dark: '{colors.blue.200}' }
      }
    }
  })
}

tokens/colors.semantic.ts

import { defineSemanticTokens } from '@pandacss/dev'
 
export const colors = defineSemanticTokens.colors({
  primary: {
    value: { _light: '{colors.blue.400}', _dark: '{colors.blue.200}' }
  }
})
Edit this page on GitHubView as markdown
Last updated on