css/
The css/ folder — css(), cx(), cva(), sva(), viewTransition(), positionTry(), and keyframes().
Folder: styled-system/css/
Import: import { css, cx, cva, sva, viewTransition, positionTry, keyframes } from '../styled-system/css'
Codegen writes one module per helper (css/css.ts, css/cva.ts, …) plus css/index.ts as the barrel. Every helper
maps a style object to a class name string. Panda extracts those styles at build time and emits atomic CSS. At runtime,
the helpers only resolve class names: no <style> tags, no theme context, no work on render. See
How it works.
css()
Pass a style object, or several to merge, and get back a class name string.
import { css } from '../styled-system/css'
const className = css({
bg: 'blue.500',
color: 'white',
px: '4',
py: '2',
rounded: 'md',
_hover: { bg: 'blue.600' },
md: { px: '5' }
})
// => "bg_blue.500 text_white px_4 py_2 rounded_md ..."<button className={className}>Save</button>Multiple arguments, or arrays, merge in order. Later styles win for the same property:
css({ color: 'red.500' }, { color: 'blue.500' }) // blue wins
css([{ color: 'red.500' }, condition && { fontWeight: 'bold' }])css.raw() returns the merged style object instead of a class name. Use it to pass styles into another helper, a
pattern, or a component without generating a class yet:
const base = css.raw({ display: 'flex', gap: '2' })
const styles = css(base, { p: '4' })Guides: Writing Styles, Merging Styles, Conditional Styles.
cx()
Joins class name strings into one, skipping false, null, and undefined:
import { css, cx } from '../styled-system/css'
const base = css({ display: 'flex', gap: '2' })
const active = css({ bg: 'blue.500', color: 'white' })
cx(base, isActive && active, className)cx() does not merge style objects or resolve conflicts between utilities. Merge with css() first, then join the
result with other class strings:
// ❌ cx does not merge style objects
cx({ color: 'red' }, { color: 'blue' })
// ✅ merge with css, then join with cx
cx(css({ color: 'red' }, { color: 'blue' }), otherClassName)cva()
An atomic recipe: base styles plus named variants, colocated in the component file. Call the result with a variant map and get a class name.
import { cva } from '../styled-system/css'
const button = cva({
base: {
display: 'inline-flex',
alignItems: 'center',
rounded: 'md',
fontWeight: 'semibold'
},
variants: {
visual: {
solid: { bg: 'blue.500', color: 'white' },
outline: { borderWidth: '1px', borderColor: 'blue.500' }
},
size: {
sm: { px: '2', py: '1', fontSize: 'sm' },
md: { px: '4', py: '2', fontSize: 'md' }
}
},
defaultVariants: {
visual: 'solid',
size: 'md'
}
})
button({ visual: 'outline', size: 'sm' }) // => class name stringThe recipe object takes:
base: styles that always apply.variants: named axes (size,visual, …) and the styles for each value.compoundVariants: styles for specific combinations of variant values.defaultVariants: values used when a variant is omitted.
Variant props are typed from the definition:
import { cva, type RecipeVariantProps } from '../styled-system/css'
type ButtonProps = RecipeVariantProps<typeof button>Use cva for variants local to one component. For a recipe shared across a design system, define it in config and
import it from recipes/ instead. Guides: Atomic recipes,
Config recipes, Thinking in Panda.
sva()
cva for components with several parts. One variant map drives every slot, and the call returns a class name per slot.
import { sva } from '../styled-system/css'
const card = sva({
slots: ['root', 'title', 'body'],
base: {
root: { rounded: 'lg', borderWidth: '1px', p: '4' },
title: { fontWeight: 'semibold', mb: '2' },
body: { color: 'fg.muted' }
},
variants: {
size: {
sm: {
root: { p: '3' },
title: { fontSize: 'sm' }
},
md: {
root: { p: '4' },
title: { fontSize: 'md' }
}
}
},
defaultVariants: { size: 'md' }
})
const classes = card({ size: 'sm' })
// classes.root, classes.title, classes.body<div className={classes.root}>
<h2 className={classes.title}>Title</h2>
<p className={classes.body}>Body</p>
</div>The recipe object takes the same keys as cva, plus slots, the list of part names. base, variants, and
compoundVariants are then written per slot.
A slot recipe shared across packages belongs in config as defineSlotRecipe, imported from
recipes/. Guides: Slot recipes,
Config slot recipe,
Slot recipe context.
viewTransition()
Returns a shared class built on view-transition-class, so Panda can extract and dedupe view transition styles like any
other atomic style. You still set a unique view-transition-name on each element yourself.
import { viewTransition } from '../styled-system/css'
const slide = viewTransition({
group: {
animationDuration: '0.4s',
animationTimingFunction: 'ease-in-out'
},
imagePair: { isolation: 'isolate' },
old: { opacity: 0 },
new: { opacity: 1 }
})
// => "vt_xxx"The keys group, imagePair, old, and new map to the matching ::view-transition-* pseudo-elements.
View transition names must be unique per element, so keying styles on the name would leave nothing to share.
view-transition-class is the CSS feature built for shared animation styles, and this helper emits that class. Guide:
View Transitions.
positionTry()
Returns the dashed-ident for a CSS anchor-positioning fallback and emits its @position-try block. Unlike
viewTransition(), which returns a class, this returns a value you put in position-try-fallbacks.
import { css, positionTry } from '../styled-system/css'
const bottom = positionTry({ top: 'anchor(bottom)', insetInlineStart: 'anchor(start)' })
// => "--pt_xxx"
css({ positionTryFallbacks: positionTry('bottom') })A string argument resolves a named theme.positionTry bag (positionTry('bottom') => "--pt_bottom"); an object
hashes to --pt_<hash>. The call folds inside css(), so a fallback list composes in your own style object. Guide:
Position Try.
keyframes()
Names an inline @keyframes block and returns the animation name for animationName or the animation shorthand.
Object form only — a bare animationName: 'spin' already resolves a theme.keyframes entry.
import { css, keyframes } from '../styled-system/css'
const spin = keyframes({ from: { transform: 'rotate(0deg)' }, to: { transform: 'rotate(360deg)' } })
// => "kf_xxx"
css({ animation: `${spin} 1s linear infinite` })The block emits only when its name is referenced, tree-shaken like theme.keyframes. Guide:
Keyframes.
See also
- patterns/ and jsx/, which call
css()under the hood - recipes/, the config-recipe counterpart to
cvaandsva - Styled System overview