Build-time Compile
PikaCSS is a build-time CSS engine. Every pika() call in your source code is evaluated and replaced during the build process — before your application ever runs in the browser. The result is plain string literals in your JavaScript and a pre-generated CSS file. There is no runtime style engine.
How the Build Pipeline Works
When your build tool (Vite, Webpack, Rollup, esbuild, Rspack, or Rolldown) processes your source files, the PikaCSS unplugin intercepts them and performs a multi-stage transformation:
Stage 1: Function Call Detection
The plugin scans each source file using a regex to find all pika() calls, including format variants (pika.str(), pika.arr()) and preview variants (pikap()). It uses a bracket-depth parser to correctly extract the full function call, handling nested parentheses, strings, comments, and template literals.
Global Function
pika() is a global function — you do not import it. The build plugin finds and replaces these calls via static analysis. The generated pika.gen.ts provides TypeScript support through declare global, not through module exports.
Stage 2: Argument Evaluation
The extracted argument string is evaluated at build time using new Function(...). For example, pika({ color: 'red', fontSize: '16px' }) has its argument parsed as a JavaScript object literal and executed. This is why all arguments must be statically analyzable — they are literally executed during the build, not at runtime.
Stage 3: Atomic Style Extraction
The evaluated arguments are passed to engine.use(), which:
- Transforms style items through the plugin hook pipeline (resolving shortcuts, handling nested selectors, etc.)
- Extracts atomic style contents — each CSS property-value pair becomes an independent unit with its own selector, property, and value
- Deduplicates — if the same property appears multiple times for the same selector, only the last value wins (CSS override semantics)
Stage 4: Atomic ID Generation
Each unique combination of [selector, property, value] receives a short, unique class name. IDs are generated sequentially using a bijective base-52 encoding (a–z, A–Z, aa, ba, ...) via the numberToChars() function. If the same property-value-selector combination is encountered again (even in a different file), it reuses the existing ID.
Stage 5: Code Replacement
Using MagicString, the plugin replaces each pika() call with its output — the generated class names in the configured format. The original function call is completely removed from the code. Sourcemaps are generated to preserve debugging capability.
Stage 6: CSS Code Generation
After all files are processed, the engine renders all collected atomic styles into a CSS file (pika.gen.css by default). TypeScript type definitions are also written to pika.gen.ts for autocomplete support.
Before and After
Source code (what you write):
// pika() is available as a global function — no import needed
// A button component using pika() for styling
const buttonClass = pika({
padding: '0.5rem 1rem',
borderRadius: '0.5rem',
backgroundColor: '#0ea5e9',
color: 'white',
cursor: 'pointer',
})
document.querySelector('#btn')!.className = buttonClassCompiled output (what runs in the browser):
// After build-time compilation, pika() calls are replaced
// with plain string literals — no function call remains.
const buttonClass = 'a b c d e'
document.querySelector('#btn')!.className = buttonClassGenerated CSS (pika.gen.css):
/* Auto-generated by @pikacss/unplugin-pikacss */
.a { padding: 0.5rem 1rem; }
.b { border-radius: 0.5rem; }
.c { background-color: #0ea5e9; }
.d { color: white; }
.e { cursor: pointer; }Output Formats
PikaCSS supports three output formats for transformed pika() calls. The format can be set globally via the transformedFormat plugin option, or forced per-call using method variants.
String Format (default)
pika() or pika.str() produces a space-joined string of class names:
// Output format: 'string' (default)
// pika() or pika.str() produces a space-joined string
// Source:
const cls = pika({ color: 'red', fontSize: '16px' })
// Compiled output:
const cls = 'a b'Array Format
pika.arr() produces an array of class name strings, useful for frameworks that accept class arrays:
// Output format: 'array'
// pika.arr() produces an array of class name strings
// Source:
const cls = pika.arr({ color: 'red', fontSize: '16px' })
// Compiled output:
const cls = ['a', 'b'] Static Analyzability Constraint
Because arguments are evaluated at build time with new Function(...), they must be statically analyzable. This means the arguments can only contain values that are resolvable without running your application.
Valid — static values the build can evaluate:
// ✅ Valid: all arguments are static and analyzable at build time
// Object literal with static values
pika({ color: 'red', fontSize: '16px' })
// Multiple arguments
pika({ color: 'red' }, { fontSize: '16px' })
// Nested selectors with static values
pika({
'color': 'black',
'&:hover': { color: 'blue' },
})
// Static string values
pika({ padding: '1rem', margin: '0 auto' })Invalid — runtime values that cannot be evaluated during build:
// ❌ Invalid: runtime-only values cannot be evaluated at build time
// Variable reference — not statically analyzable
const myColor = getUserTheme()
pika({ color: myColor }) // Error!
// Dynamic expression — depends on runtime state
pika({ fontSize: `${window.innerWidth > 768 ? '16px' : '14px'}` }) // Error!
// Function call in value — cannot be evaluated statically
pika({ color: getColor() }) // Error!
// Spread from runtime object — not statically analyzable
const styles = getStyles()
pika({ ...styles }) // Error!TIP
If you need dynamic styling, use CSS custom properties (variables) in your pika() definitions and change their values at runtime via JavaScript or inline styles.
Atomic CSS Deduplication
PikaCSS generates atomic CSS — each unique CSS property-value pair maps to exactly one class. When the same declaration appears in multiple components, it is automatically deduplicated across the entire application.
Two components sharing styles:
// Two different components using the same CSS property-value pairs
// Button component
const btnClass = pika({
color: 'white', // → class 'a'
padding: '1rem', // → class 'b'
cursor: 'pointer', // → class 'c'
})
// Link component — shares `color: white` and `cursor: pointer`
const linkClass = pika({
color: 'white', // → reuses class 'a' (same property-value!)
fontSize: '14px', // → class 'd' (new)
cursor: 'pointer', // → reuses class 'c' (same property-value!)
})Generated CSS — only 4 classes, not 6:
/* Only 4 atomic classes — not 6!
Shared property-value pairs are deduplicated across the entire app */
.a { color: white; }
.b { padding: 1rem; }
.c { cursor: pointer; }
.d { font-size: 14px; }This approach minimizes CSS output size: as your application grows, the CSS file grows logarithmically rather than linearly, since most new components reuse existing property-value pairs.
Build vs. Serve Mode
The unplugin behaves differently depending on the mode:
- Build mode (
production): RunsfullyCssCodegen()atbuildStart, which scans all matched files, collects all style usages, and writes the complete CSS file before bundling begins. - Serve mode (
development): Transforms files on-demand as they are requested. CSS and TypeScript codegen files are written incrementally with debouncing (300ms) as files change.