Concepts

Writing Styles

Using the object syntax is a fundamental approach to writing styles in Panda. It not only provides a type-safe style authoring experience, but also improves readability and ensures a consistent experience with style overrides.

Atomic Styles

When you write styles in Panda, it generates a modern atomic stylesheet that is automatically scoped to the @layer utilities cascade layer.

The atomic stylesheets approach offers several advantages, such as improved code maintainability and reusability, as well as a smaller overall CSS footprint.

Panda exposes a css function that can be used to author styles. It accepts a style object and returns a className string.

import { css } from '../styled-system/css'
 
const styles = css({
  backgroundColor: 'gainsboro',
  borderRadius: '9999px',
  fontSize: '13px',
  padding: '10px 15px'
})
 
// Generated className:
// --> bg_gainsboro rounded_9999px fs_13px p_10px_15px
 
<div className={styles}>
  <p>Hello World</p>
</div>

The styles generated at build time end up like this:

@layer utilities {
  .bg_gainsboro {
    background-color: gainsboro;
  }
 
  .rounded_9999px {
    border-radius: 9999px;
  }
 
  .fs_13px {
    font-size: 13px;
  }
 
  .p_10px_15px {
    padding: 10px 15px;
  }
}

Shorthand Properties

Panda provides shorthands for common css properties to help improve the speed of development and reduce the visual density of your style declarations.

Properties like borderRadius, backgroundColor, and padding can be swapped to their shorthand equivalent rounded, bg, and p.

import { css } from '../styled-system/css'
 
// BEFORE - Good
const styles = css({
  backgroundColor: 'gainsboro',
  borderRadius: '9999px',
  fontSize: '13px',
  padding: '10px 15px'
})
 
// AFTER - Better
const styles = css({
  bg: 'gainsboro',
  rounded: '9999px',
  fontSize: '13px',
  p: '10px 15px'
})
💡

Shorthands are documented alongside their respective properties in the utilities section.

Type safety

Panda is built with TypeScript and provides type safety for all style properties and shorthands. Most of the style properties are connected to either the native CSS properties or their respective token value defined as defined in the theme object.

import { css } from '../styled-system/css'
 
//                       ⤵ you'll get autocomplete for colors
const styles = css({ bg: '|' })
💡

You can also enable the strictTokens: true setting in the Panda configuration. This allows only token values and prevents the use of custom or raw CSS values.

  • config.strictTokens will only affect properties that have config tokens, such as color, bg, borderColor, etc.
  • config.strictPropertyValues will throw for properties that do not have config tokens, such as display, content, willChange, etc. when the value is not a predefined CSS value.

strictTokens

With config.strictTokens enabled, you can only use token values in your styles. This prevents the use of custom or raw CSS values.

import { css } from '../styled-system/css'
 
css({ bg: 'red' }) // ❌ Error: "red" is not a valid token value
css({ fontSize: '123px' }) // ❌ Error: "123px" is not a valid token value
 
css({ bg: 'red.400' }) // ✅ Valid
css({ fontSize: '[123px]' }) // ✅ Valid, since `[123px]` is using the escape-hatch syntax
css({ content: 'abc' }) // ✅ Valid, since `content` isn't bound to a config token

For one-off styles, you can always use the escape-hatch syntax [xxx] to use custom or raw CSS values without TypeScript errors.

import { css } from '../styled-system/css'
 
css({ bg: '[red]' }) // ✅ Valid, since `[red]` is using the escape-hatch syntax
css({ fontSize: '[123px]' }) // ✅ Valid, since `[123px]` is using the escape-hatch syntax

strictPropertyValues

With config.strictPropertyValues enabled, you can only use valid CSS values for properties that do have a predefined list of values in your styles. This prevents the use of custom or raw CSS values.

css({ display: 'flex' }) // ✅ Valid
css({ display: 'block' }) // ✅ Valid
 
css({ display: 'abc' }) // ❌ will throw since 'abc' is not part of predefined values of 'display'
css({ pos: 'absolute123' }) // ❌ will throw since 'absolute123' is not part of predefined values of 'position'
 
css({ content: '""' }) // ✅ Valid, since `content` does not have a predefined list of values
css({ flex: '0 1' }) // ✅ Valid, since `flex` does not have a predefined list of values

The config.strictPropertyValues option will only be applied to this exhaustive list of properties:

