Find what nobody uses
Run panda analyze in each app before you remove a token, recipe, utility, pattern, keyframe, or composition.
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
compositions 41 uses, 9 unique
Keyframes 10/25 used (40.00%)
Keyframe Uses Used in
fade-in 7 1 file
fade-out 7 1 file
spin 2 globalCss, via animations.spin
Unused (15): bounce, marqueeScroll, ping, pulse, scale-in, +10 moreA use is anywhere a name is written: a source file, or a place in your config. "Used in" lists both. Config places look
like globalCss, recipes.button, or staticCss.css. A name pulled in by something else reads via that thing, like
a keyframe via animations.spin or a token via textStyles.body.
--scope prints one section. --limit caps the rows and 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 19A token used only by a recipe, a global style, or another token counts as used. Files counts source files only.
Recipes
Which variants get picked, and whether a recipe is reached through JSX or a function call. A recipe requested by
staticCss counts as used. Defining a recipe alone doesn't.
pnpm panda analyze --scope recipes --limit 3Recipes
Recipe Variants Top variants Used in Used as
button 3/13 (23.08%) color.neutral (2), size.icon (1), size.xs (1) 5 files jsx 33%, fn 67%
segmented 2/6 (33.33%) size.sm (2), tone.pill (2) 3 files, staticCss.recipes.segmented jsx 67%, fn 33%
callout 0/4 (0.00%) - 1 file jsx 0%, fn 100%Utilities
Which properties dominate, and which configured utilities nobody uses. That's how you decide what to prune from a
preset. 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 Used in
color 197 54 files, globalCss, recipes.button
background 123 53 files
display 119 44 files
Unused (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 Used in
box 132 25 files
stack 47 31 files
hstack 40 23 files
Unused (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. A keyframe reached through an animation token or an animation style shows via that token or style.
pnpm panda analyze --scope keyframes --limit 3Keyframes 10/25 used (40.00%)
Keyframe Uses Used in
fade-in 7 1 file
slide-from-left-full 2 1 file
spin 1 via animations.spin
Unused (15): bounce, marqueeScroll, ping, +12 moreCompositions
Which text styles, layer styles, and animation styles get applied. Names carry their group, such as textStyles.body or
animationStyles.slide-fade-in.
pnpm panda analyze --scope compositions --limit 3Compositions 9/14 used (64.29%)
Composition Uses Used in
textStyles.body 19 9 files, globalCss
layerStyles.card 12 7 files
animationStyles.slide-fade-in 4 2 files
Unused (5): textStyles.4xl, textStyles.5xl, +3 more"Unused" means no scanned file and no part of your config uses the name, directly or through something else. Usage in another app, or a value built only at runtime, doesn't show here. Run it in every consumer before you remove anything.
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
Tokens (477)
animations.bounce
animations.ping
animations.pulse
…
Recipes (0)
Utilities (290)
WebkitTextFillColor
accentColor
…
Keyframes (15)
bounce
marqueeScroll
…
Compositions (5)
textStyles.4xl
…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.
views.<scope>.unusedlists removal candidates.views.<scope>.itemshas one row per used name.usescounts source sites plus config places,filescounts source files, andconfiglists the config places, such as["globalCss", "via colors.brand"].usageshas one entry per source site, with kind, name, file, line, and column.
pnpm panda analyze --outfile analyze.json
jq '.views.tokens.unused[]' analyze.json
jq '.usages[] | select(.name == "colors.brand.300")' analyze.json
jq '.views.keyframes.items[] | select(.config)' analyze.jsonNext: mark it deprecated and ship the warning before the removal.