Guides

Dynamic styling

While Panda is mainly focused on the statically analyzable styles, you might need to handle dynamic styles in your project.

💡

We recommend that you avoid relying on runtime values for your styles . Consider using recipes, css variables or data-* attributes instead.

Here are some ways you can handle dynamic styles in Panda:

Runtime values

Using a value that is not statically analyzable at build-time will not work in Panda due to the inability to determine the style values.

import { useState } from 'react'
import { css } from '../styled-system/css'
 
const App = () => {
  const [color, setColor] = useState('red.300')
 
  return (
    <div
      className={css({
        // ❌ Avoid: Panda can't determine the value of color at build-time
        color
      })}
    />
  )
}

The example above will not work because Panda can't determine the value of color at build-time. Here are some ways to fix this:

Using Static CSS

Panda supports a staticCss option in the config you can use to pre-generate some styles ahead of time.

import { defineConfig } from '@pandacss/dev'
 
export default defineConfig({
  staticCss: {
    css: [
      {
        properties: {
          // ✅ Good: Pre-generate the styles for the color
          color: ['red.300']
        }
      }
    ]
  }
})
import { useState } from 'react'
import { styled } from '../styled-system/jsx'
 
export const Button = () => {
  const [color, setColor] = useState('red.300')
 
  // ✅ Good: This will work because `red.300` is pre-generated using `staticCss` config
  return <styled.button color={color} />
}

Using token()

The token() function is generated by Panda and contains an object of all tokens by dot-path, allowing you to query for token's raw value at runtime.

import { useState } from 'react'
import { css } from '../styled-system/css'
import { token } from '../styled-system/tokens'
 
const Component = props => {
  return (
    <div
      className={css({
        // ✅ Good: Store the value in a CSS custom property
        color: 'var(--color)'
      })}
      style={{
        // ✅ Good: Handle the runtime value in the style attribute
        '--color': token(`colors.${props.color}`)
      }}
    >
      Dynamic color with runtime value
    </div>
  )
}
 
// App.tsx
const App = () => {
  const [runtimeColor, setRuntimeColor] = useState('pink.300')
 
  return <Component color={runtimeColor} />
}

Using token.var()

You could also directly use the token.var() function to get a reference to the underling CSS custom property for a given token:

import { useState } from 'react'
import { token } from '../styled-system/tokens'
 
const Component = props => {
  return (
    <div
      style={{
        // ✅ Good: Dynamically generate CSS custom property from the token
        color: token.var(`colors.${props.color}`)
      }}
    >
      Dynamic color with runtime value
    </div>
  )
}
 
const App = () => {
  const [runtimeColor, setRuntimeColor] = useState('yellow.300')
 
  return <Component color={runtimeColor} />
}

JSX Style Props

Panda supports forwarding JSX style properties to any element in your codebase.

For example, let's say we create a Card component that accepts a color prop:

import { styled } from '../styled-system/jsx'
 
const Card = props => {
  return <styled.div px="4" py="3" {...props} />
}

Then you add more style props to the Card component in a different file:

const App = () => {
  return (
    <Card color="blue.300">
      <p>Some content</p>
    </Card>
  )
}

As long as all prop-value pairs are statically extractable, Panda will automatically generate the CSS, so avoid using runtime values:

import { useState } from 'react'
 
const App = () => {
  const [color, setColor] = useState('blue.300')
 
  // ❌ Avoid: Panda can't determine the value of color at build-time
  return (
    <Card color={color}>
      <p>Some content</p>
    </Card>
  )
}

Property Renaming

Due to the static nature of Panda, you can't rename properties at runtime.

import { Circle, CircleProps } from '../styled-system/jsx'
 
type Props = {
  circleSize?: CircleProps['size']
}
 
const CustomCircle = (props: Props) => {
  const { circleSize = '3' } = props
  return (
    <Circle
      // ❌ Avoid: Panda can't determine the value of circleSize at build-time
      size={circleSize}
    />
  )
}

In this case, you need to use the size prop.

Alternative

As of v0.8, we added a new {fn}.raw() method to css, patterns and recipes. This function is an identity function and only serves as a hint for the compiler to extract the css.

It can be useful, for example, in Storybook args or custom react props.

// mark the object as valid css for the extractor
<Button rootProps={css.raw({ bg: 'red.400' })} />
export const Funky: Story = {
  // mark this as a button recipe usage
  args: button.raw({
    visual: 'funky',
    shape: 'circle',
    size: 'sm'
  })
}

Static expressions

Panda supports static expressions in your styles, as long as they are statically analyzable.

Static Composition

You can compose different style objects together using the css.raw() function.

import { css } from 'styled-system/css'
 
const paragraphSpacingStyle = css.raw({
  '& p': { marginBlockEnd: '1em' }
})
 
export const proseCss = css.raw({
  '& h1': paragraphSpacingStyle
})

This will result in the following CSS:

/* ... */
@layer utilities {
  .\[\&_p\]\:mb_1em p,
  .\[\&_h1\]\:\[\&_p\]\:mb_1em h1 p {
    margin-block-end: 1em;
  }
}

Static Expressions

Panda supports the use of functions to generate the style objects as long they are statically analyzable.

You can only use functions that are defined in the ECMAScript spec such as Math, Object, Array, etc, to support the evaluation of basic expressions like this:

import { cva } from '.panda/css'
 
const getVariants = () => {
  const spacingTokens = Object.entries({
    sm: 'token(spacing.1)',
    md: 'token(spacing.2)'
  })
 
  // Generate variants programmatically
  const variants = spacingTokens.map(([variant, token]) => [
    variant,
    { paddingX: token }
  ])
  return Object.fromEntries(variants)
}
 