type StrictableProps =
  | 'alignContent'
  | 'alignItems'
  | 'alignSelf'
  | 'all'
  | 'animationComposition'
  | 'animationDirection'
  | 'animationFillMode'
  | 'appearance'
  | 'backfaceVisibility'
  | 'backgroundAttachment'
  | 'backgroundClip'
  | 'borderCollapse'
  | 'border'
  | 'borderBlock'
  | 'borderBlockEnd'
  | 'borderBlockStart'
  | 'borderBottom'
  | 'borderInline'
  | 'borderInlineEnd'
  | 'borderInlineStart'
  | 'borderLeft'
  | 'borderRight'
  | 'borderTop'
  | 'borderBlockEndStyle'
  | 'borderBlockStartStyle'
  | 'borderBlockStyle'
  | 'borderBottomStyle'
  | 'borderInlineEndStyle'
  | 'borderInlineStartStyle'
  | 'borderInlineStyle'
  | 'borderLeftStyle'
  | 'borderRightStyle'
  | 'borderTopStyle'
  | 'boxDecorationBreak'
  | 'boxSizing'
  | 'breakAfter'
  | 'breakBefore'
  | 'breakInside'
  | 'captionSide'
  | 'clear'
  | 'columnFill'
  | 'columnRuleStyle'
  | 'contentVisibility'
  | 'direction'
  | 'display'
  | 'emptyCells'
  | 'flexDirection'
  | 'flexWrap'
  | 'float'
  | 'fontKerning'
  | 'forcedColorAdjust'
  | 'isolation'
  | 'lineBreak'
  | 'mixBlendMode'
  | 'objectFit'
  | 'outlineStyle'
  | 'overflow'
  | 'overflowX'
  | 'overflowY'
  | 'overflowBlock'
  | 'overflowInline'
  | 'overflowWrap'
  | 'pointerEvents'
  | 'position'
  | 'resize'
  | 'scrollBehavior'
  | 'touchAction'
  | 'transformBox'
  | 'transformStyle'
  | 'userSelect'
  | 'visibility'
  | 'wordBreak'
  | 'writingMode'

Nested Styles

Panda provides different ways of nesting style declarations. You can use the native css nesting syntax, or the built-in pseudo props like _hover and _focus. Pseudo props are covered more in-depth in the next section.

Native CSS Nesting

Panda supports the native css nesting syntax. You can use the & selector to create nested styles.

💡

Important: It is required to use the "&" character when nesting styles.

<div
  className={css({
    bg: 'red.400',
    '&:hover': {
      bg: 'orange.400'
    }
  })}
/>

You can also target children and siblings using the & syntax.

<div
  className={css({
    bg: 'red.400',
    '& span': {
      color: 'pink.400'
    }
  })}
/>

We recommend not using descendant selectors as they can lead to specificity issues when managing style overrides. Colocating styles directly on the element is the preferred way of writing styles in Panda.

Using Pseudo Props

Panda provides a set of pseudo props that can be used to create nested styles. The pseudo props are prefixed with an underscore _ to avoid conflicts with the native pseudo selectors.

For example, to create a hover style, you can use the _hover pseudo prop.

<div
  className={css({
    bg: 'red.400',
    _hover: {
      bg: 'orange.400'
    }
  })}
/>
💡

See the pseudo props section for a list of all available pseudo props.

Global styles

Sometimes you might want to insert global css like adding additional resets or font faces. Global styles in Panda can be added to the panda.config.ts using the globalCss property.

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

Global styles are inserted at the top of the stylesheet and are scoped to the @layer base cascade layer.

The styles generated at build time will look like this:

@layer base {
  html,
  body {
    color: var(--colors-gray-900);
    line-height: 1.5;
  }
}

Style Composition

Merging styles

Passing multiple styles to the css function will deeply merge the styles, allowing you to override styles in a predictable way.

import { css } from '../styled-system/css'
 
const result = css({ mx: '3', paddingTop: '4' }, { mx: '10', pt: '6' })
//    ^? result = "mx_10 pt_6"

To design a component that supports style overrides, you can provide the css prop as a style object, and it'll be merged correctly.

import { css } from '../styled-system/css'
 
export const Button = ({ css: cssProp = {}, children }) => {
  const className = css(
    { display: 'flex', alignItems: 'center', color: 'black' },
    cssProp
  )
  return <button className={className}>{children}</button>
}

Then you can use the Button component like this:

import { Button } from './Button'
 
