Dynamic styling
Represent runtime choices without a runtime style engine.
Bamboo style APIs are compile-time syntax. The Vite compiler must be able to recover every declaration a call can produce; there is no runtime CSS fallback.
Choose the representation from the shape of the value:
| Runtime input | Representation |
|---|---|
| One value from a finite set | A recipe variant |
| A finite branch between complete styles | A branch between separately compiled calls |
| A continuous value such as width or offset | The style attribute, or a CSS variable when the cascade must still reach it |
| A dynamic token name | A recipe variant that names the allowed token set |
| An opaque class alongside Bamboo styles | cx() — merged semantically if analyzable, a plain string-join otherwise |
Finite choices
Declare the possible values as recipe variants. Bamboo compiles every leaf and replaces the runtime choice with a small lookup:
const badge = cva({
variants: {
tone: {
neutral: { color: 'gray.700' },
danger: { color: 'red.700' },
},
},
})
export const Badge = ({ tone }: { tone: 'neutral' | 'danger' }) => <span className={badge({ tone })}>Status</span>
An ordinary branch between complete calls is also finite:
const className = compact ? css({ px: '2' }) : css({ px: '4' })
The compiler can rewrite both calls independently. Moving the branch inside a style value is open-ended and rejected:
// Rejected: `color` is a runtime style value.
css({ color: props.color })
Continuous values
A dragged offset, progress percentage, or measured size has no finite rule set. Put an unconditional value in the style attribute:
const track = css({ height: '[4px]', bg: 'blue.500' })
export const Progress = ({ value }: { value: number }) => <div className={track} style={{ width: `${value}%` }} />
Use a CSS variable when Bamboo conditions or consumer CSS must still be able to override the property:
const sidebar = css({
width: 'var(--sidebar-width)',
_tablet: { width: '100%' },
})
export const Sidebar = ({ width }: { width: string }) => (
<aside className={sidebar} style={{ '--sidebar-width': width } as React.CSSProperties} />
)
The variable value is runtime data; the declaration that reads it remains static and compiles normally.
Token choices
token('colors.red.500') with a literal path is fine where the compiler can evaluate it. A path assembled from runtime
data is not:
// Rejected: the build cannot know which token declaration is needed.
token(`colors.${tone}.500`)
Represent that set as recipe variants instead:
const text = cva({
variants: {
tone: {
red: { color: 'red.500' },
blue: { color: 'blue.500' },
},
},
})
Composing with an opaque class
A forwarded className prop has no declarations the compiler can see. cx() is the one call allowed to degrade instead
of reject:
import { css, cx } from '../styled-system/css'
const styles = css({ borderWidth: '1px', paddingX: '12px' })
const Card = ({ className, ...props }) => <div className={cx('group', styles, className)} {...props} />
When every argument is analyzable, cx() merges the underlying styles before allocating classes and later declarations
win:
cx(css({ paddingX: '4' }), css({ paddingX: '2' }))
// one atom for padding-inline: 2
An opaque argument like className above removes that guarantee: cx() falls back to a plain string-join, and the
opaque class's position in the call gives it no override priority over the compiled styles beside it. This does not fail
the build — it is cx()'s documented second contract — but the two are worth telling apart. See
classname concatenation for how the resulting cascade race
resolves, and source compilation for the opaque-composition diagnostic that
flags it.
css.raw()
css.raw() returns a style object for build-time composition; it does not return a class. The outer class-producing
call must still compile:
const shared = css.raw({ display: 'flex' })
const className = css(shared, { gap: '4' })
A live css.raw() result passed around at runtime violates the strict compiler contract and fails the build.
See Source compilation for the complete list of accepted and rejected call shapes.