Panda CSS is a universal, build-time, type-safe CSS-in-JS solution that extracts styles at compile time and generates optimized CSS and TypeScript utilities. The system follows a modular architecture built as a pnpm monorepo with distinct packages handling different aspects of the styling pipeline.
- Build-time extraction: Styles are analyzed and extracted during the build process, not at runtime
- Type safety: Full TypeScript support with auto-generated types based on configuration
- Zero runtime: CSS is generated at build time, minimal JavaScript shipped to the browser
- Framework agnostic: Works with React, Vue, Svelte, Solid, Preact, Qwik, and more
- Modern CSS: Leverages CSS custom properties, cascade layers (
@layer), and modern CSS features
panda/
├── packages/ # Core packages (published to npm)
├── sandbox/ # Framework integration examples
├── playground/ # Development testing environment
├── website/ # Documentation site
└── .changeset/ # Changeset-based versioning
- Purpose: Main entry point for end users
- Exports: CLI binary (
pandacommand), PostCSS plugin, presets - Key responsibilities:
- Command-line interface (init, codegen, build, analyze, debug, studio)
- Interactive setup wizard
- Update notifications
- Dependencies: Orchestrates all other packages
- Purpose: PostCSS plugin integration
- Key responsibilities:
- Integrates Panda into PostCSS build pipeline
- Uses
Builderclass to process CSS files - Registers file dependencies for hot module replacement
- Validates and processes Panda-specific CSS
- Purpose: Heart of the Panda system containing all core logic
- Key classes:
- Context: Central orchestration class that manages all engines
- Utility: CSS utility generation and processing
- Recipes: Component recipe/variant system (like Stitches)
- Patterns: Layout pattern generation (stack, flex, grid, etc.)
- Conditions: Responsive/conditional style handling (breakpoints, pseudo-classes)
- Stylesheet: CSS generation and optimization
- StyleEncoder/StyleDecoder: Style serialization and deserialization
- TokenDictionary: Design token management
- JsxEngine: JSX component analysis
- ImportMap: Import tracking for panda functions
- Key responsibilities:
- Style transformation and serialization
- CSS optimization and minification
- Layer management (
@layerdirectives) - Selector parsing and manipulation
- Color mixing utilities
- Purpose: Static code analysis and extraction
- Key responsibilities:
- TypeScript/JavaScript AST parsing (uses ts-morph)
- Vue SFC parsing (uses @vue/compiler-sfc)
- Svelte component parsing
- Extracting style declarations from source code
- Matching function calls and JSX props against Panda APIs
- Key classes:
- Project: Manages ts-morph project for file analysis
- ParserResult: Collects and organizes extraction results
- Purpose: Low-level AST extraction and evaluation
- Key responsibilities:
- Extracting values from AST nodes
- Static evaluation of expressions (uses ts-evaluator)
- Box/unbox pattern for value wrapping
- JSX attribute and spread analysis
- Object literal analysis
- Core concepts:
- Boxing: Wrapping AST nodes with metadata
- Evaluation: Computing static values from code
- Purpose: Code generation for styled-system output
- Directory structure:
artifacts/ ├── css/ # CSS generation (tokens, reset, static, global, keyframes) ├── js/ # JavaScript utility functions ├── jsx/ # JSX factory functions (React, Solid, Vue, Preact, Qwik) ├── types/ # TypeScript type definitions └── generated/ # Generated helper files - Key responsibilities:
- Generating the
styled-systemdirectory - Type definition generation for autocomplete
- CSS utility classes
- Pattern and recipe types/functions
- Framework-specific JSX factories
- Generating the
- Purpose: Configuration loading and merging
- Key responsibilities:
- Finding and loading
panda.config.ts/js - Dynamic config import with bundle-n-require
- Merging user config with presets
- Config diffing for incremental updates
- TypeScript path mapping resolution
- Finding and loading
- Available presets:
preset-base: Minimal foundation presetpreset-panda: Default preset with design system tokenspreset-atlaskit: Atlassian design system integrationpreset-open-props: Open Props design tokens
- Purpose: Shareable configuration and design tokens
- Purpose: Node.js runtime and build orchestration
- Key classes/functions:
- PandaContext: Extended context with build-time features
- Builder: Incremental build system with file watching
- DiffEngine: Detects configuration changes
- OutputEngine: Manages file writing
- Key responsibilities:
- File watching (chokidar)
- Incremental builds
- Config change detection
- Git ignore management
- CSS generation and optimization
- Prettier formatting integration
- Purpose: Design token processing
- Key responsibilities:
- Token transformation and references
- Semantic token resolution
- CSS variable generation
- Token categorization (colors, spacing, typography, etc.)
- Critical API distinction:
get(path): Returns raw token values (e.g.,"#ef4444","1rem")getVar(path): Returns CSS variable references (e.g.,"var(--colors-red-500)")- Use
getVar()for RuleProcessor and string pattern expansion - Use
get()for AST evaluation of token CallExpressions
- Purpose: Shared TypeScript types
- Build process: Generates complex conditional types from csstype
- Exports: All type definitions used across packages
- Purpose: Shared utility functions
- Examples: Object manipulation, string utilities, memoization, pattern functions
- Purpose: Validates CSS property names
- Used by: Core and generator for prop filtering
- Purpose: Centralized logging with log levels
- Features: Colored output, timing utilities, debug mode
- Purpose: User-friendly error and warning messages
- Purpose: Visual documentation of design tokens
- Technology: Astro-based static site
- Features:
- Token visualization
- Color palette preview
- Typography scale
- Spacing scale
- Interactive token explorer
- Purpose: Astro integration for Panda Studio
User runs `panda init`
↓
CLI (packages/cli)
↓
Interactive wizard or flag parsing
↓
setupConfig() - Creates panda.config.ts
↓
setupPostcss() - Optional postcss.config.js
↓
loadConfigAndCreateContext() - Loads and validates config
↓
codegen() - Generates styled-system directory
↓
setupGitIgnore() - Updates .gitignore
User runs `panda`
↓
CLI (packages/cli)
↓
Builder.setup()
├─→ Find config (packages/config)
├─→ Load config with presets
├─→ Create Context (packages/core)
│ ├─→ Initialize TokenDictionary
│ ├─→ Initialize Utility engine
│ ├─→ Initialize Recipes
│ ├─→ Initialize Patterns
│ └─→ Initialize Conditions
↓
Builder.emit() - Generate baseline CSS & JS
↓
Builder.extract() - Scan source files
├─→ Fast-glob finds files
├─→ Parser (packages/parser)
│ ├─→ ts-morph creates AST
│ ├─→ Extractor (packages/extractor)
│ │ └─→ Extract style objects
│ └─→ Returns ParserResult
↓
Generator (packages/generator)
├─→ Generate artifacts
│ ├─→ CSS files (tokens, utilities, reset)
│ ├─→ JS files (css, cva, sva, patterns)
│ ├─→ TypeScript types
│ └─→ Framework JSX factories
↓
Builder.write() - Write files to styled-system
↓
Optimize CSS (postcss plugins)
└─→ Format with prettier
Build tool runs PostCSS
↓
@pandacss/postcss plugin
↓
Builder.setup() - Same as above
↓
Builder.isValidRoot() - Check for Panda CSS
↓
Builder.emit() - Generate artifacts
↓
Builder.extract() - Extract from changed files
↓
Builder.write(root) - Inject CSS into PostCSS AST
↓
Register dependencies for HMR
Source file (e.g., App.tsx)
↓
ts-morph creates SourceFile
↓
getImportDeclarations() - Find panda imports
├─→ css(), cva(), styled(), token(), etc.
↓
extract() from @pandacss/extractor
├─→ Scan for function calls
│ └─→ Match against ImportMap
├─→ Scan for JSX elements
│ ├─→ Match styled.div, panda.div
│ └─→ Match JSX props (if jsxStyleProps enabled)
↓
For each match:
├─→ box() - Wrap AST node
├─→ Evaluate statically (with token resolution if needed)
└─→ unbox() - Extract value
↓
Parser switch statement (packages/parser/src/parser.ts:123-334)
├─→ .when(imports.matchers.css.match) - Handle css/cva/sva
├─→ .when(imports.matchers.tokens.match) - Handle token() calls
├─→ .when(file.isValidPattern) - Handle pattern functions
├─→ .when(file.isValidRecipe) - Handle recipe functions
└─→ .when(jsx.isJsxFactory) - Handle JSX factory calls
↓
ParserResult
├─→ set('css', ...) - Store CSS styles
├─→ setToken(...) - Store token references
├─→ setPattern(...) - Store pattern usage
├─→ setRecipe(...) - Store recipe usage
├─→ setJsx(...) - Store JSX component styles
↓
StyleEncoder - Encode to atomic classes
├─→ Store in StyleDecoder
└─→ Return extracted styles
Token Extraction Details:
When token() is encountered:
imports.matchers.tokens.matchidentifies token imports (packages/core/src/import-map.ts:25)- Parser extracts the token CallExpression arguments (packages/parser/src/parser.ts:165-176)
- Extractor evaluates the token path and optional fallback (packages/extractor/src/maybe-box-node.ts)
- TokenDictionary resolves the path:
get(path)returns raw value for base tokensgetVar(path)returns CSS variable for semantic/virtual tokens
- Result is stored in ParserResult with
setToken()
Generator.getArtifacts()
↓
generateArtifacts() dispatches to:
├─→ CSS Artifacts
│ ├─→ generateResetCss() - Preflight/reset
│ ├─→ generateTokenCss() - CSS variables
│ ├─→ generateStaticCss() - Static utilities
│ ├─→ generateGlobalCss() - Global styles
│ └─→ generateKeyframeCss() - @keyframes
├─→ JS Artifacts
│ ├─→ css.mjs - Main styling API
│ ├─→ cva.mjs - Component variants
│ ├─→ sva.mjs - Slot variants
│ ├─→ patterns/*.mjs - Layout patterns
│ └─→ recipes/*.mjs - Recipe functions
├─→ JSX Artifacts (framework-specific)
│ ├─→ styled.mjs - styled('div') API
│ └─→ factory.mjs - JSX factory
└─→ Type Artifacts
├─→ style-props.d.ts
├─→ pattern.d.ts
└─→ recipes/types.d.ts
- Context class is the central orchestrator
- All engines (Utility, Recipes, Patterns, etc.) are initialized in Context
- Context is passed down to all subsystems
- Provides unified access to configuration, tokens, and utilities
- Specialized engines handle different concerns:
- Utility: CSS utility generation
- Recipes: Component recipes
- Patterns: Layout patterns
- JsxEngine: JSX analysis
- PathEngine: File path management
- FileEngine: File template management
- Builder class manages incremental builds
- Tracks file changes and config diffs
- Coordinates setup → extract → generate → write cycle
- Handles file watching and HMR
- StyleEncoder: Converts style objects to atomic class names
- StyleDecoder: Collects all encoded styles for CSS generation
- Enables atomic CSS with automatic deduplication
- Boxing: Wraps AST nodes with metadata and utilities
- Evaluation: Computes static values
- Unboxing: Extracts final values
- Handles complex expressions, spreads, and conditionals
Panda CSS has a dual-mode token resolution system that handles tokens differently based on context:
When token() appears as a CallExpression in the AST (i.e., actually called as a function):
// Template literal interpolation with CallExpression
const styles = css({
border: `1px solid ${token('colors.yellow.100')}` // token() is called
})Resolution: The extractor evaluates token() calls at build time and resolves them to:
- Base tokens (no conditions) → Raw value (e.g.,
"#fef9c3") - Semantic/conditional tokens → CSS variable (e.g.,
"var(--colors-primary)") - Virtual tokens (colorPalette) → CSS variable (e.g.,
"var(--colors-color-palette-500)")
Key files:
packages/extractor/src/maybe-box-node.ts- Handles token CallExpression evaluationpackages/token-dictionary/src/dictionary.ts-get()returns raw values,getVar()returns CSS variables
When token(...) appears as a string pattern (not executed as a function):
// String literal with token pattern
const styles = css({
border: "1px solid token(colors.yellow.100)" // Plain string, no CallExpression
})Resolution: The RuleProcessor pattern-matches the string after parsing via expandReferenceInValue():
- Finds pattern:
token(path.to.token) - Always resolves to CSS variable:
"var(--path-to-token)"
Key files:
packages/token-dictionary/src/dictionary.ts:392-411-expandReferenceInValue()methodpackages/core/src/utility.ts:262-getToken()usesgetVar()for CSS variable outputpackages/core/src/utility.ts:400-defaultTransform()usesgetVar()for CSS custom properties
| Context | Example | Resolution | Output |
|---|---|---|---|
| Template literal + CallExpression | `1px solid ${token('colors.yellow.100')}` |
AST evaluation → Raw value | "1px solid #fef9c3" |
| String pattern (no call) | "1px solid token(colors.yellow.100)" |
RuleProcessor → CSS variable | "1px solid var(--colors-yellow-100)" |
| Object property + CallExpression | { color: token('colors.red.500') } |
AST evaluation → Raw value | { color: "#ef4444" } |
| Object property + token.var() | { color: token.var('colors.red.500') } |
AST evaluation → CSS variable | { color: "var(--colors-red-500)" } |
This dual-mode system exists because:
- Template literals with interpolation execute functions at runtime, so build-time evaluation must match runtime behavior
- String patterns are processed by Panda's RuleProcessor and can be transformed to CSS variables for dynamic theming
- Semantic tokens (with conditions) must always use CSS variables to support responsive/conditional values
- Users can explicitly request CSS variables with
token.var()when needed in CallExpressions
- File tracking: Tracks modified times of source and config files
- Dependency graph: Knows which files affect what artifacts
- Smart invalidation: Only regenerates changed artifacts
- Config diffing: Detects specific config changes to minimize regeneration
- Atomic CSS: Each unique style gets one class
- Layer ordering: Uses
@layerfor predictable cascade - PostCSS pipeline:
postcss-nested- Unwrap nested rulespostcss-merge-rules- Merge duplicate selectorspostcss-discard-duplicates- Remove duplicate rulespostcss-minify-selectors- Optimize selectorslightningcss- Fast minification and vendor prefixing
- Separate artifacts for different concerns
- Lazy-loadable pattern and recipe functions
- Tree-shakeable exports
// Utility props
css({ color: 'red.500' }) // Autocomplete for 'red.500'
// Pattern props
stack({ gap: '4' }) // Autocomplete for spacing tokens
// Recipe variants
button({ size: 'lg', variant: 'solid' }) // Autocomplete variantsUser config (theme.tokens)
↓
TokenDictionary processes tokens
↓
Generator creates type definitions
├─→ Token paths as string literals
├─→ Recipe variant types
├─→ Pattern prop types
└─→ Utility prop types
↓
TypeScript provides autocomplete
Each framework gets a custom JSX factory:
// React
import { styled } from './styled-system/jsx'
<styled.div color="red.500" />
// Solid
import { styled } from './styled-system/jsx/solid'
<styled.div color="red.500" />
// Vue
import { styled } from './styled-system/jsx/vue'
<styled.div :color="'red.500'" />- React/Preact: Standard JSX
- Vue: SFC parsing with
@vue/compiler-sfc - Svelte: Component parsing with custom transformer
- Solid: JSX with Solid-specific patterns
Panda provides a hookable API for extensibility:
hooks: {
'tokens:created': (args) => { /* Modify tokens */ },
'parser:before': (args) => { /* Pre-parsing */ },
'parser:after': (args) => { /* Post-parsing */ },
'cssgen:done': (args) => { /* Post-CSS generation */ },
'codegen:prepare': (args) => { /* Pre-codegen */ },
}Plugins can:
- Modify token dictionary
- Add custom utilities
- Transform generated code
- Integrate with build tools
- Vitest: Test runner with globals
- Happy-dom: Browser environment simulation
- Unit tests: Per-package in
__tests__directories - Integration tests: In sandbox directories
- Fixtures: Sample projects in
packages/fixture
- Config loading: Various config formats and merging
- Token processing: Token transformation and references
- Parser: AST extraction accuracy
- Generator: Artifact generation correctness
- CSS output: Style transformation and optimization
- Type generation: TypeScript type correctness
pnpm install # Install dependencies
pnpm build-fast # Quick build without types
pnpm dev # Watch mode for packages
pnpm playground # Run playground examplesbuild: Full build with typesbuild-fast: Quick build, no type generationdev: Watch modetest: Run teststypecheck: TypeScript validation
- Changes tracked with Changesets (
.changeset/) - Run
pnpm changesetto document changes - Changesets generates changelog and version bumps
pnpm releasepublishes to npm
- Lazy imports: Delays loading until needed
- Parallel processing: Uses
pnpm --parallelfor multi-package builds - Caching: ts-morph caches parsed ASTs
- Fast glob: Efficient file scanning
- Zero runtime: All styles generated at build time
- Minimal JS: Only necessary utility functions shipped
- CSS variables: Dynamic theming without JS
- Tree-shaking: Unused utilities eliminated
- Memoization: Expensive computations cached
- Streaming: Large file processing in chunks
- Context cleanup: Proper disposal of resources
- Reporter package: Formatted, helpful error messages
- Logger levels: debug, info, warn, error, silent
- Stack traces: Preserved for debugging
- Suggestions: Actionable error messages
- Config errors: Invalid configuration
- Parse errors: Malformed source code
- Generation errors: Failed artifact creation
- File system errors: Permission/access issues
Pitfall: Using tokens.view.get() when CSS variables are needed
// ❌ WRONG - Returns raw value when CSS variable needed
const tokenValue = this.tokens.view.get(path) // Returns "#ef4444"
// ✅ CORRECT - Returns CSS variable for RuleProcessor
const tokenValue = this.tokens.view.getVar(path) // Returns "var(--colors-red-500)"When to use each:
- Use
get(): When evaluating token CallExpressions in AST (extractor phase) - Use
getVar(): When processing string patterns in RuleProcessor (expandReferenceInValue, getToken, defaultTransform)
Common locations that need getVar():
packages/core/src/utility.ts:262-getToken()methodpackages/core/src/utility.ts:400-defaultTransform()methodpackages/token-dictionary/src/dictionary.ts:392-411-expandReferenceInValue()method
Pitfall: Including function names in matcher type that aren't actually matched
// ❌ WRONG - 'token' is not matched by imports.matchers.css
.when(imports.matchers.css.match, (name: 'css' | 'cva' | 'sva' | 'token') => {
// This will never receive 'token' as name!
})
// ✅ CORRECT - Separate matchers for different function types
.when(imports.matchers.css.match, (name: 'css' | 'cva' | 'sva') => {
// Handle css/cva/sva
})
.when(imports.matchers.tokens.match, (name: 'token') => {
// Handle token
})Check matcher configuration: Look at packages/core/src/import-map.ts to see what each matcher actually matches.
Pitfall: Confusing template literal interpolation with string patterns
// Template literal with CallExpression - Resolved at AST extraction
border: `1px solid ${token('colors.yellow.100')}`
// → Result: "1px solid #fef9c3" (raw value)
// String pattern - Resolved by RuleProcessor
border: "1px solid token(colors.yellow.100)"
// → Result: "1px solid var(--colors-yellow-100)" (CSS variable)Debugging tip: Check if token() appears inside ${} interpolation or as plain text in the string.
Pitfall: Adding new function types to ImportMap but forgetting to add parser handlers
Checklist when adding new function types:
- ✅ Add to
packages/core/src/import-map.tsmatcher configuration - ✅ Add
.when()case inpackages/parser/src/parser.tsswitch statement - ✅ Add storage method in
packages/parser/src/parser-result.ts(e.g.,setToken()) - ✅ Add result type to
packages/types/src/parser.tsif needed - ✅ Write tests in
packages/parser/__tests__/
Enable debug logging:
DEBUG=* pandaLook for these log messages:
ast:import- Shows what imports were foundast:css,ast:token, etc. - Shows what functions were extracted- Token resolution paths in extractor
Test both modes:
- Write tests for CallExpression evaluation (token.test.ts)
- Write tests for string pattern resolution (output.test.ts)
- Ensure semantic and virtual tokens resolve to CSS variables
- Custom pattern definitions
- Plugin marketplace
- Framework adapters
- Build tool integrations
- Multi-threaded parsing for large codebases
- Distributed caching for monorepos
- Incremental type checking
- Remote artifact caching
Panda CSS architecture emphasizes:
- Modularity: Clear separation of concerns across packages
- Performance: Build-time optimization and zero runtime overhead
- Type safety: Full TypeScript integration with generated types
- Flexibility: Framework-agnostic with multiple integration points
- Developer experience: Autocomplete, helpful errors, and visual tools
The system is designed to scale from small projects to large monorepos while maintaining fast build times and excellent developer experience.