Find what nobody uses
Run panda analyze in each app before you remove a token, recipe, utility, pattern, or keyframe.
Removing a token that an app still uses doesn't fail the build. The reference is emitted as a literal CSS value and
the page quietly loses its color. panda analyze tells you, per app, what is safe to drop.
Run it
Run it in the app, not the design-system package. It scans the app's sources and reports against the theme the app resolved.
pnpm panda analyze --limit 5analyze: scanned 151 files
Summary
tokens 1032 uses, 101 unique
recipes 29 uses, 12 unique
utilities 2299 uses, 140 unique
patterns 270 uses, 17 unique
keyframes 26 uses, 10 unique
Keyframes 10/25 used (40.00%)
Keyframe Uses Files
fade-in 7 1
fade-out 7 1
Unused in scanned sources (15): bounce, marqueeScroll, ping, pulse, scale-in, +10 moreEvery section prints the same way: a ranked table, then the configured names no scanned file touches. --scope
prints one section and --limit caps the rows and the unused names.
Read the output
Each section answers one question, and its last line is the list you act on.
Tokens
Which tokens carry the design, and which raw values could have been tokens. A category at 12% used is a palette you can shrink.
pnpm panda analyze --scope tokens --limit 3Tokens
Category Used Top tokens Raw values Files
colors 42/352 (11.93%) fg (60), border (50), fg.subtle (47) 18 64
spacing 27/71 (38.03%) 4 (66), 2 (57), 3 (53) 10 69
fontSizes 11/15 (73.33%) md (14), sm (9), 3xl (8) 1 19Recipes
Which variants get picked, and whether a recipe is reached through JSX or a function call. A variant nobody selects is CSS you ship for nothing.
pnpm panda analyze --scope recipes --limit 3Recipes
Recipe Variants Top variants Files Used as
button 3/13 (23.08%) color.neutral (2), size.icon (1), size.xs (1) 5 jsx 33%, fn 67%
segmented 2/6 (33.33%) size.sm (2), tone.pill (2) 3 jsx 67%, fn 33%
callout 0/4 (0.00%) - 1 jsx 0%, fn 100%Utilities
Which properties dominate, and which configured utilities no file touches. That's how you decide what to prune from
a preset. It's the biggest bucket in any app, and a count alone tells you nothing about which ones. Shorthands count
as their longhand, so p and padding are one row.
pnpm panda analyze --scope utilities --limit 3Utilities 120/410 used (29.27%)
Utility Uses Files
color 195 54
background 123 53
display 119 44
Unused in scanned sources (290): WebkitTextFillColor, accentColor, alignContent, +287 morePatterns
Whether anyone reaches for hstack, or everyone wrote flex by hand. That decides whether a pattern earns its place
in the generated code.
pnpm panda analyze --scope patterns --limit 3Patterns 10/20 used (50.00%)
Pattern Uses Files
box 132 25
stack 47 31
hstack 40 23
Unused in scanned sources (10): aspectRatio, bleed, cq, +7 moreKeyframes
Which keyframes are dead. A keyframe nobody references is pure CSS weight, which is why the removeUnusedKeyframes
optimizer exists. This list is what it would drop.
pnpm panda analyze --scope keyframes --limit 3Keyframes 10/25 used (40.00%)
Keyframe Uses Files
fade-in 7 1
fade-out 7 1
slide-from-left-full 2 1
Unused in scanned sources (15): bounce, marqueeScroll, ping, +12 more"Unused" means unused in the files this run scanned. Usage in another app, or behind a dynamic value, doesn't show here, so run it in every consumer before you decide.
Decide: remove or deprecate
Zero uses across every consuming app: remove it in the next major. Any uses at all: deprecate it first, so those apps get warned while they migrate.
--unused skips the tables and prints only the names nobody uses, one per line and uncapped. That is the list you
paste into the deprecation PR, or diff between two runs.
pnpm panda analyze --unusedanalyze: scanned 151 files
Unused in scanned sources
Tokens (477)
animations.bounce
animations.ping
animations.pulse
…
Recipes (0)
Utilities (290)
WebkitTextFillColor
accentColor
…
Keyframes (15)
bounce
marqueeScroll
…With a scope it prints the names and nothing else, so it pipes:
pnpm panda analyze --scope keyframes --unused | xargsScript it
--outfile writes the full report as JSON and keeps the terminal output. --json sends the same payload to stdout.
The usages list has one entry per site, with kind, name, file, line, and column, so a script can answer the removal
question without joining tables.
pnpm panda analyze --outfile analyze.json
jq '.usages[] | select(.name == "colors.brand.300")' analyze.jsonNext: mark it deprecated and ship the warning before the removal.