[{"data":1,"prerenderedAt":1734},["ShallowReactive",2],{"blog-\u002Fblog\u002Fknow-what-your-css-actually-uses":3,"cover-\u002Fblog\u002Fknow-what-your-css-actually-uses":1733},{"id":4,"title":5,"body":6,"canonical":1720,"date":1721,"description":1722,"draft":1723,"duration":1720,"extension":705,"lang":1720,"meta":1724,"navigation":1492,"part":1725,"path":1726,"place":1720,"placeLink":1720,"readingTime":1727,"seo":1728,"seoTitle":1729,"series":1730,"stem":1731,"updated":1720,"__hash__":1732},"blog\u002Fblog\u002Fknow-what-your-css-actually-uses.md","Know what your CSS actually uses",{"type":7,"value":8,"toc":1703},"minimark",[9,21,24,40,43,50,55,58,76,100,103,108,112,115,361,368,398,421,425,428,449,496,515,518,522,525,555,585,588,594,605,636,651,654,676,680,695,707,718,722,735,761,776,790,793,800,804,821,844,856,860,885,906,911,915,921,941,966,969,980,984,987,990,1019,1033,1039,1043,1052,1076,1082,1089,1092,1098,1114,1118,1121,1135,1138,1149,1153,1158,1171,1261,1264,1305,1308,1312,1315,1345,1349,1411,1419,1692,1699],[10,11,12],"subtitle",{},[13,14,15,16,20],"p",{},"A guide to ",[17,18,19],"code",{},"panda analyze",", for teams who want to clean up their Panda setup without breaking anything.",[22,23],"post-cover",{},[13,25,26],{},[27,28,29,30,35,36,39],"em",{},"An interlude in the Panda internals series, between ",[31,32,34],"a",{"href":33},"\u002Fblog\u002Fhow-panda-makes-cva-fast","part 6"," and part 7. A side episode: the parts so far explain how Panda works, and this one is about a tool that helps your team use it well. There's a kit at the end to take with you: an agent skill, rules for your ",[17,37,38],{},"AGENTS.md",", prompts, and a CI check.",[13,41,42],{},"Every Panda project grows a little extra over time. A token nobody uses anymore. A recipe variant from a redesign. A keyframe someone added for one animation and then removed the animation. A hex code that should have been a token.",[13,44,45,46,49],{},"None of that breaks the build. So it stays, and nobody notices. Deleting things is no safer: remove a token your app still uses and the build won't fail either. Panda writes ",[17,47,48],{},"color: brand\\.500"," into the CSS as-is, the browser ignores it, and the page quietly loses its color.",[51,52,54],"h2",{"id":53},"why-it-matters-more-now","Why it matters more now",[13,56,57],{},"A lot of styling code is now written by agents, and an agent learns your design system from what's already there.",[13,59,60,64,65,71,72,75],{},[61,62,63],"strong",{},"It reads your code first."," Claude Code's ",[31,66,70],{"href":67,"rel":68},"https:\u002F\u002Fcode.claude.com\u002Fdocs\u002Fen\u002Fbest-practices",[69],"nofollow","best practices"," put it plainly: \"Claude starts every task by reading your code\", and it picks up \"your conventions\" and \"how existing components are built\". To an agent, a ",[17,73,74],{},"#6366f1"," sitting in a component looks like a convention. The next component it writes gets one too.",[13,77,78,81,82,85,86,91,92,95,96,99],{},[61,79,80],{},"It reads your theme as a list of options."," Whether an agent opens your ",[17,83,84],{},"panda.config.ts"," or asks Panda's ",[31,87,90],{"href":88,"rel":89},"https:\u002F\u002Fpanda-css.com\u002Fdocs\u002Fget-started\u002Fmcp-server",[69],"MCP server"," for your tokens, it sees every token you've defined. Not the ones your app uses, all of them. A token nobody has touched in a year looks just as valid as ",[17,93,94],{},"brand.500",", unless you've deprecated it. The same goes for recipe variants. If ",[17,97,98],{},"size.sm"," is defined, an agent has every reason to use it.",[13,101,102],{},"The same Claude Code page suggests writing down \"an old pattern that's everywhere but that new code must not copy\". That works, but the cleaner fix is to not have the old pattern. Every unused token you remove and every hard-coded value you replace is one less wrong example for the next agent, and for the next person.",[13,104,105,107],{},[17,106,19],{}," tells you what your app actually uses. This post walks through the questions it answers, with real output from a small app.",[51,109,111],{"id":110},"the-example-app","The example app",[13,113,114],{},"Say we have an app with a heading, a paragraph and two buttons:",[116,117,122],"pre",{"className":118,"code":119,"language":120,"meta":121,"style":121},"language-tsx shiki shiki-themes github-light github-dark","export function App() {\n  return (\n    \u003CStack gap=\"4\" p=\"6\">\n      \u003Ch1 className={css({ color: 'brand.500', fontSize: 'xl' })}>Welcome\u003C\u002Fh1>\n      \u003Cp className={css({ color: '#6366f1', padding: '16px' })}>Get started\u003C\u002Fp>\n      \u003CButton size=\"md\" tone=\"solid\">Go\u003C\u002FButton>\n      \u003CButton size=\"md\" tone=\"ghost\">Later\u003C\u002FButton>\n      \u003Cspan className={css({ animation: 'spin' })} \u002F>\n    \u003C\u002FStack>\n  )\n}\n","tsx","",[17,123,124,144,153,184,224,257,288,315,339,349,355],{"__ignoreMap":121},[125,126,129,133,136,140],"span",{"class":127,"line":128},"line",1,[125,130,132],{"class":131},"szBVR","export",[125,134,135],{"class":131}," function",[125,137,139],{"class":138},"sScJk"," App",[125,141,143],{"class":142},"sVt8B","() {\n",[125,145,147,150],{"class":127,"line":146},2,[125,148,149],{"class":131},"  return",[125,151,152],{"class":142}," (\n",[125,154,156,159,163,166,169,173,176,178,181],{"class":127,"line":155},3,[125,157,158],{"class":142},"    \u003C",[125,160,162],{"class":161},"sj4cs","Stack",[125,164,165],{"class":138}," gap",[125,167,168],{"class":131},"=",[125,170,172],{"class":171},"sZZnC","\"4\"",[125,174,175],{"class":138}," p",[125,177,168],{"class":131},[125,179,180],{"class":171},"\"6\"",[125,182,183],{"class":142},">\n",[125,185,187,190,194,197,199,202,205,208,211,214,217,220,222],{"class":127,"line":186},4,[125,188,189],{"class":142},"      \u003C",[125,191,193],{"class":192},"s9eBZ","h1",[125,195,196],{"class":138}," className",[125,198,168],{"class":131},[125,200,201],{"class":142},"{",[125,203,204],{"class":138},"css",[125,206,207],{"class":142},"({ color: ",[125,209,210],{"class":171},"'brand.500'",[125,212,213],{"class":142},", fontSize: ",[125,215,216],{"class":171},"'xl'",[125,218,219],{"class":142}," })}>Welcome\u003C\u002F",[125,221,193],{"class":192},[125,223,183],{"class":142},[125,225,227,229,231,233,235,237,239,241,244,247,250,253,255],{"class":127,"line":226},5,[125,228,189],{"class":142},[125,230,13],{"class":192},[125,232,196],{"class":138},[125,234,168],{"class":131},[125,236,201],{"class":142},[125,238,204],{"class":138},[125,240,207],{"class":142},[125,242,243],{"class":171},"'#6366f1'",[125,245,246],{"class":142},", padding: ",[125,248,249],{"class":171},"'16px'",[125,251,252],{"class":142}," })}>Get started\u003C\u002F",[125,254,13],{"class":192},[125,256,183],{"class":142},[125,258,260,262,265,268,270,273,276,278,281,284,286],{"class":127,"line":259},6,[125,261,189],{"class":142},[125,263,264],{"class":161},"Button",[125,266,267],{"class":138}," size",[125,269,168],{"class":131},[125,271,272],{"class":171},"\"md\"",[125,274,275],{"class":138}," tone",[125,277,168],{"class":131},[125,279,280],{"class":171},"\"solid\"",[125,282,283],{"class":142},">Go\u003C\u002F",[125,285,264],{"class":161},[125,287,183],{"class":142},[125,289,291,293,295,297,299,301,303,305,308,311,313],{"class":127,"line":290},7,[125,292,189],{"class":142},[125,294,264],{"class":161},[125,296,267],{"class":138},[125,298,168],{"class":131},[125,300,272],{"class":171},[125,302,275],{"class":138},[125,304,168],{"class":131},[125,306,307],{"class":171},"\"ghost\"",[125,309,310],{"class":142},">Later\u003C\u002F",[125,312,264],{"class":161},[125,314,183],{"class":142},[125,316,318,320,322,324,326,328,330,333,336],{"class":127,"line":317},8,[125,319,189],{"class":142},[125,321,125],{"class":192},[125,323,196],{"class":138},[125,325,168],{"class":131},[125,327,201],{"class":142},[125,329,204],{"class":138},[125,331,332],{"class":142},"({ animation: ",[125,334,335],{"class":171},"'spin'",[125,337,338],{"class":142}," })} \u002F>\n",[125,340,342,345,347],{"class":127,"line":341},9,[125,343,344],{"class":142},"    \u003C\u002F",[125,346,162],{"class":161},[125,348,183],{"class":142},[125,350,352],{"class":127,"line":351},10,[125,353,354],{"class":142},"  )\n",[125,356,358],{"class":127,"line":357},11,[125,359,360],{"class":142},"}\n",[13,362,363,364,367],{},"A second file turns the ",[17,365,366],{},"button"," recipe into a component:",[116,369,371],{"className":118,"code":370,"language":120,"meta":121,"style":121},"export const Button = styled('button', button)\n",[17,372,373],{"__ignoreMap":121},[125,374,375,377,380,383,386,389,392,395],{"class":127,"line":128},[125,376,132],{"class":131},[125,378,379],{"class":131}," const",[125,381,382],{"class":161}," Button",[125,384,385],{"class":131}," =",[125,387,388],{"class":138}," styled",[125,390,391],{"class":142},"(",[125,393,394],{"class":171},"'button'",[125,396,397],{"class":142},", button)\n",[13,399,400,401,404,405,408,409,412,413,416,417,420],{},"The recipe lives in the config, with ",[17,402,403],{},"size: sm | md | lg"," and ",[17,406,407],{},"tone: solid | ghost",". There's also a ",[17,410,411],{},"brand"," color palette, a ",[17,414,415],{},"spinSlow"," keyframe, and one line of ",[17,418,419],{},"globalCss",".",[51,422,424],{"id":423},"start-with-the-summary","Start with the summary",[13,426,427],{},"Run it in the app:",[116,429,433],{"className":430,"code":431,"language":432,"meta":121,"style":121},"language-bash shiki shiki-themes github-light github-dark","panda analyze --limit 5\n","bash",[17,434,435],{"__ignoreMap":121},[125,436,437,440,443,446],{"class":127,"line":128},[125,438,439],{"class":138},"panda",[125,441,442],{"class":171}," analyze",[125,444,445],{"class":161}," --limit",[125,447,448],{"class":161}," 5\n",[116,450,454],{"className":451,"code":452,"language":453,"meta":121,"style":121},"language-txt shiki shiki-themes github-light github-dark","analyze: scanned 2 files\nSummary\ntokens         10 uses, 8 unique\nrecipes        3 uses, 1 unique\nutilities      15 uses, 11 unique\npatterns       1 uses, 1 unique\nkeyframes      1 uses, 1 unique\ncompositions   0 uses, 0 unique\n","txt",[17,455,456,461,466,471,476,481,486,491],{"__ignoreMap":121},[125,457,458],{"class":127,"line":128},[125,459,460],{},"analyze: scanned 2 files\n",[125,462,463],{"class":127,"line":146},[125,464,465],{},"Summary\n",[125,467,468],{"class":127,"line":155},[125,469,470],{},"tokens         10 uses, 8 unique\n",[125,472,473],{"class":127,"line":186},[125,474,475],{},"recipes        3 uses, 1 unique\n",[125,477,478],{"class":127,"line":226},[125,479,480],{},"utilities      15 uses, 11 unique\n",[125,482,483],{"class":127,"line":259},[125,484,485],{},"patterns       1 uses, 1 unique\n",[125,487,488],{"class":127,"line":290},[125,489,490],{},"keyframes      1 uses, 1 unique\n",[125,492,493],{"class":127,"line":317},[125,494,495],{},"compositions   0 uses, 0 unique\n",[13,497,498,499,502,503,506,507,510,511,514],{},"Then you get one table per section: tokens, recipes, utilities, patterns, keyframes, and compositions (text styles, layer styles, animation styles). ",[17,500,501],{},"--limit"," caps how many rows each table shows. ",[17,504,505],{},"--scope tokens"," (or ",[17,508,509],{},"recipes",", ",[17,512,513],{},"keyframes",", and so on) prints just one section.",[13,516,517],{},"The rest of this post is the questions you'd actually bring to it.",[51,519,521],{"id":520},"is-this-token-safe-to-delete","\"Is this token safe to delete?\"",[13,523,524],{},"Tokens are grouped by category (trimmed to four rows):",[116,526,528],{"className":451,"code":527,"language":453,"meta":121,"style":121},"Category     Used            Top tokens                                     Raw values   Files\ncolors       3\u002F292 (1.03%)   brand.500 (2), gray.900 (1), transparent (1)   1            1\nspacing      2\u002F36 (5.56%)    4 (2), 6 (1)                                   1            1\nanimations   1\u002F4 (25.00%)    spin (1)                                       0            1\nfontSizes    1\u002F14 (7.14%)    xl (1)                                         0            1\n",[17,529,530,535,540,545,550],{"__ignoreMap":121},[125,531,532],{"class":127,"line":128},[125,533,534],{},"Category     Used            Top tokens                                     Raw values   Files\n",[125,536,537],{"class":127,"line":146},[125,538,539],{},"colors       3\u002F292 (1.03%)   brand.500 (2), gray.900 (1), transparent (1)   1            1\n",[125,541,542],{"class":127,"line":155},[125,543,544],{},"spacing      2\u002F36 (5.56%)    4 (2), 6 (1)                                   1            1\n",[125,546,547],{"class":127,"line":186},[125,548,549],{},"animations   1\u002F4 (25.00%)    spin (1)                                       0            1\n",[125,551,552],{"class":127,"line":226},[125,553,554],{},"fontSizes    1\u002F14 (7.14%)    xl (1)                                         0            1\n",[13,556,557,558,560,561,564,565,568,569,571,572,404,574,577,578,581,582,584],{},"If you're counting along, the app only writes ",[17,559,94],{}," and spacing ",[17,562,563],{},"4"," once each, and never writes ",[17,566,567],{},"transparent",". The other uses come from the ",[17,570,366],{}," recipe's own styles: its base uses ",[17,573,94],{},[17,575,576],{},"px: '4'",", and the ",[17,579,580],{},"ghost"," tone uses ",[17,583,567],{},". Analyze counts the bodies of recipes your app uses, once per recipe.",[13,586,587],{},"Two things to look at.",[13,589,590,593],{},[61,591,592],{},"The percentage."," 3 of 292 colors is a small app on a big preset, which is expected. In a real design system, a category at 12% used is a palette you can shrink.",[13,595,596,601,602,604],{},[61,597,598,420],{},[17,599,600],{},"gray.900"," No file in the app uses it. It's used by ",[17,603,419],{},":",[116,606,610],{"className":607,"code":608,"language":609,"meta":121,"style":121},"language-ts shiki shiki-themes github-light github-dark","globalCss: { body: { color: 'gray.900' } }\n","ts",[17,611,612],{"__ignoreMap":121},[125,613,614,616,619,622,624,627,630,633],{"class":127,"line":128},[125,615,419],{"class":138},[125,617,618],{"class":142},": { ",[125,620,621],{"class":138},"body",[125,623,618],{"class":142},[125,625,626],{"class":138},"color",[125,628,629],{"class":142},": ",[125,631,632],{"class":171},"'gray.900'",[125,634,635],{"class":142}," } }\n",[13,637,638,639,404,641,644,645,647,648,650],{},"Analyze counts your config as well as your source files. ",[17,640,419],{},[17,642,643],{},"staticCss"," count, and so do the bodies of recipes your app uses, like the ",[17,646,366],{}," recipe above. So a token that only appears in ",[17,649,419],{}," won't show up as unused, and you won't delete it by mistake.",[13,652,653],{},"Once a name shows zero uses, the next step depends on how many apps use your theme:",[655,656,657,664],"ul",{},[658,659,660,663],"li",{},[61,661,662],{},"Zero uses in every app:"," remove it.",[658,665,666,669,670,675],{},[61,667,668],{},"Still used somewhere, but you want it gone?"," ",[31,671,674],{"href":672,"rel":673},"https:\u002F\u002Fpanda-css.com\u002Fdocs\u002Fdesign-systems\u002Fdeprecations",[69],"Deprecate it"," first, so those apps get a warning before it disappears.",[51,677,679],{"id":678},"which-recipe-variants-does-anyone-use","\"Which recipe variants does anyone use?\"",[116,681,683],{"className":451,"code":682,"language":453,"meta":121,"style":121},"Recipe   Variants       Top variants                                  Used in   Used as\nbutton   3\u002F5 (60.00%)   size.md (2), tone.ghost (1), tone.solid (1)   2 files   jsx 67%, fn 33%\n",[17,684,685,690],{"__ignoreMap":121},[125,686,687],{"class":127,"line":128},[125,688,689],{},"Recipe   Variants       Top variants                                  Used in   Used as\n",[125,691,692],{"class":127,"line":146},[125,693,694],{},"button   3\u002F5 (60.00%)   size.md (2), tone.ghost (1), tone.solid (1)   2 files   jsx 67%, fn 33%\n",[13,696,697,404,699,702,703,706],{},[17,698,98],{},[17,700,701],{},"size.lg"," never show up. Every button in the app is ",[17,704,705],{},"md",". If that holds across your apps, those two variants are dead weight in the recipe.",[13,708,709,710,713,714,717],{},"\"Used as\" splits the two files: the ",[17,711,712],{},"\u003CButton>"," tags count as JSX, and the ",[17,715,716],{},"styled('button', button)"," line counts as a function call.",[51,719,721],{"id":720},"where-did-this-hard-coded-value-come-from","\"Where did this hard-coded value come from?\"",[13,723,724,725,404,727,730,731,734],{},"The paragraph has two values that aren't tokens: ",[17,726,74],{},[17,728,729],{},"16px",". That's the ",[17,732,733],{},"Raw values"," column in the tokens table. The JSON report shows what Panda matched them to:",[116,736,738],{"className":430,"code":737,"language":432,"meta":121,"style":121},"panda analyze --json | jq -c '.views.tokens.categories[].rawValues[]'\n",[17,739,740],{"__ignoreMap":121},[125,741,742,744,746,749,752,755,758],{"class":127,"line":128},[125,743,439],{"class":138},[125,745,442],{"class":171},[125,747,748],{"class":161}," --json",[125,750,751],{"class":131}," |",[125,753,754],{"class":138}," jq",[125,756,757],{"class":161}," -c",[125,759,760],{"class":171}," '.views.tokens.categories[].rawValues[]'\n",[116,762,764],{"className":451,"code":763,"language":453,"meta":121,"style":121},"{\"prop\":\"color\",\"value\":\"#6366f1\",\"uses\":1,\"files\":1,\"suggestions\":[{\"token\":\"brand.500\",\"semantic\":false,\"conditional\":false}]}\n{\"prop\":\"padding\",\"value\":\"16px\",\"uses\":1,\"files\":1,\"suggestions\":[{\"token\":\"4\",\"semantic\":false,\"conditional\":false}]}\n",[17,765,766,771],{"__ignoreMap":121},[125,767,768],{"class":127,"line":128},[125,769,770],{},"{\"prop\":\"color\",\"value\":\"#6366f1\",\"uses\":1,\"files\":1,\"suggestions\":[{\"token\":\"brand.500\",\"semantic\":false,\"conditional\":false}]}\n",[125,772,773],{"class":127,"line":146},[125,774,775],{},"{\"prop\":\"padding\",\"value\":\"16px\",\"uses\":1,\"files\":1,\"suggestions\":[{\"token\":\"4\",\"semantic\":false,\"conditional\":false}]}\n",[13,777,778,780,781,783,784,786,787,789],{},[17,779,74],{}," is exactly ",[17,782,94],{},", and ",[17,785,729],{}," is spacing ",[17,788,563],{},". Someone wrote a value the theme already had a name for. When the brand color changes, the token updates and this paragraph doesn't. That's the kind of bug that's easy to ship and annoying to find later.",[13,791,792],{},"Treat this list as a to-do list. Each entry already has the token it should be.",[13,794,795,796,799],{},"A raw value only makes the list when Panda finds a matching token. A one-off ",[17,797,798],{},"13px"," with no matching token isn't flagged.",[51,801,803],{"id":802},"where-is-this-used","\"Where is this used?\"",[13,805,806,807,810,811,813,814,816,817,820],{},"The JSON has a flat ",[17,808,809],{},"usages"," list with the file, line and column of every use in your source. Names in ",[17,812,809],{}," carry their category, so ",[17,815,94],{}," from the table is ",[17,818,819],{},"colors.brand.500"," here:",[116,822,824],{"className":430,"code":823,"language":432,"meta":121,"style":121},"panda analyze --json | jq -r '.usages[] | select(.name == \"colors.brand.500\") | \"\\(.file):\\(.line)\"'\n",[17,825,826],{"__ignoreMap":121},[125,827,828,830,832,834,836,838,841],{"class":127,"line":128},[125,829,439],{"class":138},[125,831,442],{"class":171},[125,833,748],{"class":161},[125,835,751],{"class":131},[125,837,754],{"class":138},[125,839,840],{"class":161}," -r",[125,842,843],{"class":171}," '.usages[] | select(.name == \"colors.brand.500\") | \"\\(.file):\\(.line)\"'\n",[13,845,846,847,849,850,852,853,855],{},"That's the question you ask before renaming a token, or when you want to know which screens a change will touch. Uses from your config, like ",[17,848,600],{}," in ",[17,851,419],{},", aren't in ",[17,854,809],{},". They're listed on each name's entry in the tables instead.",[51,857,859],{"id":858},"why-is-this-keyframe-still-here","\"Why is this keyframe still here?\"",[116,861,863],{"className":451,"code":862,"language":453,"meta":121,"style":121},"Keyframes   1\u002F5 used (20.00%)\nKeyframe   Uses   Used in\nspin       1      via animations.spin\nUnused (4): bounce, ping, pulse, spinSlow\n",[17,864,865,870,875,880],{"__ignoreMap":121},[125,866,867],{"class":127,"line":128},[125,868,869],{},"Keyframes   1\u002F5 used (20.00%)\n",[125,871,872],{"class":127,"line":146},[125,873,874],{},"Keyframe   Uses   Used in\n",[125,876,877],{"class":127,"line":155},[125,878,879],{},"spin       1      via animations.spin\n",[125,881,882],{"class":127,"line":186},[125,883,884],{},"Unused (4): bounce, ping, pulse, spinSlow\n",[13,886,887,888,891,892,895,896,898,899,901,902,905],{},"The app never wrote ",[17,889,890],{},"spin"," as a keyframe. It wrote ",[17,893,894],{},"animation: 'spin'",", which is the ",[17,897,890],{}," animation token, and that token points at the ",[17,900,890],{}," keyframe. So the keyframe shows ",[17,903,904],{},"via animations.spin",". Delete it and the animation token breaks, even though no file names it directly.",[13,907,908,910],{},[17,909,415],{}," is the one this app added and then stopped using. That's the one to remove.",[51,912,914],{"id":913},"give-me-the-list-for-the-pr","\"Give me the list for the PR\"",[13,916,917,920],{},[17,918,919],{},"--unused"," skips the tables and prints only the names nobody uses, one per line, with no cap:",[116,922,924],{"className":430,"code":923,"language":432,"meta":121,"style":121},"panda analyze --scope keyframes --unused\n",[17,925,926],{"__ignoreMap":121},[125,927,928,930,932,935,938],{"class":127,"line":128},[125,929,439],{"class":138},[125,931,442],{"class":171},[125,933,934],{"class":161}," --scope",[125,936,937],{"class":171}," keyframes",[125,939,940],{"class":161}," --unused\n",[116,942,944],{"className":451,"code":943,"language":453,"meta":121,"style":121},"bounce\nping\npulse\nspinSlow\n",[17,945,946,951,956,961],{"__ignoreMap":121},[125,947,948],{"class":127,"line":128},[125,949,950],{},"bounce\n",[125,952,953],{"class":127,"line":146},[125,954,955],{},"ping\n",[125,957,958],{"class":127,"line":155},[125,959,960],{},"pulse\n",[125,962,963],{"class":127,"line":186},[125,964,965],{},"spinSlow\n",[13,967,968],{},"Paste that into a cleanup PR, or save it and diff it against next month's run to see what's become unused since.",[13,970,971,972,975,976,979],{},"For anything you want to keep or script, ",[17,973,974],{},"--outfile report.json"," writes the full report and still prints the tables. ",[17,977,978],{},"--json"," prints the report instead of the tables.",[51,981,983],{"id":982},"why-is-my-style-missing","\"Why is my style missing?\"",[13,985,986],{},"Analyze reads your files the same way the build does. It finds your Panda calls and works out their values without running your code. So if analyze can't see a value, the build couldn't either, and there's no CSS for it.",[13,988,989],{},"Say a component does this:",[116,991,993],{"className":118,"code":992,"language":120,"meta":121,"style":121},"\u003Cbutton className={button({ size: 'lg', tone })} \u002F>\n",[17,994,995],{"__ignoreMap":121},[125,996,997,1000,1002,1004,1006,1008,1010,1013,1016],{"class":127,"line":128},[125,998,999],{"class":142},"\u003C",[125,1001,366],{"class":192},[125,1003,196],{"class":138},[125,1005,168],{"class":131},[125,1007,201],{"class":142},[125,1009,366],{"class":138},[125,1011,1012],{"class":142},"({ size: ",[125,1014,1015],{"class":171},"'lg'",[125,1017,1018],{"class":142},", tone })} \u002F>\n",[13,1020,1021,1023,1024,1027,1028,1032],{},[17,1022,701],{}," counts. ",[17,1025,1026],{},"tone"," comes from a prop, so Panda can't know which tone it is, and nothing counts for it. If one of the tones has no CSS in your stylesheet, this is why. ",[31,1029,1031],{"href":1030},"\u002Fblog\u002Fpanda-never-runs-your-code","Panda never runs your code"," covers what Panda can and can't follow, and how to write values it can see.",[13,1034,1035,1036,1038],{},"A quick check: if a name you know you wrote is missing from ",[17,1037,809],{},", Panda didn't see it.",[51,1040,1042],{"id":1041},"seeing-it-all-at-once","Seeing it all at once",[13,1044,1045,1046,1051],{},"For a visual version, open the Usage page in ",[31,1047,1050],{"href":1048,"rel":1049},"https:\u002F\u002Fpanda-css.com\u002Fdocs\u002Ftheming\u002Fstudio",[69],"Panda Studio",". There are two ways in:",[655,1053,1054,1062],{},[658,1055,1056,1061],{},[61,1057,1058],{},[17,1059,1060],{},"panda studio"," runs a local server that rescans as you save.",[658,1063,1064,1067,1068,1071,1072,1075],{},[61,1065,1066],{},"Studio's Usage page"," can open a ",[17,1069,1070],{},"report.json"," you made with ",[17,1073,1074],{},"--outfile",". Same data, no server.",[13,1077,1078,1079,1081],{},"Here's the example app in ",[17,1080,1060],{},". Every square is a token, and the lit ones are the ones your files use:",[13,1083,1084],{},[1085,1086],"img",{"alt":1087,"src":1088},"Panda Studio's Usage page for the example app. A project scan with 5 of 515 tokens used, 1% coverage, 0 unknown and 2 hard-coded values, above a grid of every token category with the used tokens lit up.","\u002Fimages\u002Fblog\u002Fknow-what-your-css-actually-uses\u002Fstudio-scan.webp",[13,1090,1091],{},"Further down are the most used tokens, with a swatch for colors, and the hard-coded values, each next to the token it should be:",[13,1093,1094],{},[1085,1095],{"alt":1096,"src":1097},"Studio's most used tokens list, with spacing 4 and 6, the spin animation, brand.500 with its indigo swatch and fontSize xl, next to an Issues list showing #6366f1 suggested as brand.500 and 16px suggested as spacing 4.","\u002Fimages\u002Fblog\u002Fknow-what-your-css-actually-uses\u002Fstudio-issues.webp",[13,1099,1100,1101,1103,1104,1106,1107,1109,1110,1113],{},"Either way, Studio shows only the uses in your source files. The report has the config uses too, but Studio doesn't draw them, so its numbers are smaller than the terminal's. ",[17,1102,600],{}," from ",[17,1105,419],{}," and the ",[17,1108,366],{}," recipe's own styles don't light up here. It also flags token names that don't exist in your theme, like a typo such as ",[17,1111,1112],{},"bleu.500",", as Unknown.",[51,1115,1117],{"id":1116},"let-your-agent-run-it","Let your agent run it",[13,1119,1120],{},"Cleanup is a good task to hand off, and an agent doesn't need anything special to do it. It can run the same command you would:",[116,1122,1124],{"className":430,"code":1123,"language":432,"meta":121,"style":121},"panda analyze --json\n",[17,1125,1126],{"__ignoreMap":121},[125,1127,1128,1130,1132],{"class":127,"line":128},[125,1129,439],{"class":138},[125,1131,442],{"class":171},[125,1133,1134],{"class":161}," --json\n",[13,1136,1137],{},"The JSON has the counts, the unused names, the raw values with their suggested tokens, and the file and line of every use. That's enough for an agent to replace each hard-coded value with its token, or to check that a token is unused before deleting it. It gets the same answer you'd get, so it doesn't have to guess.",[13,1139,1140,1141,1144,1145,1148],{},"If you already use Panda's ",[31,1142,90],{"href":88,"rel":1143},[69],", the same analysis is there as a tool, ",[17,1146,1147],{},"get_usage_report",". Ask \"Which tokens aren't used in this project?\" and the agent calls it instead of the CLI. Either way works.",[51,1150,1152],{"id":1151},"make-it-a-habit","Make it a habit",[13,1154,1155,1157],{},[17,1156,19],{}," tells you what's already there. Lint stops the mess from coming back.",[13,1159,1160,669,1163,1170],{},[61,1161,1162],{},"Lint while you write.",[31,1164,1167],{"href":1165,"rel":1166},"https:\u002F\u002Fpanda-css.com\u002Fdocs\u002Fget-started\u002Flinting",[69],[17,1168,1169],{},"@pandacss\u002Feslint-plugin"," runs on Panda's own compiler, so it knows your actual theme. It works with ESLint and Oxlint. This site runs it through Oxlint:",[116,1172,1176],{"className":1173,"code":1174,"language":1175,"meta":121,"style":121},"language-json shiki shiki-themes github-light github-dark","{\n  \"jsPlugins\": [\"@pandacss\u002Feslint-plugin\u002Foxlint\"],\n  \"rules\": {\n    \"@pandacss\u002Fno-invalid-token-paths\": \"error\",\n    \"@pandacss\u002Fprefer-token\": [\"warn\", { \"categories\": [\"colors\"] }],\n    \"@pandacss\u002Fno-deprecated\": \"warn\"\n  }\n}\n","json",[17,1177,1178,1183,1197,1205,1218,1242,1252,1257],{"__ignoreMap":121},[125,1179,1180],{"class":127,"line":128},[125,1181,1182],{"class":142},"{\n",[125,1184,1185,1188,1191,1194],{"class":127,"line":146},[125,1186,1187],{"class":161},"  \"jsPlugins\"",[125,1189,1190],{"class":142},": [",[125,1192,1193],{"class":171},"\"@pandacss\u002Feslint-plugin\u002Foxlint\"",[125,1195,1196],{"class":142},"],\n",[125,1198,1199,1202],{"class":127,"line":155},[125,1200,1201],{"class":161},"  \"rules\"",[125,1203,1204],{"class":142},": {\n",[125,1206,1207,1210,1212,1215],{"class":127,"line":186},[125,1208,1209],{"class":161},"    \"@pandacss\u002Fno-invalid-token-paths\"",[125,1211,629],{"class":142},[125,1213,1214],{"class":171},"\"error\"",[125,1216,1217],{"class":142},",\n",[125,1219,1220,1223,1225,1228,1231,1234,1236,1239],{"class":127,"line":226},[125,1221,1222],{"class":161},"    \"@pandacss\u002Fprefer-token\"",[125,1224,1190],{"class":142},[125,1226,1227],{"class":171},"\"warn\"",[125,1229,1230],{"class":142},", { ",[125,1232,1233],{"class":161},"\"categories\"",[125,1235,1190],{"class":142},[125,1237,1238],{"class":171},"\"colors\"",[125,1240,1241],{"class":142},"] }],\n",[125,1243,1244,1247,1249],{"class":127,"line":259},[125,1245,1246],{"class":161},"    \"@pandacss\u002Fno-deprecated\"",[125,1248,629],{"class":142},[125,1250,1251],{"class":171},"\"warn\"\n",[125,1253,1254],{"class":127,"line":290},[125,1255,1256],{"class":142},"  }\n",[125,1258,1259],{"class":127,"line":317},[125,1260,360],{"class":142},[13,1262,1263],{},"Each of those rules matches something in this guide:",[655,1265,1266,1277,1297],{},[658,1267,1268,1273,1274,1276],{},[61,1269,1270],{},[17,1271,1272],{},"prefer-token"," flags a raw value when your theme has a token for it. It's the raw values list, caught while you type, so ",[17,1275,74],{}," never lands.",[658,1278,1279,1284,1285,1288,1289,1292,1293,1296],{},[61,1280,1281],{},[17,1282,1283],{},"no-invalid-token-paths"," flags token references that don't exist, like ",[17,1286,1287],{},"{colors.ghost}"," in a border or ",[17,1290,1291],{},"token(sizes.ghost)"," in a ",[17,1294,1295],{},"calc()",". The compiler doesn't error on those either.",[658,1298,1299,1304],{},[61,1300,1301],{},[17,1302,1303],{},"no-deprecated"," warns on deprecated tokens, recipes, utilities and patterns. Pair it with deprecating before you delete, and every app sees the warning before the token goes.",[13,1306,1307],{},"Lint runs for agents too. An agent that runs your lint before it finishes gets the same warnings you do.",[51,1309,1311],{"id":1310},"things-to-keep-in-mind","Things to keep in mind",[13,1313,1314],{},"\"Unused\" means unused in what analyze scanned. That's narrower than \"nobody uses it\":",[655,1316,1317,1323,1329,1339],{},[658,1318,1319,1322],{},[61,1320,1321],{},"Run it in the app, not the design-system package."," The app is where the tokens get used.",[658,1324,1325,1328],{},[61,1326,1327],{},"Run it in every app."," If three apps share one design system, each one sees only its own code.",[658,1330,1331,1338],{},[61,1332,1333,1334,1337],{},"Only files in your config's ",[17,1335,1336],{},"include"," are scanned."," Code outside it doesn't count.",[658,1340,1341,1344],{},[61,1342,1343],{},"Values Panda can't see don't count."," A variant passed through a variable counts as nothing.",[51,1346,1348],{"id":1347},"the-short-version","The short version",[655,1350,1351,1360,1366,1375,1383,1392,1405],{},[658,1352,1353,1356,1357,1359],{},[61,1354,1355],{},"Before deleting a token, variant or keyframe:"," run ",[17,1358,19],{}," in every app that uses the theme.",[658,1361,1362,1365],{},[61,1363,1364],{},"To find hard-coded values:"," check the raw values. Each one comes with the token it should be.",[658,1367,1368,1371,1372,1374],{},[61,1369,1370],{},"To find where something is used:"," filter ",[17,1373,809],{}," in the JSON.",[658,1376,1377,1380,1381,420],{},[61,1378,1379],{},"For a cleanup PR:"," use ",[17,1382,919],{},[658,1384,1385,1388,1389,1391],{},[61,1386,1387],{},"When a style is missing:"," if a name you know you wrote is missing from ",[17,1390,809],{},", Panda didn't see it, and the build didn't either.",[658,1393,1394,1397,1398,1401,1402,1404],{},[61,1395,1396],{},"To hand cleanup to an agent:"," have it run ",[17,1399,1400],{},"panda analyze --json",", or use ",[17,1403,1147],{}," if you have the Panda MCP server.",[658,1406,1407,1410],{},[61,1408,1409],{},"To keep it clean:"," turn on the Panda lint rules, and grab the kit below.",[13,1412,1413,1414,420],{},"Full reference in the ",[31,1415,1418],{"href":1416,"rel":1417},"https:\u002F\u002Fpanda-css.com\u002Fdocs\u002Fdesign-systems\u002Fanalyze",[69],"Panda docs",[1420,1421,1423,1440,1446,1465,1477,1529,1534,1549,1564,1579,1587,1686],"takeaway",{"title":1422},"Take this with you",[13,1424,1425,669,1428,1432,1433,1436,1437,1439],{},[61,1426,1427],{},"1. A cleanup skill for your agent.",[31,1429,1431],{"href":1430},"\u002Fresources\u002Fpanda-usage-cleanup\u002FSKILL.md","Download SKILL.md"," and save it as ",[17,1434,1435],{},".claude\u002Fskills\u002Fpanda-usage-cleanup\u002FSKILL.md",", or wherever your agent loads skills from. It makes the agent run ",[17,1438,1400],{}," before it removes or renames anything, replace raw values with their suggested tokens, deprecate instead of delete when a theme is shared, and stop to ask about values it can't see.",[13,1441,1442,1445],{},[61,1443,1444],{},"2. Panda's official skills."," They teach agents to write Panda that Panda can read, so the style reaches the stylesheet in the first place:",[116,1447,1449],{"className":430,"code":1448,"language":432,"meta":121,"style":121},"npx skills add chakra-ui\u002Fpanda\n",[17,1450,1451],{"__ignoreMap":121},[125,1452,1453,1456,1459,1462],{"class":127,"line":128},[125,1454,1455],{"class":138},"npx",[125,1457,1458],{"class":171}," skills",[125,1460,1461],{"class":171}," add",[125,1463,1464],{"class":171}," chakra-ui\u002Fpanda\n",[13,1466,1467,1476],{},[61,1468,1469,1470,1472,1473,420],{},"3. Rules for your ",[17,1471,38],{}," or ",[17,1474,1475],{},"CLAUDE.md"," Agents read these every session:",[116,1478,1481],{"className":1479,"code":1480,"language":705,"meta":121,"style":121},"language-md shiki shiki-themes github-light github-dark","## Styling (Panda CSS)\n\n- Use theme tokens. Never hard-code a color, spacing or font size the theme already has.\n- Before removing or renaming a token, recipe variant, keyframe or pattern, run\n  `npx panda analyze --json` and check `usages` for it. Names there carry their\n  category, like `colors.brand.500`.\n- After styling changes, run `npx panda analyze --scope tokens` and make sure\n  no new raw values showed up.\n- Run lint before you finish.\n",[17,1482,1483,1488,1494,1499,1504,1509,1514,1519,1524],{"__ignoreMap":121},[125,1484,1485],{"class":127,"line":128},[125,1486,1487],{},"## Styling (Panda CSS)\n",[125,1489,1490],{"class":127,"line":146},[125,1491,1493],{"emptyLinePlaceholder":1492},true,"\n",[125,1495,1496],{"class":127,"line":155},[125,1497,1498],{},"- Use theme tokens. Never hard-code a color, spacing or font size the theme already has.\n",[125,1500,1501],{"class":127,"line":186},[125,1502,1503],{},"- Before removing or renaming a token, recipe variant, keyframe or pattern, run\n",[125,1505,1506],{"class":127,"line":226},[125,1507,1508],{},"  `npx panda analyze --json` and check `usages` for it. Names there carry their\n",[125,1510,1511],{"class":127,"line":259},[125,1512,1513],{},"  category, like `colors.brand.500`.\n",[125,1515,1516],{"class":127,"line":290},[125,1517,1518],{},"- After styling changes, run `npx panda analyze --scope tokens` and make sure\n",[125,1520,1521],{"class":127,"line":317},[125,1522,1523],{},"  no new raw values showed up.\n",[125,1525,1526],{"class":127,"line":341},[125,1527,1528],{},"- Run lint before you finish.\n",[13,1530,1531],{},[61,1532,1533],{},"4. Prompts to try.",[116,1535,1537],{"className":451,"code":1536,"language":453,"meta":121,"style":121},"Run `npx panda analyze --json`. For every raw value that has a suggested token,\nreplace it with that token in the file it's used in. List the ones with no suggestion.\n",[17,1538,1539,1544],{"__ignoreMap":121},[125,1540,1541],{"class":127,"line":128},[125,1542,1543],{},"Run `npx panda analyze --json`. For every raw value that has a suggested token,\n",[125,1545,1546],{"class":127,"line":146},[125,1547,1548],{},"replace it with that token in the file it's used in. List the ones with no suggestion.\n",[116,1550,1552],{"className":451,"code":1551,"language":453,"meta":121,"style":121},"Run `npx panda analyze --scope keyframes --unused` and remove the keyframes nobody uses,\none commit each. Rerun analyze after each removal and stop if anything new shows up.\n",[17,1553,1554,1559],{"__ignoreMap":121},[125,1555,1556],{"class":127,"line":128},[125,1557,1558],{},"Run `npx panda analyze --scope keyframes --unused` and remove the keyframes nobody uses,\n",[125,1560,1561],{"class":127,"line":146},[125,1562,1563],{},"one commit each. Rerun analyze after each removal and stop if anything new shows up.\n",[116,1565,1567],{"className":451,"code":1566,"language":453,"meta":121,"style":121},"Is `colors.brand.300` still used anywhere? Check `panda analyze --json` usages and the\nconfig uses before answering.\n",[17,1568,1569,1574],{"__ignoreMap":121},[125,1570,1571],{"class":127,"line":128},[125,1572,1573],{},"Is `colors.brand.300` still used anywhere? Check `panda analyze --json` usages and the\n",[125,1575,1576],{"class":127,"line":146},[125,1577,1578],{},"config uses before answering.\n",[13,1580,1581,669,1584,1586],{},[61,1582,1583],{},"5. A CI check for new hard-coded values.",[17,1585,919],{}," never fails a build, so make your own gate. This fails when the raw values with a matching token go above a number you pick:",[116,1588,1590],{"className":430,"code":1589,"language":432,"meta":121,"style":121},"max=0\ncount=$(npx panda analyze --json | jq '[.views.tokens.categories[].rawValues[].uses] | add \u002F\u002F 0')\n[ \"$count\" -le \"$max\" ] || { echo \"$count hard-coded values have a token\"; exit 1; }\n",[17,1591,1592,1602,1631],{"__ignoreMap":121},[125,1593,1594,1597,1599],{"class":127,"line":128},[125,1595,1596],{"class":142},"max",[125,1598,168],{"class":131},[125,1600,1601],{"class":171},"0\n",[125,1603,1604,1607,1609,1612,1614,1617,1619,1621,1623,1625,1628],{"class":127,"line":146},[125,1605,1606],{"class":142},"count",[125,1608,168],{"class":131},[125,1610,1611],{"class":142},"$(",[125,1613,1455],{"class":138},[125,1615,1616],{"class":171}," panda",[125,1618,442],{"class":171},[125,1620,748],{"class":161},[125,1622,751],{"class":131},[125,1624,754],{"class":138},[125,1626,1627],{"class":171}," '[.views.tokens.categories[].rawValues[].uses] | add \u002F\u002F 0'",[125,1629,1630],{"class":142},")\n",[125,1632,1633,1636,1639,1642,1644,1647,1650,1653,1655,1658,1661,1664,1667,1669,1671,1674,1677,1680,1683],{"class":127,"line":155},[125,1634,1635],{"class":142},"[ ",[125,1637,1638],{"class":171},"\"",[125,1640,1641],{"class":142},"$count",[125,1643,1638],{"class":171},[125,1645,1646],{"class":131}," -le",[125,1648,1649],{"class":171}," \"",[125,1651,1652],{"class":142},"$max",[125,1654,1638],{"class":171},[125,1656,1657],{"class":142}," ] ",[125,1659,1660],{"class":131},"||",[125,1662,1663],{"class":142}," { ",[125,1665,1666],{"class":161},"echo",[125,1668,1649],{"class":171},[125,1670,1641],{"class":142},[125,1672,1673],{"class":171}," hard-coded values have a token\"",[125,1675,1676],{"class":142},"; ",[125,1678,1679],{"class":161},"exit",[125,1681,1682],{"class":161}," 1",[125,1684,1685],{"class":142},"; }\n",[13,1687,1688,1689,1691],{},"Start ",[17,1690,1596],{}," at today's count, and lower it as you clean up.",[13,1693,1694],{},[27,1695,1696,1697,420],{},"Back to the internals next: part 7, cold vs warm, and how Panda's watch mode got fast. If you want to know why some values never show up in analyze, read ",[31,1698,1031],{"href":1030},[1700,1701,1702],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":121,"searchDepth":146,"depth":146,"links":1704},[1705,1706,1707,1708,1709,1710,1711,1712,1713,1714,1715,1716,1717,1718,1719],{"id":53,"depth":146,"text":54},{"id":110,"depth":146,"text":111},{"id":423,"depth":146,"text":424},{"id":520,"depth":146,"text":521},{"id":678,"depth":146,"text":679},{"id":720,"depth":146,"text":721},{"id":802,"depth":146,"text":803},{"id":858,"depth":146,"text":859},{"id":913,"depth":146,"text":914},{"id":982,"depth":146,"text":983},{"id":1041,"depth":146,"text":1042},{"id":1116,"depth":146,"text":1117},{"id":1151,"depth":146,"text":1152},{"id":1310,"depth":146,"text":1311},{"id":1347,"depth":146,"text":1348},null,"2026-11-12","A practical guide to panda analyze. Find the tokens, recipe variants and keyframes nobody uses, track down hard-coded values, and why a clean theme matters more now that agents write your styles.",false,{},6.5,"\u002Fblog\u002Fknow-what-your-css-actually-uses","13min",{"title":5,"description":1722},"Know what your CSS actually uses: a guide to panda analyze in Panda CSS","Panda internals","blog\u002Fknow-what-your-css-actually-uses","4Kw3OiduU9dLO7LVOan2xg17FMlSYaSy0lKTMXWDyYw",{"series":1730,"part":1725,"total":259},1794505029512]