export default function Page() {
  return (
    <Button css={{ color: 'pink', _hover: { color: 'red' } }}>
      will result in `class="d_flex items_center text_pink hover:text_red"`
    </Button>
  )
}

You can use this approach as well with the {cvaFn}.raw, {svaFn.raw} and {patternFn}.raw functions, allowing style objects to be merged as expected in any situation.

Pattern Example:

import { hstack } from '../styled-system/patterns'
import { css } from '../styled-system/css'
 
export const Button = ({ css: cssProp = {}, children }) => {
  // using the flex pattern
  const hstackProps = hstack.raw({
    border: '1px solid',
    _hover: { color: 'blue.400' }
  })
 
  // merging the styles
  const className = css(hstackProps, cssProp)
 
  return <button className={className}>{children}</button>
}

CVA Example:

import { css, cva } from '../styled-system/css'
 
const buttonRecipe = cva({
  base: { display: 'flex', fontSize: 'lg' },
  variants: {
    variant: {
      primary: { color: 'white', backgroundColor: 'blue.500' }
    }
  }
})
 
export const Button = ({ css: cssProp = {}, children }) => {
  const className = css(
    // using the button recipe
    buttonRecipe.raw({ variant: 'primary' }),
 
    // adding style overrides (internal)
    { _hover: { color: 'blue.400' } },
 
    // adding style overrides (external)
    cssProp
  )
 
  return <button className={className}>{children}</button>
}

SVA Example:

import { css, sva } from '../styled-system/css'
 
const checkbox = sva({
  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'
  }
})
 
export const Checkbox = ({ rootProps, controlProps, labelProps }) => {
  // using the checkbox recipe
  const slotStyles = checkbox.raw({ size: 'md' })
 
  return (
    <label className={css(slotStyles.root, rootProps)}>
      <input type="checkbox" className={css({ srOnly: true })} />
      <div className={css(slotStyles.control, controlProps)} />
      <span className={css(slotStyles.label, labelProps)}>Checkbox Label</span>
    </label>
  )
}
 
// Usage
 
const App = () => {
  return (
    <Checkbox
      rootProps={css.raw({ gap: 4 })}
      controlProps={css.raw({ borderColor: 'yellow.400' })}
      labelProps={css.raw({ fontSize: 'lg' })}
    />
  )
}

Classname concatenation

Panda provides a simple cx function to join classnames. It accepts a list of classnames and returns a string.

import { css, cx } from '../styled-system/css'
 
const styles = css({
  borderWidth: '1px',
  borderRadius: '8px',
  paddingX: '12px',
  paddingY: '24px'
})
 
const Card = ({ className, ...props }) => {
  const rootClassName = cx('group', styles, className)
  return <div className={rootClassName} {...props} />
}

Hashing

When debugging or previewing DOM elements in the browser, the length of the generated atomic className can get quite long, and a bit annoying. If you prefer to have terser classnames, use the hash option to enable className and css variable name hashing.

import { defineConfig } from '@pandacss/dev'
 
export default defineConfig({
  // ...
  hash: true
})
💡

You might need to generate a new code artifact by running panda codegen --clean

When you write a style like this:

import { css } from '../styled-system/css'
 
const styles = css({
  display: 'flex',
  flexDirection: 'row',
  _hover: {
    bg: 'red.50'
  }
})

The hash generated css will look like:

.fPSBzf {
  display: flex;
}
 
.ksWBqx {
  flex-direction: row;
}
 
.btpEVp:is(:hover, [data-hover]) {
  background: var(--bINrJX);
}
💡

We recommend that you use this in production builds only, as it can make debugging a bit harder.

Important styles

Applying important styles works just like CSS

css({
  color: 'red !important'
})

You can also apply important using just the exclamation syntax !

css({
  color: 'red!'
})

TypeScript

Use the SystemStyleObject type if you want to type your styles.

import { css } from '../styled-system/css'
import type { SystemStyleObject } from '../styled-system/types'
 
const styles: SystemStyleObject = {
  color: 'red'
}

Property conflicts

When you combine shorthand and longhand properties, Panda will resolve the styles in a predictable way. The shorthand property will take precedence over the longhand property.

import { css } from '../styled-system/css'
 
const styles = css({
  paddingTop: '20px'
  padding: "10px",
})

The styles generated at build time will look like this:

@layer utilities {
  .p_10px {
    padding: 10px;
  }
 
  .pt_20px {
    padding-top: 20px;
  }
}