Auto-generate Panda CSS staticCss values by scanning component-boundary usage (like wrappers using .raw()), then inject them at Panda config:resolved time.
Panda can miss values across component boundaries. Example: a wrapper component calls flex.raw({ ...props }) internally, and consumers pass dynamic/conditional/responsive JSX props. This plugin adds a pre-scan that resolves those values before Panda CSS generation.
- Discovers boundary components automatically by finding
.raw()calls. - Reads pattern definitions (
definePattern) to map component props to CSS properties. - Scans consumers (
.tsx/.jsx) with SWC and extracts supported values:- literals
- negative numbers
- ternaries
- responsive objects (
{ base, md, ... }and ranges) - identifier indirection
- local helper functions
useMemo(() => ...)
- Injects unconditional token sets (for example
colorPalette) from source of truth files. - Writes deterministic JSON output (write-on-change only).
- Panda plugin reads JSON and injects generated rules into
staticCss.css.
pnpm add -D panda-plugin-static-extractimport { defineConfig } from '@pandacss/dev'
import { staticExtractPlugin } from 'panda-plugin-static-extract'
export default defineConfig({
plugins: [staticExtractPlugin()],
})panda-static-extract scan
panda cssgenFor Nx/Turbo, wire scan as a dependency of CSS generation.
panda-static-extract scan
panda-static-extract audit
panda-static-extract watchCommon flags:
--output <path>--workspace-root <path>--scan-root <path>(repeatable)--minimum-values <n>--strict/--no-strict--verbose--debounce <ms>(default300)--poll(watch fallback mode)--skip-initial(watch mode)
- Debounce is built in and defaults to
300ms. - Re-runs are queued (no overlapping scans).
- Fast-path trigger:
- if changed file is an already-known consumer, rescan immediately
- else check if changed source now contains tracked component markers
- Non-TSX changes to relevant sources (patterns/color palette) also trigger scan.
staticExtractPlugin({
scanRoots: ['libs', 'apps'],
outputPath: '.panda/static-extract.json',
minimumValues: 5,
strict: true,
verbose: false,
})Auto-detection defaults:
- workspace root markers:
pnpm-workspace.yaml,nx.json,turbo.json,lerna.json,.git - scan roots:
libs/apps, thenpackages/apps, thenpackages, thensrc - breakpoints file candidates:
src/presets/breakpoints.tssrc/theme/breakpoints.tssrc/breakpoints.ts
- color palette file candidates:
src/presets/themes/colorPalette/colorPalette.tssrc/theme/colorPalette.tssrc/colorPalette.ts
Generated JSON example:
{
"schemaVersion": 1,
"generatedBy": "panda-plugin-static-extract",
"trackedComponents": ["FlexBox", "FlexItem"],
"breakpointKeys": ["base", "md", "sm", "smToMd"],
"filesScanned": 1909,
"filesMatched": 853,
"unconditionalProperties": ["colorPalette"],
"responsive": { "gap": ["sm", "lg"] },
"nonResponsive": { "colorPalette": ["brand", "neutral"] }
}Manual staticCss vs auto-extraction (same include paths; only staticCss strategy changed):
| Metric | Manual staticCss | Auto extraction |
|---|---|---|
| CSS size | 732KB | 171KB |
| CSS lines | 24,853 | 3,874 |
| Cold build time | 26.7s | 11.2s |
| Panda extraction stage | 2353ms | 141ms |
Main gain came from removing wildcard over-generation and generating only observed values.
Implemented end-to-end:
- scanner modules fully implemented
- plugin injection with schema checks
- CLI
scan/audit/watch - deterministic write behavior
- integration tests for scanner and plugin
Validation:
pnpm typecheckpnpm testpnpm build
GitHub Actions workflows included:
CI:.github/workflows/ci.ymlRelease:.github/workflows/release.yml
Release strategy uses semantic-release on main:
- analyze conventional commits
- compute next semver version
- update
CHANGELOG.md - publish to npm with provenance
- create GitHub release and tag
Required repository secret:
NPM_TOKEN
Use conventional commits so semantic-release can decide the next version:
fix: ...=> patchfeat: ...=> minorfeat!: ...orBREAKING CHANGE:=> major
pnpm run verify
pnpm run release -- --dry-run