Skip to content

Dynamic Values With CSS Variables

PikaCSS does not include a styling runtime. That is where a lot of its performance and predictability come from, but it also changes how you handle dynamic styling.

If a value must change at runtime, keep the style definition static inside pika() and bind the changing value to a CSS variable yourself.

Pick the right pattern for the job

NeedPatternWhy
A finite set of visual statesPredeclare variants and switch class names at runtime.PikaCSS can still scan every style shape ahead of time.
A continuous or per-instance valueUse var(--...) inside pika() and bind the variable at runtime.The style shape stays static while the value remains dynamic.
Shared theme tokensDefine variables in config and scope them with selectors.The design system stays centralized and reusable.

What not to do

This is the runtime CSS-in-JS habit that usually causes the first mismatch with PikaCSS.

tsx
type ProgressBarProps = {
	value: number
	color: string
}

function ProgressBar({ value, color }: ProgressBarProps) {
	return (
		<div
			className={pika({
				width: `${value}%`,
				backgroundColor: color,
			})}
		/>
	)
}

pika() cannot safely evaluate that object because the actual style values only exist after your app runs.

Bind runtime values through CSS variables

The fix is not to force more runtime into pika(). The fix is to let PikaCSS generate static declarations that reference CSS variables, then update those variables from your framework or DOM code.

tsx
import type { CSSProperties } from 'react'

type ProgressBarProps = {
	value: number
	color: string
}

const progressBar = pika({
	width: 'var(--progress-width)',
	backgroundColor: 'var(--progress-color)',
})

function ProgressBar({ value, color }: ProgressBarProps) {
	return (
		<div
			className={progressBar}
			style={{
				'--progress-width': `${value}%`,
				'--progress-color': color,
			} as CSSProperties}
		/>
	)
}

This works because PikaCSS only needs to emit width: var(--progress-width) and background-color: var(--progress-color). Your app still owns the runtime binding step.

Keep style shape static, switch variants separately

Many components need both kinds of dynamism at the same time:

  1. Discrete states such as solid or outline
  2. Per-instance values such as a brand color from data

Handle them separately.

tsx
import type { CSSProperties } from 'react'

type BadgeVariant = 'solid' | 'outline'

const badgeClassNames = {
	solid: pika({
		backgroundColor: 'var(--badge-color)',
		color: 'white',
	}),
	outline: pika({
		border: '1px solid var(--badge-color)',
		color: 'var(--badge-color)',
	}),
} satisfies Record<BadgeVariant, string>

type BadgeProps = {
	variant: BadgeVariant
	color: string
}

function Badge({ variant, color }: BadgeProps) {
	return (
		<span
			className={badgeClassNames[variant]}
			style={{ '--badge-color': color } as CSSProperties}
		/>
	)
}

Choose the static class name at runtime. Bind the changing token value through a CSS variable.

A useful migration mental model

If you are coming from traditional runtime CSS-in-JS, this shift matters:

  1. Do not build style objects from runtime data.
  2. Do choose among predeclared style objects at runtime.
  3. Do push changing values into CSS variables.
  4. Do let the app layer own how those variables are assigned.

The boundary to remember

PikaCSS can generate var(--accent) references for you. It will never manage --accent state for you.

For shared tokens and theme switching, continue with Theming And Variables.

Next