const baseStyle = cva({
  variants: {
    variant: getVariants()
  }
})

This will generate the following variants object:

{
  "sm": { "paddingX": "token(spacing.1)" },
  "md": { "paddingX": "token(spacing.2)" }
}

And the following CSS

@layer utilities {
  .px_token\(spacing\.1\) {
    padding-inline: var(--spacing-1);
  }
 
  .px_token\(spacing\.2\) {
    padding-inline: var(--spacing-2);
  }
}

Runtime conditions

Even though we recommend that you first look for better alternatives (such as using recipe variants), you may still occasionally need runtime conditions.

When encountering a runtime condition, Panda will first try to resolve it statically. If it can't, it will fallback to the generating the corresponding CSS for each possible branches.

import { useState } from 'react'
import { css } from '../styled-system/css'
import { Stack } from '../styled-system/jsx'
 
const App = () => {
  const [isHovered, setIsHovered] = useState(false)
 
  return (
    <Stack
      color={isHovered ? { _hover: 'red.100' } : 'red.200'}
      _hover={{
        color: { base: 'red.300', md: isHovered ? 'red.400' : undefined }
      }}
    >
      <div className={css({ color: isHovered ? 'red.500' : 'red.600' })} />
    </Stack>
  )
}

Since none of the conditions above are statically extractable, Panda will generate css for all possible code path, resulting in a css that looks like this:

/* ... */
@layer utilities {
  .hover\:text_red\.100:where(:hover, [data-hover]) {
    color: var(--colors-red-100);
  }
 
  .text_red\.200 {
    color: var(--colors-red-200);
  }
 
  .hover\:text_red\.300:where(:hover, [data-hover]) {
    color: var(--colors-red-300);
  }
 
  @media screen and (min-width: 768px) {
    .hover\:md\:text_red\.400:where(:hover, [data-hover]) {
      color: var(--colors-red-400);
    }
  }
 
  .text_red\.500 {
    color: var(--colors-red-500);
  }
 
  .text_red\.600 {
    color: var(--colors-red-600);
  }
}
/* ... */

Referenced values

Although you should have your styles inlined most of the time, maybe you want to store a value in a variable and re-use in multiple places. This should be fine as long as you keep it statically analyzable.

Here's a short list of things to avoid:

  • Variables that are not defined in the same file
  • Variables resulting from a function call (e.g. const color = getColor())
💡

If you don't know what value a variable holds with a quick glance, Panda won't be able to either.

import { css } from '../styled-system/css'
 
// ✅ Good: All values are statically extractable
const mainColor = 'red.300'
const sizes = { sm: '12px', md: '16px', '2xl': '42px' }
 
const App = () => {
  return (
    <div
      className={css({
        color: mainColor,
        fontSize: sizes.md,
        width: sizes['2xl']
      })}
    />
  )
}

Runtime reference on known objects

Using a more complex but still common example :

import { useState } from 'react'
import { css } from '../styled-system/css'
 
const colorByType = {
  primary: 'red.300',
  secondary: 'blue.300',
  tertiary: 'green.300'
}
 
const Section = () => {
  const [type, setType] = useState('primary')
 
  // ❌ Avoid: since only "gray.100" is statically extractable here
  // This will not work as expected, the color CSS won't be generated
  return (
    <section className={css({ color: colorByType[type] ?? 'gray.100' })}>
      ❌ Will not be extracted
    </section>
  )
}

Even though colorByType is statically analyzable, Panda does not yet support this kind of automatic extraction fallback. This is the perfect opportunity to use the recipes.

import { useState } from 'react'
import { cva } from '../styled-system/cva'
 
const sectionRecipe = cva({
  base: { color: 'gray.100' },
  variants: {
    type: {
      primary: { color: 'red.300' },
      secondary: { color: 'blue.300' },
      tertiary: { color: 'green.300' }
    }
  }
})
 
const Section = () => {
  const [type, setType] = useState('primary')
 
  // ✅ Good: This will work as expected
  return <section className={sectionRecipe({ type })}>✅ With a recipe</section>
}

Not only did you get the same end result, but you also got a more readable and maintainable code !

You can now :

  • add more variants to your recipe
  • add more properties
  • use a shorthand or a condition

All of this with complete type-safety and without having to make drastic changes to the component.

💡

Note that you can also integrate this recipe directly into your theme if you want to only generate the CSS that you use, among other things

Summary

What you can do

// ✅ Good: Conditional styles
<styled.div color={{ base: "red.100", md: "red.200" }} />
 
// ✅ Good: Arbitrary value
<styled.div color="#121qsd" />
 
// ✅ Good: Arbitrary selector
<styled.div css={{ "&[data-thing] > span": { color: "red.100" } }} />
 
// ✅ Good: Runtime value (with config.`staticCss`)
const Button = () => {
  const [color, setColor] = useState('red.300')
  return <styled.button color={color} />
}
 
// ✅ Good: Runtime condition
<styled.div color={{ base: "red.100", md: isHovered ? "red.200" : "red.300" }} />
 
// ✅ Good: Referenced value
<styled.div color={mainColor} />
 

What you can't do

// ❌ Avoid: Runtime value (without config.`staticCss`)
const Button = () => {
  const [color, setColor] = useState('red.300')
  return <styled.button color={color} />
}
 
// ❌ Avoid: Referenced value (not statically analyzable or from another file)
<styled.div color={getColor()} />
<styled.div color={colors[getColorName()]} />
<styled.div color={colors[colorFromAnotherFile]} />
 
const CustomCircle = (props) => {
  const { circleSize = '3' } = props
  return (
    <Circle
      // ❌ Avoid: Panda can't determine the value of circleSize at build-time
      size={circleSize}
    />
  )
}