[{"data":1,"prerenderedAt":1729},["ShallowReactive",2],{"blog-\u002Fblog\u002Fhow-panda-makes-cva-fast":3,"cover-\u002Fblog\u002Fhow-panda-makes-cva-fast":1728},{"id":4,"title":5,"body":6,"canonical":1715,"date":1716,"description":1717,"draft":1718,"duration":1715,"extension":1719,"lang":1715,"meta":1720,"navigation":77,"part":122,"path":1721,"place":1715,"placeLink":1715,"readingTime":1722,"seo":1723,"seoTitle":1724,"series":1725,"stem":1726,"updated":1715,"__hash__":1727},"blog\u002Fblog\u002Fhow-panda-makes-cva-fast.md","How Panda makes cva fast",{"type":7,"value":8,"toc":1702},"minimark",[9,21,24,36,43,193,196,226,237,247,252,269,301,313,317,320,590,593,648,654,672,676,685,688,719,733,740,743,751,761,773,793,797,800,934,937,1110,1125,1131,1135,1138,1217,1220,1324,1349,1355,1365,1369,1396,1403,1407,1410,1442,1448,1455,1518,1524,1588,1597,1601,1658,1662,1665,1698],[10,11,12],"subtitle",{},[13,14,15,16,20],"p",{},"What a ",[17,18,19],"code",{},"cva"," recipe turns into before it reaches the browser.",[22,23],"post-cover",{},[13,25,26],{},[27,28,29,30,35],"em",{},"Panda internals, part 6. Start with ",[31,32,34],"a",{"href":33},"\u002Fblog\u002Fbuild-once-at-build-time","Build once, at build time"," if you're new here.",[13,37,38,42],{},[31,39,41],{"href":40},"\u002Fblog\u002Fthe-ir-behind-your-ternary","Part 5"," ended on a promise: how Panda makes variants fast. Here's the recipe we'll follow:",[44,45,50],"pre",{"className":46,"code":47,"language":48,"meta":49,"style":49},"language-ts shiki shiki-themes github-light github-dark","import { cva } from 'styled-system\u002Fcss'\n\nexport const button = cva({\n  base: { color: 'white' },\n  variants: {\n    size: { sm: { fontSize: '12px' } },\n    intent: { danger: { backgroundColor: 'red' } },\n  },\n  compoundVariants: [{ size: 'sm', intent: 'danger', css: { color: 'black' } }],\n  defaultVariants: { size: 'sm', intent: 'danger' },\n})\n","ts","",[17,51,52,72,79,102,114,120,132,143,149,173,187],{"__ignoreMap":49},[53,54,57,61,65,68],"span",{"class":55,"line":56},"line",1,[53,58,60],{"class":59},"szBVR","import",[53,62,64],{"class":63},"sVt8B"," { cva } ",[53,66,67],{"class":59},"from",[53,69,71],{"class":70},"sZZnC"," 'styled-system\u002Fcss'\n",[53,73,75],{"class":55,"line":74},2,[53,76,78],{"emptyLinePlaceholder":77},true,"\n",[53,80,82,85,88,92,95,99],{"class":55,"line":81},3,[53,83,84],{"class":59},"export",[53,86,87],{"class":59}," const",[53,89,91],{"class":90},"sj4cs"," button",[53,93,94],{"class":59}," =",[53,96,98],{"class":97},"sScJk"," cva",[53,100,101],{"class":63},"({\n",[53,103,105,108,111],{"class":55,"line":104},4,[53,106,107],{"class":63},"  base: { color: ",[53,109,110],{"class":70},"'white'",[53,112,113],{"class":63}," },\n",[53,115,117],{"class":55,"line":116},5,[53,118,119],{"class":63},"  variants: {\n",[53,121,123,126,129],{"class":55,"line":122},6,[53,124,125],{"class":63},"    size: { sm: { fontSize: ",[53,127,128],{"class":70},"'12px'",[53,130,131],{"class":63}," } },\n",[53,133,135,138,141],{"class":55,"line":134},7,[53,136,137],{"class":63},"    intent: { danger: { backgroundColor: ",[53,139,140],{"class":70},"'red'",[53,142,131],{"class":63},[53,144,146],{"class":55,"line":145},8,[53,147,148],{"class":63},"  },\n",[53,150,152,155,158,161,164,167,170],{"class":55,"line":151},9,[53,153,154],{"class":63},"  compoundVariants: [{ size: ",[53,156,157],{"class":70},"'sm'",[53,159,160],{"class":63},", intent: ",[53,162,163],{"class":70},"'danger'",[53,165,166],{"class":63},", css: { color: ",[53,168,169],{"class":70},"'black'",[53,171,172],{"class":63}," } }],\n",[53,174,176,179,181,183,185],{"class":55,"line":175},10,[53,177,178],{"class":63},"  defaultVariants: { size: ",[53,180,157],{"class":70},[53,182,160],{"class":63},[53,184,163],{"class":70},[53,186,113],{"class":63},[53,188,190],{"class":55,"line":189},11,[53,191,192],{"class":63},"})\n",[13,194,195],{},"And a component calls it on every render:",[44,197,201],{"className":198,"code":199,"language":200,"meta":49,"style":49},"language-tsx shiki shiki-themes github-light github-dark","\u003Cbutton className={button({ size, intent })} \u002F>\n","tsx",[17,202,203],{"__ignoreMap":49},[53,204,205,208,212,215,218,221,223],{"class":55,"line":56},[53,206,207],{"class":63},"\u003C",[53,209,211],{"class":210},"s9eBZ","button",[53,213,214],{"class":97}," className",[53,216,217],{"class":59},"=",[53,219,220],{"class":63},"{",[53,222,211],{"class":97},[53,224,225],{"class":63},"({ size, intent })} \u002F>\n",[13,227,228,229,232,233,236],{},"The CSS for every variant is already in your stylesheet. That part happens at build time, like everything in ",[31,230,231],{"href":33},"part 1",". The question is how much work that ",[17,234,235],{},"button()"," call does in the browser.",[13,238,239],{},[27,240,241,242,246],{},"Just want the habits? Skip to ",[31,243,245],{"href":244},"#what-this-means-for-your-code","what this means for your code",".",[248,249,251],"h2",{"id":250},"what-cva-does-at-runtime","What cva does at runtime",[13,253,254,255,261,262,264,265,268],{},"Without ",[31,256,260],{"href":257,"rel":258},"https:\u002F\u002Fpanda-css.com\u002Fdocs\u002Fstyling\u002Fsource-transforms",[259],"nofollow","source transforms",", ",[17,263,19],{}," is a function in your generated ",[17,266,267],{},"styled-system",". Every call does the same steps:",[270,271,272,279,285,288,291,294],"ol",{},[273,274,275,276,246],"li",{},"Merge your props over ",[17,277,278],{},"defaultVariants",[273,280,281,282,246],{},"Start a list with ",[17,283,284],{},"base",[273,286,287],{},"For each variant, look up the style object for the selected value and push it.",[273,289,290],{},"Check every compound variant against the selection and merge the ones that match.",[273,292,293],{},"Deep-merge all of those style objects into one.",[273,295,296,297,300],{},"Call ",[17,298,299],{},"css()"," on the result to get class names.",[13,302,303,304,307,308,312],{},"All of that is wrapped in the same ",[17,305,306],{},"memo()"," from ",[31,309,311],{"href":310},"\u002Fblog\u002Flook-it-up-once","part 3",", so a repeated call is a cache hit. But a new combination goes through every step, and steps 5 and 6 work on style objects, not strings. Panda merges objects in the browser so it can turn them into class names it already knew at build time.",[248,314,316],{"id":315},"what-the-transform-writes-instead","What the transform writes instead",[13,318,319],{},"With source transforms on, Panda compiles the recipe. Every variant's classes are known at build time, so the merging can happen at build time too. The recipe above ships as this (formatted, with the config argument trimmed):",[44,321,325],{"className":322,"code":323,"language":324,"meta":49,"style":49},"language-js shiki shiki-themes github-light github-dark","import { cx as __pcx, attachRecipe as __pr } from '@pandacss-internal\u002Fcss'\n\nexport const button = __pr((p = {}) => {\n  p ??= {}\n  const _p0 = p[\"size\"], v0 = _p0 === void 0 ? 'sm' : _p0\n  const _p1 = p[\"intent\"], v1 = _p1 === void 0 ? 'danger' : _p1\n  return __pcx(\n    'color_white',\n    { sm: \"fs_12px\" }[v0],\n    { danger: \"background-color_red\" }[v1],\n    v0 === 'sm' && v1 === 'danger' && \"color_black\",\n  )\n}, \u002F* config *\u002F, [\"size\", \"intent\"], { size: [\"sm\"], intent: [\"danger\"] })\n","js",[17,326,327,350,354,384,395,443,483,494,502,513,524,550,556],{"__ignoreMap":49},[53,328,329,331,334,337,340,342,345,347],{"class":55,"line":56},[53,330,60],{"class":59},[53,332,333],{"class":63}," { cx ",[53,335,336],{"class":59},"as",[53,338,339],{"class":63}," __pcx, attachRecipe ",[53,341,336],{"class":59},[53,343,344],{"class":63}," __pr } ",[53,346,67],{"class":59},[53,348,349],{"class":70}," '@pandacss-internal\u002Fcss'\n",[53,351,352],{"class":55,"line":74},[53,353,78],{"emptyLinePlaceholder":77},[53,355,356,358,360,362,364,367,370,373,375,378,381],{"class":55,"line":81},[53,357,84],{"class":59},[53,359,87],{"class":59},[53,361,91],{"class":90},[53,363,94],{"class":59},[53,365,366],{"class":97}," __pr",[53,368,369],{"class":63},"((",[53,371,13],{"class":372},"s4XuR",[53,374,94],{"class":59},[53,376,377],{"class":63}," {}) ",[53,379,380],{"class":59},"=>",[53,382,383],{"class":63}," {\n",[53,385,386,389,392],{"class":55,"line":104},[53,387,388],{"class":63},"  p ",[53,390,391],{"class":59},"??=",[53,393,394],{"class":63}," {}\n",[53,396,397,400,403,405,408,411,414,417,419,422,425,428,431,434,437,440],{"class":55,"line":116},[53,398,399],{"class":59},"  const",[53,401,402],{"class":90}," _p0",[53,404,94],{"class":59},[53,406,407],{"class":63}," p[",[53,409,410],{"class":70},"\"size\"",[53,412,413],{"class":63},"], ",[53,415,416],{"class":90},"v0",[53,418,94],{"class":59},[53,420,421],{"class":63}," _p0 ",[53,423,424],{"class":59},"===",[53,426,427],{"class":59}," void",[53,429,430],{"class":90}," 0",[53,432,433],{"class":59}," ?",[53,435,436],{"class":70}," 'sm'",[53,438,439],{"class":59}," :",[53,441,442],{"class":63}," _p0\n",[53,444,445,447,450,452,454,457,459,462,464,467,469,471,473,475,478,480],{"class":55,"line":122},[53,446,399],{"class":59},[53,448,449],{"class":90}," _p1",[53,451,94],{"class":59},[53,453,407],{"class":63},[53,455,456],{"class":70},"\"intent\"",[53,458,413],{"class":63},[53,460,461],{"class":90},"v1",[53,463,94],{"class":59},[53,465,466],{"class":63}," _p1 ",[53,468,424],{"class":59},[53,470,427],{"class":59},[53,472,430],{"class":90},[53,474,433],{"class":59},[53,476,477],{"class":70}," 'danger'",[53,479,439],{"class":59},[53,481,482],{"class":63}," _p1\n",[53,484,485,488,491],{"class":55,"line":134},[53,486,487],{"class":59},"  return",[53,489,490],{"class":97}," __pcx",[53,492,493],{"class":63},"(\n",[53,495,496,499],{"class":55,"line":145},[53,497,498],{"class":70},"    'color_white'",[53,500,501],{"class":63},",\n",[53,503,504,507,510],{"class":55,"line":151},[53,505,506],{"class":63},"    { sm: ",[53,508,509],{"class":70},"\"fs_12px\"",[53,511,512],{"class":63}," }[v0],\n",[53,514,515,518,521],{"class":55,"line":175},[53,516,517],{"class":63},"    { danger: ",[53,519,520],{"class":70},"\"background-color_red\"",[53,522,523],{"class":63}," }[v1],\n",[53,525,526,529,531,533,536,539,541,543,545,548],{"class":55,"line":189},[53,527,528],{"class":63},"    v0 ",[53,530,424],{"class":59},[53,532,436],{"class":70},[53,534,535],{"class":59}," &&",[53,537,538],{"class":63}," v1 ",[53,540,424],{"class":59},[53,542,477],{"class":70},[53,544,535],{"class":59},[53,546,547],{"class":70}," \"color_black\"",[53,549,501],{"class":63},[53,551,553],{"class":55,"line":552},12,[53,554,555],{"class":63},"  )\n",[53,557,559,562,566,569,571,573,575,578,581,584,587],{"class":55,"line":558},13,[53,560,561],{"class":63},"}, ",[53,563,565],{"class":564},"sJ8bj","\u002F* config *\u002F",[53,567,568],{"class":63},", [",[53,570,410],{"class":70},[53,572,261],{"class":63},[53,574,456],{"class":70},[53,576,577],{"class":63},"], { size: [",[53,579,580],{"class":70},"\"sm\"",[53,582,583],{"class":63},"], intent: [",[53,585,586],{"class":70},"\"danger\"",[53,588,589],{"class":63},"] })\n",[13,591,592],{},"Line by line:",[594,595,596,614,620,637],"ul",{},[273,597,598,602,603,606,607,609,610,613],{},[599,600,601],"strong",{},"Defaults"," become a check for ",[17,604,605],{},"undefined",". Only ",[17,608,605],{}," takes the default, so passing ",[17,611,612],{},"null"," doesn't.",[273,615,616,619],{},[599,617,618],{},"Each variant"," becomes a small object that maps an option to its class string. Picking a variant is one property lookup. An option with no classes is left out of the object.",[273,621,622,625,626,629,630,633,634,246],{},[599,623,624],{},"Each compound variant"," becomes a plain ",[17,627,628],{},"&&"," check. An array condition like ",[17,631,632],{},"size: ['sm', 'md']"," becomes ",[17,635,636],{},"v0 === 'sm' || v0 === 'md'",[273,638,639,644,645,647],{},[599,640,641],{},[17,642,643],{},"__pcx"," joins the pieces. When two pieces set the same property under the same conditions, the later one wins. Compounds come last, so a compound beats a variant, the same order the runtime ",[17,646,19],{}," merges them in.",[13,649,650,651,653],{},"No style objects and no ",[17,652,299],{}," call. The function only picks strings.",[13,655,656,657,660,661,664,665,668,669,246],{},"Boolean variants work the same way. A ",[17,658,659],{},"disabled: { true: {...} }"," variant becomes ",[17,662,663],{},"{ true: \"opacity_0.5\" }[v1]",", because the property lookup turns ",[17,666,667],{},"true"," into the key ",[17,670,671],{},"\"true\"",[248,673,675],{"id":674},"remembering-the-answer","Remembering the answer",[13,677,678,679,681,682,684],{},"The compiled function is already cheap, but Panda still caches it, because a component re-renders with the same props far more often than with new ones. It doesn't reuse the general ",[17,680,306],{}," from part 3, though. A recipe knows something ",[17,683,299],{}," doesn't: the full list of values each prop can take.",[13,686,687],{},"So Panda gives each prop a small number:",[594,689,690,698,707],{},[273,691,692,695,696],{},[17,693,694],{},"0"," for ",[17,697,605],{},[273,699,700,261,703,706],{},[17,701,702],{},"1",[17,704,705],{},"2",", ... for each declared option",[273,708,709,710,261,712,715,716,718],{},"up to three more for ",[17,711,667],{},[17,713,714],{},"false"," and ",[17,717,612],{},", when they aren't already options",[13,720,721,722,715,725,728,729,732],{},"Then it combines those numbers into one index, the same way digits combine into a number. For a recipe with ",[17,723,724],{},"size: sm | md",[17,726,727],{},"visual: solid | outline",", each prop has 6 possible states, so there are 6 × 6 = 36 combinations. ",[17,730,731],{},"{ size: 'md', visual: 'solid' }"," lands in one exact slot of a 36-slot array. No string keys, no hashing. A cache hit is a few multiplications and an array read.",[13,734,735,736,739],{},"That only works while the table stays small. Every new prop multiplies the size, so the table is capped at 256 slots. Past that, Panda switches to a short list of the last 16 prop combinations, compared value by value. That's a different 16 from the cap in ",[31,737,738],{"href":40},"part 5",". That one limits how many class strings Panda inlines at a call site. This one is a cache that lives in the browser.",[13,741,742],{},"Some calls skip the cache entirely:",[594,744,745,748],{},[273,746,747],{},"In the table, a string that isn't a declared option, like a typo, is resolved without caching. It has no slot to go in.",[273,749,750],{},"Objects and functions are never cached.",[248,752,754,755,715,757,760],{"id":753},"why-true-and-true-get-two-slots","Why ",[17,756,667],{},[17,758,759],{},"'true'"," get two slots",[13,762,763,764,715,766,768,769,772],{},"When a recipe has no compound variants, ",[17,765,667],{},[17,767,759],{}," share a slot. The lookup ",[17,770,771],{},"{ true: \"...\" }[v]"," treats them the same, so they always produce the same classes.",[13,774,775,776,779,780,782,783,785,786,261,788,715,790,792],{},"Compound variants break that. A compound compiled to ",[17,777,778],{},"v1 === true"," matches ",[17,781,667],{}," but not ",[17,784,759],{},". Two props that look the same can now produce different class strings. So when a recipe has compounds, Panda switches the table to strict mode: ",[17,787,667],{},[17,789,714],{},[17,791,612],{}," each get their own slot, and numbers aren't cached at all.",[248,794,796],{"id":795},"sva-one-string-per-slot","sva: one string per slot",[13,798,799],{},"A slot recipe compiles the same way, with one selection shared by every slot. This one:",[44,801,803],{"className":46,"code":802,"language":48,"meta":49,"style":49},"const tabs = sva({\n  slots: ['root', 'trigger'],\n  className: 'tabs',\n  base: { root: { display: 'flex' }, trigger: { cursor: 'pointer' } },\n  variants: {\n    size: {\n      sm: { root: { gap: '4px' } },\n      lg: { root: { gap: '8px' }, trigger: { fontSize: '16px' } },\n    },\n  },\n  defaultVariants: { size: 'sm' },\n  compoundVariants: [{ size: 'lg', css: { trigger: { fontWeight: 'bold' } } }],\n})\n",[17,804,805,820,836,846,862,866,871,881,897,902,906,914,930],{"__ignoreMap":49},[53,806,807,810,813,815,818],{"class":55,"line":56},[53,808,809],{"class":59},"const",[53,811,812],{"class":90}," tabs",[53,814,94],{"class":59},[53,816,817],{"class":97}," sva",[53,819,101],{"class":63},[53,821,822,825,828,830,833],{"class":55,"line":74},[53,823,824],{"class":63},"  slots: [",[53,826,827],{"class":70},"'root'",[53,829,261],{"class":63},[53,831,832],{"class":70},"'trigger'",[53,834,835],{"class":63},"],\n",[53,837,838,841,844],{"class":55,"line":81},[53,839,840],{"class":63},"  className: ",[53,842,843],{"class":70},"'tabs'",[53,845,501],{"class":63},[53,847,848,851,854,857,860],{"class":55,"line":104},[53,849,850],{"class":63},"  base: { root: { display: ",[53,852,853],{"class":70},"'flex'",[53,855,856],{"class":63}," }, trigger: { cursor: ",[53,858,859],{"class":70},"'pointer'",[53,861,131],{"class":63},[53,863,864],{"class":55,"line":116},[53,865,119],{"class":63},[53,867,868],{"class":55,"line":122},[53,869,870],{"class":63},"    size: {\n",[53,872,873,876,879],{"class":55,"line":134},[53,874,875],{"class":63},"      sm: { root: { gap: ",[53,877,878],{"class":70},"'4px'",[53,880,131],{"class":63},[53,882,883,886,889,892,895],{"class":55,"line":145},[53,884,885],{"class":63},"      lg: { root: { gap: ",[53,887,888],{"class":70},"'8px'",[53,890,891],{"class":63}," }, trigger: { fontSize: ",[53,893,894],{"class":70},"'16px'",[53,896,131],{"class":63},[53,898,899],{"class":55,"line":151},[53,900,901],{"class":63},"    },\n",[53,903,904],{"class":55,"line":175},[53,905,148],{"class":63},[53,907,908,910,912],{"class":55,"line":189},[53,909,178],{"class":63},[53,911,157],{"class":70},[53,913,113],{"class":63},[53,915,916,918,921,924,927],{"class":55,"line":552},[53,917,154],{"class":63},[53,919,920],{"class":70},"'lg'",[53,922,923],{"class":63},", css: { trigger: { fontWeight: ",[53,925,926],{"class":70},"'bold'",[53,928,929],{"class":63}," } } }],\n",[53,931,932],{"class":55,"line":558},[53,933,192],{"class":63},[13,935,936],{},"ships as this:",[44,938,940],{"className":322,"code":939,"language":324,"meta":49,"style":49},"const tabs = __pm((p = {}) => {\n  p ??= {}\n  const _p0 = p[\"size\"], v0 = _p0 === void 0 ? 'sm' : _p0\n  return {\n    root: __pcx(\"d_flex\", \"tabs__root\", { sm: \"gap_4px\", lg: \"gap_8px\" }[v0]),\n    trigger: __pcx(\"cursor_pointer\", \"tabs__trigger\", { lg: \"fs_16px\" }[v0], v0 === 'lg' && \"font-weight_bold\"),\n  }\n}, { size: [\"sm\", \"lg\"] }, 1)\n",[17,941,942,965,973,1007,1013,1046,1085,1090],{"__ignoreMap":49},[53,943,944,946,948,950,953,955,957,959,961,963],{"class":55,"line":56},[53,945,809],{"class":59},[53,947,812],{"class":90},[53,949,94],{"class":59},[53,951,952],{"class":97}," __pm",[53,954,369],{"class":63},[53,956,13],{"class":372},[53,958,94],{"class":59},[53,960,377],{"class":63},[53,962,380],{"class":59},[53,964,383],{"class":63},[53,966,967,969,971],{"class":55,"line":74},[53,968,388],{"class":63},[53,970,391],{"class":59},[53,972,394],{"class":63},[53,974,975,977,979,981,983,985,987,989,991,993,995,997,999,1001,1003,1005],{"class":55,"line":81},[53,976,399],{"class":59},[53,978,402],{"class":90},[53,980,94],{"class":59},[53,982,407],{"class":63},[53,984,410],{"class":70},[53,986,413],{"class":63},[53,988,416],{"class":90},[53,990,94],{"class":59},[53,992,421],{"class":63},[53,994,424],{"class":59},[53,996,427],{"class":59},[53,998,430],{"class":90},[53,1000,433],{"class":59},[53,1002,436],{"class":70},[53,1004,439],{"class":59},[53,1006,442],{"class":63},[53,1008,1009,1011],{"class":55,"line":104},[53,1010,487],{"class":59},[53,1012,383],{"class":63},[53,1014,1015,1018,1020,1023,1026,1028,1031,1034,1037,1040,1043],{"class":55,"line":116},[53,1016,1017],{"class":63},"    root: ",[53,1019,643],{"class":97},[53,1021,1022],{"class":63},"(",[53,1024,1025],{"class":70},"\"d_flex\"",[53,1027,261],{"class":63},[53,1029,1030],{"class":70},"\"tabs__root\"",[53,1032,1033],{"class":63},", { sm: ",[53,1035,1036],{"class":70},"\"gap_4px\"",[53,1038,1039],{"class":63},", lg: ",[53,1041,1042],{"class":70},"\"gap_8px\"",[53,1044,1045],{"class":63}," }[v0]),\n",[53,1047,1048,1051,1053,1055,1058,1060,1063,1066,1069,1072,1074,1077,1079,1082],{"class":55,"line":122},[53,1049,1050],{"class":63},"    trigger: ",[53,1052,643],{"class":97},[53,1054,1022],{"class":63},[53,1056,1057],{"class":70},"\"cursor_pointer\"",[53,1059,261],{"class":63},[53,1061,1062],{"class":70},"\"tabs__trigger\"",[53,1064,1065],{"class":63},", { lg: ",[53,1067,1068],{"class":70},"\"fs_16px\"",[53,1070,1071],{"class":63}," }[v0], v0 ",[53,1073,424],{"class":59},[53,1075,1076],{"class":70}," 'lg'",[53,1078,535],{"class":59},[53,1080,1081],{"class":70}," \"font-weight_bold\"",[53,1083,1084],{"class":63},"),\n",[53,1086,1087],{"class":55,"line":134},[53,1088,1089],{"class":63},"  }\n",[53,1091,1092,1095,1097,1099,1102,1105,1107],{"class":55,"line":145},[53,1093,1094],{"class":63},"}, { size: [",[53,1096,580],{"class":70},[53,1098,261],{"class":63},[53,1100,1101],{"class":70},"\"lg\"",[53,1103,1104],{"class":63},"] }, ",[53,1106,702],{"class":90},[53,1108,1109],{"class":63},")\n",[13,1111,1112,1113,1116,1117,1120,1121,1124],{},"Each slot gets only the variants and compounds that style it. Here the compound ",[17,1114,1115],{},"font-weight_bold"," only touches ",[17,1118,1119],{},"trigger",", so ",[17,1122,1123],{},"root"," never checks for it.",[13,1126,1127,1128,1130],{},"The trailing ",[17,1129,702],{}," turns on strict mode, because this recipe has a compound.",[248,1132,1134],{"id":1133},"when-the-call-itself-disappears","When the call itself disappears",[13,1136,1137],{},"Take a different button, in its own file:",[44,1139,1141],{"className":46,"code":1140,"language":48,"meta":49,"style":49},"\u002F\u002F button.ts\nexport const button = cva({\n  base: { color: 'red', padding: '2px' },\n  variants: { size: { sm: { padding: '4px' }, lg: { padding: '8px' } } },\n  compoundVariants: [{ size: 'lg', css: { fontSize: '20px' } }],\n  defaultVariants: { size: 'sm' },\n})\n",[17,1142,1143,1148,1162,1176,1191,1205,1213],{"__ignoreMap":49},[53,1144,1145],{"class":55,"line":56},[53,1146,1147],{"class":564},"\u002F\u002F button.ts\n",[53,1149,1150,1152,1154,1156,1158,1160],{"class":55,"line":74},[53,1151,84],{"class":59},[53,1153,87],{"class":59},[53,1155,91],{"class":90},[53,1157,94],{"class":59},[53,1159,98],{"class":97},[53,1161,101],{"class":63},[53,1163,1164,1166,1168,1171,1174],{"class":55,"line":81},[53,1165,107],{"class":63},[53,1167,140],{"class":70},[53,1169,1170],{"class":63},", padding: ",[53,1172,1173],{"class":70},"'2px'",[53,1175,113],{"class":63},[53,1177,1178,1181,1183,1186,1188],{"class":55,"line":104},[53,1179,1180],{"class":63},"  variants: { size: { sm: { padding: ",[53,1182,878],{"class":70},[53,1184,1185],{"class":63}," }, lg: { padding: ",[53,1187,888],{"class":70},[53,1189,1190],{"class":63}," } } },\n",[53,1192,1193,1195,1197,1200,1203],{"class":55,"line":116},[53,1194,154],{"class":63},[53,1196,920],{"class":70},[53,1198,1199],{"class":63},", css: { fontSize: ",[53,1201,1202],{"class":70},"'20px'",[53,1204,172],{"class":63},[53,1206,1207,1209,1211],{"class":55,"line":122},[53,1208,178],{"class":63},[53,1210,157],{"class":70},[53,1212,113],{"class":63},[53,1214,1215],{"class":55,"line":134},[53,1216,192],{"class":63},[13,1218,1219],{},"Now another file calls it with values Panda can read:",[44,1221,1223],{"className":322,"code":1222,"language":324,"meta":49,"style":49},"\u002F\u002F you write\nimport { button } from '.\u002Fbutton'\nexport const large = button({ size: 'lg' })\nexport const fallback = button()\n\n\u002F\u002F your bundle ships\nimport { button } from '.\u002Fbutton'\nexport const large = \"color_red padding_8px fs_20px\"\nexport const fallback = \"color_red padding_4px\"\n",[17,1224,1225,1230,1242,1263,1279,1283,1288,1298,1311],{"__ignoreMap":49},[53,1226,1227],{"class":55,"line":56},[53,1228,1229],{"class":564},"\u002F\u002F you write\n",[53,1231,1232,1234,1237,1239],{"class":55,"line":74},[53,1233,60],{"class":59},[53,1235,1236],{"class":63}," { button } ",[53,1238,67],{"class":59},[53,1240,1241],{"class":70}," '.\u002Fbutton'\n",[53,1243,1244,1246,1248,1251,1253,1255,1258,1260],{"class":55,"line":81},[53,1245,84],{"class":59},[53,1247,87],{"class":59},[53,1249,1250],{"class":90}," large",[53,1252,94],{"class":59},[53,1254,91],{"class":97},[53,1256,1257],{"class":63},"({ size: ",[53,1259,920],{"class":70},[53,1261,1262],{"class":63}," })\n",[53,1264,1265,1267,1269,1272,1274,1276],{"class":55,"line":104},[53,1266,84],{"class":59},[53,1268,87],{"class":59},[53,1270,1271],{"class":90}," fallback",[53,1273,94],{"class":59},[53,1275,91],{"class":97},[53,1277,1278],{"class":63},"()\n",[53,1280,1281],{"class":55,"line":116},[53,1282,78],{"emptyLinePlaceholder":77},[53,1284,1285],{"class":55,"line":122},[53,1286,1287],{"class":564},"\u002F\u002F your bundle ships\n",[53,1289,1290,1292,1294,1296],{"class":55,"line":134},[53,1291,60],{"class":59},[53,1293,1236],{"class":63},[53,1295,67],{"class":59},[53,1297,1241],{"class":70},[53,1299,1300,1302,1304,1306,1308],{"class":55,"line":145},[53,1301,84],{"class":59},[53,1303,87],{"class":59},[53,1305,1250],{"class":90},[53,1307,94],{"class":59},[53,1309,1310],{"class":70}," \"color_red padding_8px fs_20px\"\n",[53,1312,1313,1315,1317,1319,1321],{"class":55,"line":151},[53,1314,84],{"class":59},[53,1316,87],{"class":59},[53,1318,1271],{"class":90},[53,1320,94],{"class":59},[53,1322,1323],{"class":70}," \"color_red padding_4px\"\n",[13,1325,1326,1327,1330,1331,1334,1335,1338,1339,307,1342,1344,1345,1348],{},"The call is gone. Panda follows the import, reads the recipe config, and resolves the selection at build time. The default ",[17,1328,1329],{},"sm"," applies when nothing is passed, the ",[17,1332,1333],{},"lg"," compound adds ",[17,1336,1337],{},"fs_20px",", and ",[17,1340,1341],{},"padding_2px",[17,1343,284],{}," is gone because the variant's padding replaced it. A call with a value Panda can't know, like ",[17,1346,1347],{},"button({ size })",", stays a runtime call to the compiled function.",[13,1350,1351,1352,1354],{},"A call in the same file as the ",[17,1353,19],{}," is different. It stays a call, even with static values. It still benefits from the compiled function and the table, but it isn't folded into a string. If you want the call itself to go away, keep recipes in their own files.",[13,1356,1357,1360,1361,1364],{},[17,1358,1359],{},".raw()"," is the exception. A static ",[17,1362,1363],{},"button.raw({ size: 'sm' })"," folds to a plain style object in either case.",[248,1366,1368],{"id":1367},"two-kinds-of-recipe","Two kinds of recipe",[13,1370,1371,1372,1374,1375,261,1377,261,1380,1383,1384,1387,1388,1391,1392,1395],{},"Not every recipe needs the full ",[17,1373,19],{}," surface. When a recipe is exported or passed around as a value, other code might call ",[17,1376,1359],{},[17,1378,1379],{},".merge()",[17,1381,1382],{},"getVariantProps()"," or ",[17,1385,1386],{},"splitVariantProps()"," on it. Those recipes get wrapped in ",[17,1389,1390],{},"attachRecipe"," (",[17,1393,1394],{},"__pr"," above), which adds those methods at runtime from the config.",[13,1397,1398,1399,1402],{},"When a recipe is only ever called in its own file, Panda knows nothing else will touch it. It gets the bare memoized function (",[17,1400,1401],{},"__pm",") and none of the extra methods. A recipe with no variants doesn't even get the memo, since there's nothing to choose between.",[248,1404,1406],{"id":1405},"when-panda-keeps-the-runtime-cva","When Panda keeps the runtime cva",[13,1408,1409],{},"Panda doesn't compile a recipe when:",[594,1411,1412,1423,1436],{},[273,1413,1414,1417,1418,1422],{},[599,1415,1416],{},"The config isn't static."," If Panda can't read the whole recipe at build time, there's nothing to compile. ",[31,1419,1421],{"href":1420},"\u002Fblog\u002Fpanda-never-runs-your-code","Part 4"," covers what it can read.",[273,1424,1425,1428,1429,715,1432,1435],{},[599,1426,1427],{},"A compound checks a prop that isn't a variant."," This is about the recipe definition, not the props you pass. The cache table only has slots for declared variants, so ",[17,1430,1431],{},"{ size: 'sm', loading: true }",[17,1433,1434],{},"{ size: 'sm' }"," would share a slot and get the same classes.",[273,1437,1438,1441],{},[599,1439,1440],{},"Hashed class names are on."," Panda leaves recipes alone, and warns once per file for the ones it would have compiled.",[13,1443,1444,1445,1447],{},"In all of those cases you get the runtime ",[17,1446,19],{}," from the start of this post. It still works. It does more work per new combination.",[13,1449,1450,1451,1454],{},"The compound one is easy to hit without noticing. This recipe stays at runtime, because ",[17,1452,1453],{},"loading"," only exists inside the compound:",[44,1456,1458],{"className":46,"code":1457,"language":48,"meta":49,"style":49},"export const button = cva({\n  variants: {\n    size: { sm: { fontSize: '12px' }, lg: { fontSize: '16px' } },\n  },\n  compoundVariants: [{ size: 'sm', loading: true, css: { opacity: 0.5 } }],\n})\n",[17,1459,1460,1474,1478,1491,1495,1514],{"__ignoreMap":49},[53,1461,1462,1464,1466,1468,1470,1472],{"class":55,"line":56},[53,1463,84],{"class":59},[53,1465,87],{"class":59},[53,1467,91],{"class":90},[53,1469,94],{"class":59},[53,1471,98],{"class":97},[53,1473,101],{"class":63},[53,1475,1476],{"class":55,"line":74},[53,1477,119],{"class":63},[53,1479,1480,1482,1484,1487,1489],{"class":55,"line":81},[53,1481,125],{"class":63},[53,1483,128],{"class":70},[53,1485,1486],{"class":63}," }, lg: { fontSize: ",[53,1488,894],{"class":70},[53,1490,131],{"class":63},[53,1492,1493],{"class":55,"line":104},[53,1494,148],{"class":63},[53,1496,1497,1499,1501,1504,1506,1509,1512],{"class":55,"line":116},[53,1498,154],{"class":63},[53,1500,157],{"class":70},[53,1502,1503],{"class":63},", loading: ",[53,1505,667],{"class":90},[53,1507,1508],{"class":63},", css: { opacity: ",[53,1510,1511],{"class":90},"0.5",[53,1513,172],{"class":63},[53,1515,1516],{"class":55,"line":122},[53,1517,192],{"class":63},[13,1519,1520,1521,1523],{},"Declare ",[17,1522,1453],{}," as a variant, even with no styles, and it compiles:",[44,1525,1527],{"className":46,"code":1526,"language":48,"meta":49,"style":49},"export const button = cva({\n  variants: {\n    size: { sm: { fontSize: '12px' }, lg: { fontSize: '16px' } },\n    loading: { true: {} },\n  },\n  compoundVariants: [{ size: 'sm', loading: true, css: { opacity: 0.5 } }],\n})\n",[17,1528,1529,1543,1547,1559,1564,1568,1584],{"__ignoreMap":49},[53,1530,1531,1533,1535,1537,1539,1541],{"class":55,"line":56},[53,1532,84],{"class":59},[53,1534,87],{"class":59},[53,1536,91],{"class":90},[53,1538,94],{"class":59},[53,1540,98],{"class":97},[53,1542,101],{"class":63},[53,1544,1545],{"class":55,"line":74},[53,1546,119],{"class":63},[53,1548,1549,1551,1553,1555,1557],{"class":55,"line":81},[53,1550,125],{"class":63},[53,1552,128],{"class":70},[53,1554,1486],{"class":63},[53,1556,894],{"class":70},[53,1558,131],{"class":63},[53,1560,1561],{"class":55,"line":104},[53,1562,1563],{"class":63},"    loading: { true: {} },\n",[53,1565,1566],{"class":55,"line":116},[53,1567,148],{"class":63},[53,1569,1570,1572,1574,1576,1578,1580,1582],{"class":55,"line":122},[53,1571,154],{"class":63},[53,1573,157],{"class":70},[53,1575,1503],{"class":63},[53,1577,667],{"class":90},[53,1579,1508],{"class":63},[53,1581,1511],{"class":90},[53,1583,172],{"class":63},[53,1585,1586],{"class":55,"line":134},[53,1587,192],{"class":63},[13,1589,1590,1591,1594,1595,246],{},"The call is the same either way: ",[17,1592,1593],{},"button({ size: 'sm', loading: true })",". Only the first stays on the runtime ",[17,1596,19],{},[248,1598,1600],{"id":1599},"what-this-means-for-your-code","What this means for your code",[594,1602,1603,1609,1615,1630,1636,1648],{},[273,1604,1605,1608],{},[599,1606,1607],{},"Turn on source transforms."," Everything in this post needs it.",[273,1610,1611,1614],{},[599,1612,1613],{},"Keep recipe configs static."," Build them from literals and imports Panda can follow, not from functions that run at runtime.",[273,1616,1617,1620,1621,1623,1624,1626,1627,246],{},[599,1618,1619],{},"Declare every prop your compounds check as a variant."," A compound on ",[17,1622,1453],{}," with no ",[17,1625,1453],{}," variant keeps the whole recipe at runtime. Add ",[17,1628,1629],{},"loading: { true: {} }",[273,1631,1632,1635],{},[599,1633,1634],{},"Put recipes in their own files."," Static calls from other files fold to plain strings.",[273,1637,1638,1641,1642,1644,1645,1647],{},[599,1639,1640],{},"Pass booleans for boolean variants."," ",[17,1643,667],{},", not ",[17,1646,759],{},". With compounds, they aren't the same thing.",[273,1649,1650,1653,1654,1657],{},[599,1651,1652],{},"Fewer, wider variants beat many booleans."," Every variant multiplies the table. One ",[17,1655,1656],{},"tone"," with four values grows it less than four boolean flags.",[248,1659,1661],{"id":1660},"strings-all-the-way-down","Strings all the way down",[13,1663,1664],{},"At build time, Panda already knows every class your recipe can produce. With source transforms on, the browser's only job is to pick the right strings.",[13,1666,1667],{},[27,1668,1669,1670,1674,1675,1678,1679,261,1681,261,1685,261,1688,261,1691,261,1695,246],{},"Next, an interlude: ",[31,1671,1673],{"href":1672},"\u002Fblog\u002Fknow-what-your-css-actually-uses","Know what your CSS actually uses",", a guide to ",[17,1676,1677],{},"panda analyze",". Then part 7, cold vs warm, and how Panda's watch mode got fast. If you missed the earlier parts: ",[31,1680,34],{"href":33},[31,1682,1684],{"href":1683},"\u002Fblog\u002Fevery-style-is-an-atom","Every style is an atom",[31,1686,1687],{"href":310},"Look it up once",[31,1689,1690],{"href":1420},"Panda never runs your code",[31,1692,1694],{"href":1693},"\u002Fblog\u002Fwhat-runtime-means-in-panda","What runtime means in Panda",[31,1696,1697],{"href":40},"The IR behind your ternary",[1699,1700,1701],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}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);}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":49,"searchDepth":74,"depth":74,"links":1703},[1704,1705,1706,1707,1709,1710,1711,1712,1713,1714],{"id":250,"depth":74,"text":251},{"id":315,"depth":74,"text":316},{"id":674,"depth":74,"text":675},{"id":753,"depth":74,"text":1708},"Why true and 'true' get two slots",{"id":795,"depth":74,"text":796},{"id":1133,"depth":74,"text":1134},{"id":1367,"depth":74,"text":1368},{"id":1405,"depth":74,"text":1406},{"id":1599,"depth":74,"text":1600},{"id":1660,"depth":74,"text":1661},null,"2026-11-12","What Panda CSS does to a cva() recipe at build time. Variant lookups instead of style merging, compound variants as plain checks, and a 256-slot table that remembers every answer.",false,"md",{},"\u002Fblog\u002Fhow-panda-makes-cva-fast","11min",{"title":5,"description":1717},"How Panda makes cva fast: compiling Panda CSS recipes into lookups","Panda internals","blog\u002Fhow-panda-makes-cva-fast","7E1HFZ-5NWzOo21jA5-8GnGfDQerZmhDR5knkHM_aGA",{"series":1725,"part":122,"total":122},1794505029487]