Dynamic Styles
pika() arguments must be static, but your UI is not. This page shows the patterns that cover runtime-driven styling.
Why the Constraint Exists
At build time, the plugin extracts each pika() call from your source and evaluates its arguments as a self-contained expression — without running the rest of your module (packages/integration/src/ctx.ts). Anything that references a variable, calls a function, or branches on runtime state cannot be evaluated that way:
// ❌ These fail at build time — the call cannot see `color` or `isDark`
pika({ color })
pika({ color: isDark ? 'white' : 'black' })The ESLint rule no-dynamic-args catches these before the build does. The key insight for every pattern below: the set of styles must be static, but which style you apply at runtime is entirely up to you — pika() just returns a string.
Pattern 1: Variant Maps
Write one pika() call per variant and select between them at runtime. Every call is compiled at build time; the selection is plain object access.
const buttonVariants = {
primary: pika({
backgroundColor: '#3b82f6',
color: 'white',
}),
danger: pika({
backgroundColor: '#ef4444',
color: 'white',
}),
}
// Selecting a variant at runtime is plain object access —
// every pika() call above was already compiled at build time.
export function buttonClass(kind: keyof typeof buttonVariants) {
return buttonVariants[kind]
}@layer utilities {
.pk-a {
background-color: #3b82f6;
}
.pk-b {
color: white;
}
.pk-c {
background-color: #ef4444;
}
}Note in the output that shared declarations (color: white) are deduplicated into one atomic class across variants.
Boolean state works the same way:
const tabClass = pika({ padding: '0.5rem 1rem' })
const activeTabClass = pika({ fontWeight: '700', color: '#3b82f6' })
// Runtime composition of build-time strings
const className = isActive ? `${tabClass} ${activeTabClass}` : tabClassWARNING
When two variants set overlapping properties on the same element, which one wins is decided by the stylesheet, not by class order in the markup. Keep variant maps mutually exclusive per property, or split base and variant styles as above. See How PikaCSS Generates CSS.
Pattern 2: CSS Variables for Truly Runtime Values
When the value itself only exists at runtime (a slider position, a computed color, user content), reference a CSS custom property in the style definition and set the property with an inline style attribute:
const progressBarClass = pika({
width: 'var(--progress, 0%)',
height: '0.5rem',
backgroundColor: '#3b82f6',
transition: 'width 0.2s ease',
})
export { progressBarClass }@layer utilities {
.pk-a {
width: var(--progress, 0%);
}
.pk-b {
height: 0.5rem;
}
.pk-c {
background-color: #3b82f6;
}
.pk-d {
transition: width 0.2s ease;
}
}Then feed the variable at runtime — the atomic class never changes:
<div :class="progressBarClass" :style="{ '--progress': `${percent}%` }" /><div className={progressBarClass} style={{ '--progress': `${percent}%` }} />Pattern 3: Shortcuts as Recipes
For reusable multi-property styles with named variants, define shortcuts in your engine config. Shortcuts compose (a variant can include the base shortcut), and usage stays a static string:
// pika.config.ts
import { defineEngineConfig } from '@pikacss/core'
export default defineEngineConfig({
shortcuts: {
definitions: [
['btn', {
display: 'inline-flex',
alignItems: 'center',
padding: '0.5rem 1rem',
borderRadius: '0.5rem',
border: 'none',
cursor: 'pointer',
}],
['btn-primary', ['btn', { backgroundColor: '#3b82f6', color: 'white' }]],
['btn-danger', ['btn', { backgroundColor: '#ef4444', color: 'white' }]],
],
},
})const primaryButtonClass = pika('btn-primary')
const dangerButtonClass = pika('btn-danger')
export { dangerButtonClass, primaryButtonClass }@layer utilities {
.pk-a {
display: inline-flex;
}
.pk-b {
align-items: center;
}
.pk-c {
padding: 0.5rem 1rem;
}
.pk-d {
border-radius: 0.5rem;
}
.pk-e {
border: none;
}
.pk-f {
cursor: pointer;
}
.pk-g {
background-color: #3b82f6;
}
.pk-h {
color: white;
}
.pk-i {
background-color: #ef4444;
}
}Combine with Pattern 1 to select a shortcut at runtime:
const buttonVariants = {
primary: pika('btn-primary'),
danger: pika('btn-danger'),
}Dynamic shortcut rules (regex-based, like the icon shortcuts from @pikacss/plugin-icons) still require the matched string itself to appear statically in a pika() call.
Choosing a Pattern
| Situation | Pattern |
|---|---|
| A few known variants (size, intent, active state) | Variant map |
| Value computed at runtime (position, percentage, user color) | CSS variable + inline style |
| Reusable recipe shared across components | Shortcuts |
Next
- How PikaCSS Generates CSS — what the engine does with your calls.
- Shortcuts — full shortcut configuration reference.
- ESLint Config — catch dynamic arguments early.