[{"data":1,"prerenderedAt":1993},["ShallowReactive",2],{"page-\u002Fmastering-container-queries-responsive-layouts\u002Ffluid-typography-with-clamp\u002Ffluid-type-accessibility-and-zoom\u002F":3,"content-all-pages":1732},{"id":4,"title":5,"body":6,"description":1710,"extension":1711,"meta":1712,"navigation":428,"path":1728,"seo":1729,"stem":1730,"__hash__":1731},"content\u002Fmastering-container-queries-responsive-layouts\u002Ffluid-typography-with-clamp\u002Ffluid-type-accessibility-and-zoom\u002Findex.md","Fluid Type, Accessibility and Zoom: The vw-Only Trap",{"type":7,"value":8,"toc":1699},"minimark",[9,13,41,46,49,57,67,71,74,83,96,201,204,208,218,222,1179,1181,1185,1205,1230,1239,1268,1277,1305,1307,1311,1314,1359,1362,1364,1368,1371,1546,1567,1569,1573,1579,1594,1596,1600,1612,1624,1643,1660,1664,1696],[10,11,5],"h1",{"id":12},"fluid-type-accessibility-and-zoom-the-vw-only-trap",[14,15,16,17,21,22,24,25,30,31,35,36,40],"p",{},"A fluid type scale looks like the most accessible thing you can ship. It never stair-steps, it never leaves a heading absurdly large on a phone, and it reads well at every width you check in a device toolbar. Then a user who reads at 200% zoom loads the page, and the body copy barely moves while every margin, button and image around it grows — because the scale was written in ",[18,19,20],"code",{},"vw"," units, and ",[18,23,20],{}," is one of the few things in CSS that zoom cannot enlarge. This page is about that specific failure, how it relates to WCAG 2.2 Success Criterion 1.4.4 Resize Text, and the one-line change that fixes it. It sits alongside the rest of ",[26,27,29],"a",{"href":28},"\u002Fmastering-container-queries-responsive-layouts\u002Ffluid-typography-with-clamp\u002F","Fluid Typography with clamp()"," and the sizing approach described in ",[26,32,34],{"href":33},"\u002Fmastering-container-queries-responsive-layouts\u002F","Mastering Container Queries & Responsive Layouts","; the mechanics of building the scale itself are covered in ",[26,37,39],{"href":38},"\u002Fmastering-container-queries-responsive-layouts\u002Ffluid-typography-with-clamp\u002Ffluid-typography-without-javascript\u002F","fluid typography without JavaScript",".",[42,43,45],"h2",{"id":44},"what-144-actually-requires","What 1.4.4 actually requires",[14,47,48],{},"Success Criterion 1.4.4 Resize Text is a Level AA requirement: text must be resizable up to 200 percent without assistive technology, and without loss of content or functionality. Two things about the wording are easy to miss.",[14,50,51,52,56],{},"First, \"up to 200%\" is a floor for the ",[53,54,55],"em",{},"user's"," action, not a cap on yours. A scale that grows text by 12% when the user asks for 200% has not met it, even though nothing visibly broke. The criterion is about the text actually getting twice as big.",[14,58,59,60,63,64,66],{},"Second, it is deliberately silent about ",[53,61,62],{},"which"," mechanism the user employs. Full-page browser zoom satisfies it on most desktop browsers essentially for free, which is why the criterion is so often assumed to be automatically met. But text-only zoom (Firefox's zoom-text-only mode, and the text scaling controls on mobile operating systems) resizes the root font size without touching the layout viewport at all — and a ",[18,65,20],{},"-derived value does not depend on the root font size, so it does not move by a single pixel. A scale that only passes under full-page zoom passes by accident.",[42,68,70],{"id":69},"why-vw-cancels-itself-out-under-zoom","Why vw cancels itself out under zoom",[14,72,73],{},"Full-page browser zoom works by changing the ratio between CSS pixels and device pixels. At 200% zoom, one CSS pixel is painted at twice the size, so the same window now holds half as many CSS pixels across. The layout viewport, measured in CSS pixels, halves.",[14,75,76,79,80,82],{},[18,77,78],{},"1vw"," is defined as one percent of that layout viewport width. So at 200% zoom, ",[18,81,78],{}," resolves to half as many CSS pixels as before — and each of those CSS pixels is painted twice as large. The two factors multiply to exactly 1. Rendered size unchanged.",[14,84,85,88,89,91,92,95],{},[18,86,87],{},"1rem",", by contrast, is one times the root element's computed font size, which is a fixed number of CSS pixels regardless of viewport width. Zoom leaves that number alone and simply paints each CSS pixel bigger, so ",[18,90,87],{}," grows by the full zoom factor. Text-only zoom changes the number itself, so ",[18,93,94],{},"rem"," grows there too. It is the only unit in the pair that responds to either mechanism.",[97,98,104,105,104,109,104,113,104,120,104,126,104,138,104,144,104,151,104,155,104,159,104,164,104,168,104,172,104,175,104,178,104,183,104,185,104,190,104,193,104,196],"svg",{"viewBox":99,"role":100,"ariaLabel":101,"xmlns":102,"style":103},"0 0 720 360","img","Bar comparison showing that a vw-only font size barely changes at 200 percent zoom while a rem plus vw font size doubles","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;height:auto;max-width:720px;margin:2rem 0","\n  ",[106,107,108],"title",{},"Rendered text size at 100% and 200% zoom",[110,111,112],"desc",{},"A clamp scale with no rem term stays near its original rendered size when the page is zoomed to 200 percent, while a scale whose preferred value includes a rem term roughly doubles.",[114,115,119],"text",{"x":116,"y":117,"style":118},"360","28","text-anchor:middle;fill:currentColor;font:700 17px sans-serif","rendered size at 100% vs 200% zoom",[114,121,125],{"x":122,"y":123,"style":124},"175","62","text-anchor:middle;fill:currentColor;font:600 13px sans-serif","clamp(1rem, 4vw, 2.5rem)",[127,128],"rect",{"x":129,"y":130,"width":131,"height":132,"rx":133,"fill":134,"stroke":135,"strokeWidth":136,"opacity":137},"30","76","290","212","10","none","currentColor","1.5","0.55",[114,139,143],{"x":140,"y":141,"style":142},"56","120","text-anchor:start;fill:currentColor;font:12px sans-serif;opacity:0.75","100%",[127,145],{"x":146,"y":147,"width":141,"height":148,"rx":149,"fill":135,"opacity":150},"110","104","24","4","0.25",[114,152,154],{"x":140,"y":153,"style":142},"188","200%",[127,156],{"x":146,"y":157,"width":158,"height":148,"rx":149,"fill":135,"opacity":150},"172","128",[114,160,163],{"x":122,"y":161,"style":162},"248","text-anchor:middle;fill:currentColor;font:12px sans-serif;opacity:0.9","grows by about 6%",[114,165,167],{"x":122,"y":166,"style":162},"270","fails 1.4.4",[114,169,171],{"x":170,"y":123,"style":124},"545","clamp(1rem, .85rem + 1.2vw, 2.5rem)",[127,173],{"x":174,"y":130,"width":131,"height":132,"rx":133,"fill":134,"stroke":135,"strokeWidth":136,"opacity":137},"400",[114,176,143],{"x":177,"y":141,"style":142},"426",[127,179],{"x":180,"y":147,"width":141,"height":148,"rx":149,"fill":181,"opacity":182},"480","#7aa2ff","0.4",[114,184,154],{"x":177,"y":153,"style":142},[127,186],{"x":180,"y":187,"width":188,"height":189,"rx":149,"fill":181,"opacity":182},"160","190","48",[114,191,192],{"x":170,"y":161,"style":162},"grows with the zoom factor",[114,194,195],{"x":170,"y":166,"style":162},"passes 1.4.4",[114,197,200],{"x":116,"y":198,"style":199},"330","text-anchor:middle;fill:currentColor;font:12px sans-serif;opacity:0.75","The rem term is the only part of the value that zoom can reach.",[202,203],"hr",{},[42,205,207],{"id":206},"see-both-scales-side-by-side","See both scales side by side",[14,209,210,211,213,214,217],{},"Both headings below carry the same ",[18,212,87],{}," floor and the same ",[18,215,216],{},"2.5rem"," ceiling. Only the preferred value differs. Zoom this page to 200% and compare them.",[219,220],"live-demo",{"name":221},"lp2-zoom-scales",[223,224,229],"pre",{"className":225,"code":226,"language":227,"meta":228,"style":228},"language-html shiki shiki-themes github-light-high-contrast github-dark-high-contrast","\u003C!DOCTYPE html>\n\u003Chtml lang=\"en\">\n\u003Chead>\n\u003Cmeta charset=\"utf-8\">\n\u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n\u003Ctitle>Zoom-safe fluid type\u003C\u002Ftitle>\n\u003Cstyle>\n  \u002F* Never set a px or % font-size on :root — it overrides the user's own\n     browser font-size preference, which is the setting rem is built on. *\u002F\n  body { margin: 0; padding: 1rem; font: 16px\u002F1.5 system-ui, sans-serif; }\n\n  .shell { display: grid; gap: 0.9rem; grid-template-columns: 1fr 1fr; }\n\n  .col { border: 1px solid #d4d4d8; border-radius: 10px; padding: 0.85rem; }\n  .col--good { border-color: #3b6df5; }\n\n  .col__tag {\n    margin: 0 0 0.4rem;\n    font-size: 0.72rem;\n    letter-spacing: 0.08em;\n    text-transform: uppercase;\n    opacity: 0.7;\n  }\n\n  .col__code {\n    display: block;\n    margin-block-start: 0.6rem;\n    font-family: ui-monospace, monospace;\n    font-size: 0.78rem;\n    opacity: 0.7;\n    overflow-wrap: anywhere;\n  }\n\n  \u002F* The trap. The preferred value is pure vw, so between the 1rem floor and\n     the 2.5rem ceiling the rendered size is pinned to the layout viewport —\n     and the layout viewport shrinks by exactly the zoom factor. *\u002F\n  .trap-heading {\n    margin: 0;\n    line-height: 1.15;\n    font-size: clamp(1rem, 4vw, 2.5rem);\n  }\n\n  \u002F* The fix. The rem term is multiplied by root font size and zoom, so it\n     carries the growth; the vw term only supplies the fluid slope. *\u002F\n  .good-heading {\n    margin: 0;\n    line-height: 1.15;\n    font-size: clamp(1rem, 0.85rem + 1.2vw, 2.5rem);\n  }\n\u003C\u002Fstyle>\n\u003C\u002Fhead>\n\u003Cbody>\n  \u003Cdiv class=\"shell\">\n    \u003Csection class=\"col col--trap\">\n      \u003Cp class=\"col__tag\">defeats zoom\u003C\u002Fp>\n      \u003Ch3 class=\"trap-heading\">Resize me\u003C\u002Fh3>\n      \u003Ccode class=\"col__code\">clamp(1rem, 4vw, 2.5rem)\u003C\u002Fcode>\n    \u003C\u002Fsection>\n    \u003Csection class=\"col col--good\">\n      \u003Cp class=\"col__tag\">honours zoom\u003C\u002Fp>\n      \u003Ch3 class=\"good-heading\">Resize me\u003C\u002Fh3>\n      \u003Ccode class=\"col__code\">clamp(1rem, 0.85rem + 1.2vw, 2.5rem)\u003C\u002Fcode>\n    \u003C\u002Fsection>\n  \u003C\u002Fdiv>\n\u003C\u002Fbody>\n\u003C\u002Fhtml>\n","html","",[18,230,231,251,271,281,299,324,338,348,355,361,423,430,477,482,529,547,552,561,582,597,612,625,638,644,649,657,670,685,703,717,728,737,742,747,753,759,765,773,784,797,830,835,840,846,852,860,871,882,920,925,935,944,954,973,991,1013,1035,1056,1066,1082,1102,1122,1142,1151,1161,1170],{"__ignoreMap":228},[232,233,236,240,244,248],"span",{"class":234,"line":235},"line",1,[232,237,239],{"class":238},"suds8","\u003C!",[232,241,243],{"class":242},"sne4z","DOCTYPE",[232,245,247],{"class":246},"s-5SL"," html",[232,249,250],{"class":238},">\n",[232,252,254,257,259,262,265,269],{"class":234,"line":253},2,[232,255,256],{"class":238},"\u003C",[232,258,227],{"class":242},[232,260,261],{"class":246}," lang",[232,263,264],{"class":238},"=",[232,266,268],{"class":267},"sT6z2","\"en\"",[232,270,250],{"class":238},[232,272,274,276,279],{"class":234,"line":273},3,[232,275,256],{"class":238},[232,277,278],{"class":242},"head",[232,280,250],{"class":238},[232,282,284,286,289,292,294,297],{"class":234,"line":283},4,[232,285,256],{"class":238},[232,287,288],{"class":242},"meta",[232,290,291],{"class":246}," charset",[232,293,264],{"class":238},[232,295,296],{"class":267},"\"utf-8\"",[232,298,250],{"class":238},[232,300,302,304,306,309,311,314,317,319,322],{"class":234,"line":301},5,[232,303,256],{"class":238},[232,305,288],{"class":242},[232,307,308],{"class":246}," name",[232,310,264],{"class":238},[232,312,313],{"class":267},"\"viewport\"",[232,315,316],{"class":246}," content",[232,318,264],{"class":238},[232,320,321],{"class":267},"\"width=device-width, initial-scale=1\"",[232,323,250],{"class":238},[232,325,327,329,331,334,336],{"class":234,"line":326},6,[232,328,256],{"class":238},[232,330,106],{"class":242},[232,332,333],{"class":238},">Zoom-safe fluid type\u003C\u002F",[232,335,106],{"class":242},[232,337,250],{"class":238},[232,339,341,343,346],{"class":234,"line":340},7,[232,342,256],{"class":238},[232,344,345],{"class":242},"style",[232,347,250],{"class":238},[232,349,351],{"class":234,"line":350},8,[232,352,354],{"class":353},"sLBg1","  \u002F* Never set a px or % font-size on :root — it overrides the user's own\n",[232,356,358],{"class":234,"line":357},9,[232,359,360],{"class":353},"     browser font-size preference, which is the setting rem is built on. *\u002F\n",[232,362,364,367,370,373,376,379,382,385,387,390,393,395,398,400,403,406,409,411,414,417,420],{"class":234,"line":363},10,[232,365,366],{"class":242},"  body",[232,368,369],{"class":238}," { ",[232,371,372],{"class":246},"margin",[232,374,375],{"class":238},": ",[232,377,378],{"class":246},"0",[232,380,381],{"class":238},"; ",[232,383,384],{"class":246},"padding",[232,386,375],{"class":238},[232,388,389],{"class":246},"1",[232,391,94],{"class":392},"sHUrx",[232,394,381],{"class":238},[232,396,397],{"class":246},"font",[232,399,375],{"class":238},[232,401,402],{"class":246},"16",[232,404,405],{"class":392},"px",[232,407,408],{"class":238},"\u002F",[232,410,136],{"class":246},[232,412,413],{"class":246}," system-ui",[232,415,416],{"class":238},", ",[232,418,419],{"class":246},"sans-serif",[232,421,422],{"class":238},"; }\n",[232,424,426],{"class":234,"line":425},11,[232,427,429],{"emptyLinePlaceholder":428},true,"\n",[232,431,433,436,438,441,443,446,448,451,453,456,458,460,463,465,467,470,473,475],{"class":234,"line":432},12,[232,434,435],{"class":246},"  .shell",[232,437,369],{"class":238},[232,439,440],{"class":246},"display",[232,442,375],{"class":238},[232,444,445],{"class":246},"grid",[232,447,381],{"class":238},[232,449,450],{"class":246},"gap",[232,452,375],{"class":238},[232,454,455],{"class":246},"0.9",[232,457,94],{"class":392},[232,459,381],{"class":238},[232,461,462],{"class":246},"grid-template-columns",[232,464,375],{"class":238},[232,466,389],{"class":246},[232,468,469],{"class":392},"fr",[232,471,472],{"class":246}," 1",[232,474,469],{"class":392},[232,476,422],{"class":238},[232,478,480],{"class":234,"line":479},13,[232,481,429],{"emptyLinePlaceholder":428},[232,483,485,488,490,493,495,497,499,502,505,507,510,512,514,516,518,520,522,525,527],{"class":234,"line":484},14,[232,486,487],{"class":246},"  .col",[232,489,369],{"class":238},[232,491,492],{"class":246},"border",[232,494,375],{"class":238},[232,496,389],{"class":246},[232,498,405],{"class":392},[232,500,501],{"class":246}," solid",[232,503,504],{"class":246}," #d4d4d8",[232,506,381],{"class":238},[232,508,509],{"class":246},"border-radius",[232,511,375],{"class":238},[232,513,133],{"class":246},[232,515,405],{"class":392},[232,517,381],{"class":238},[232,519,384],{"class":246},[232,521,375],{"class":238},[232,523,524],{"class":246},"0.85",[232,526,94],{"class":392},[232,528,422],{"class":238},[232,530,532,535,537,540,542,545],{"class":234,"line":531},15,[232,533,534],{"class":246},"  .col--good",[232,536,369],{"class":238},[232,538,539],{"class":246},"border-color",[232,541,375],{"class":238},[232,543,544],{"class":246},"#3b6df5",[232,546,422],{"class":238},[232,548,550],{"class":234,"line":549},16,[232,551,429],{"emptyLinePlaceholder":428},[232,553,555,558],{"class":234,"line":554},17,[232,556,557],{"class":246},"  .col__tag",[232,559,560],{"class":238}," {\n",[232,562,564,567,569,571,574,577,579],{"class":234,"line":563},18,[232,565,566],{"class":246},"    margin",[232,568,375],{"class":238},[232,570,378],{"class":246},[232,572,573],{"class":246}," 0",[232,575,576],{"class":246}," 0.4",[232,578,94],{"class":392},[232,580,581],{"class":238},";\n",[232,583,585,588,590,593,595],{"class":234,"line":584},19,[232,586,587],{"class":246},"    font-size",[232,589,375],{"class":238},[232,591,592],{"class":246},"0.72",[232,594,94],{"class":392},[232,596,581],{"class":238},[232,598,600,603,605,608,610],{"class":234,"line":599},20,[232,601,602],{"class":246},"    letter-spacing",[232,604,375],{"class":238},[232,606,607],{"class":246},"0.08",[232,609,53],{"class":392},[232,611,581],{"class":238},[232,613,615,618,620,623],{"class":234,"line":614},21,[232,616,617],{"class":246},"    text-transform",[232,619,375],{"class":238},[232,621,622],{"class":246},"uppercase",[232,624,581],{"class":238},[232,626,628,631,633,636],{"class":234,"line":627},22,[232,629,630],{"class":246},"    opacity",[232,632,375],{"class":238},[232,634,635],{"class":246},"0.7",[232,637,581],{"class":238},[232,639,641],{"class":234,"line":640},23,[232,642,643],{"class":238},"  }\n",[232,645,647],{"class":234,"line":646},24,[232,648,429],{"emptyLinePlaceholder":428},[232,650,652,655],{"class":234,"line":651},25,[232,653,654],{"class":246},"  .col__code",[232,656,560],{"class":238},[232,658,660,663,665,668],{"class":234,"line":659},26,[232,661,662],{"class":246},"    display",[232,664,375],{"class":238},[232,666,667],{"class":246},"block",[232,669,581],{"class":238},[232,671,673,676,678,681,683],{"class":234,"line":672},27,[232,674,675],{"class":246},"    margin-block-start",[232,677,375],{"class":238},[232,679,680],{"class":246},"0.6",[232,682,94],{"class":392},[232,684,581],{"class":238},[232,686,688,691,693,696,698,701],{"class":234,"line":687},28,[232,689,690],{"class":246},"    font-family",[232,692,375],{"class":238},[232,694,695],{"class":246},"ui-monospace",[232,697,416],{"class":238},[232,699,700],{"class":246},"monospace",[232,702,581],{"class":238},[232,704,706,708,710,713,715],{"class":234,"line":705},29,[232,707,587],{"class":246},[232,709,375],{"class":238},[232,711,712],{"class":246},"0.78",[232,714,94],{"class":392},[232,716,581],{"class":238},[232,718,720,722,724,726],{"class":234,"line":719},30,[232,721,630],{"class":246},[232,723,375],{"class":238},[232,725,635],{"class":246},[232,727,581],{"class":238},[232,729,731,734],{"class":234,"line":730},31,[232,732,733],{"class":246},"    overflow-wrap",[232,735,736],{"class":238},": anywhere;\n",[232,738,740],{"class":234,"line":739},32,[232,741,643],{"class":238},[232,743,745],{"class":234,"line":744},33,[232,746,429],{"emptyLinePlaceholder":428},[232,748,750],{"class":234,"line":749},34,[232,751,752],{"class":353},"  \u002F* The trap. The preferred value is pure vw, so between the 1rem floor and\n",[232,754,756],{"class":234,"line":755},35,[232,757,758],{"class":353},"     the 2.5rem ceiling the rendered size is pinned to the layout viewport —\n",[232,760,762],{"class":234,"line":761},36,[232,763,764],{"class":353},"     and the layout viewport shrinks by exactly the zoom factor. *\u002F\n",[232,766,768,771],{"class":234,"line":767},37,[232,769,770],{"class":246},"  .trap-heading",[232,772,560],{"class":238},[232,774,776,778,780,782],{"class":234,"line":775},38,[232,777,566],{"class":246},[232,779,375],{"class":238},[232,781,378],{"class":246},[232,783,581],{"class":238},[232,785,787,790,792,795],{"class":234,"line":786},39,[232,788,789],{"class":246},"    line-height",[232,791,375],{"class":238},[232,793,794],{"class":246},"1.15",[232,796,581],{"class":238},[232,798,800,802,804,807,810,812,814,816,818,820,822,825,827],{"class":234,"line":799},40,[232,801,587],{"class":246},[232,803,375],{"class":238},[232,805,806],{"class":246},"clamp",[232,808,809],{"class":238},"(",[232,811,389],{"class":246},[232,813,94],{"class":392},[232,815,416],{"class":238},[232,817,149],{"class":246},[232,819,20],{"class":392},[232,821,416],{"class":238},[232,823,824],{"class":246},"2.5",[232,826,94],{"class":392},[232,828,829],{"class":238},");\n",[232,831,833],{"class":234,"line":832},41,[232,834,643],{"class":238},[232,836,838],{"class":234,"line":837},42,[232,839,429],{"emptyLinePlaceholder":428},[232,841,843],{"class":234,"line":842},43,[232,844,845],{"class":353},"  \u002F* The fix. The rem term is multiplied by root font size and zoom, so it\n",[232,847,849],{"class":234,"line":848},44,[232,850,851],{"class":353},"     carries the growth; the vw term only supplies the fluid slope. *\u002F\n",[232,853,855,858],{"class":234,"line":854},45,[232,856,857],{"class":246},"  .good-heading",[232,859,560],{"class":238},[232,861,863,865,867,869],{"class":234,"line":862},46,[232,864,566],{"class":246},[232,866,375],{"class":238},[232,868,378],{"class":246},[232,870,581],{"class":238},[232,872,874,876,878,880],{"class":234,"line":873},47,[232,875,789],{"class":246},[232,877,375],{"class":238},[232,879,794],{"class":246},[232,881,581],{"class":238},[232,883,885,887,889,891,893,895,897,899,901,903,907,910,912,914,916,918],{"class":234,"line":884},48,[232,886,587],{"class":246},[232,888,375],{"class":238},[232,890,806],{"class":246},[232,892,809],{"class":238},[232,894,389],{"class":246},[232,896,94],{"class":392},[232,898,416],{"class":238},[232,900,524],{"class":246},[232,902,94],{"class":392},[232,904,906],{"class":905},"soyes"," +",[232,908,909],{"class":246}," 1.2",[232,911,20],{"class":392},[232,913,416],{"class":238},[232,915,824],{"class":246},[232,917,94],{"class":392},[232,919,829],{"class":238},[232,921,923],{"class":234,"line":922},49,[232,924,643],{"class":238},[232,926,928,931,933],{"class":234,"line":927},50,[232,929,930],{"class":238},"\u003C\u002F",[232,932,345],{"class":242},[232,934,250],{"class":238},[232,936,938,940,942],{"class":234,"line":937},51,[232,939,930],{"class":238},[232,941,278],{"class":242},[232,943,250],{"class":238},[232,945,947,949,952],{"class":234,"line":946},52,[232,948,256],{"class":238},[232,950,951],{"class":242},"body",[232,953,250],{"class":238},[232,955,957,960,963,966,968,971],{"class":234,"line":956},53,[232,958,959],{"class":238},"  \u003C",[232,961,962],{"class":242},"div",[232,964,965],{"class":246}," class",[232,967,264],{"class":238},[232,969,970],{"class":267},"\"shell\"",[232,972,250],{"class":238},[232,974,976,979,982,984,986,989],{"class":234,"line":975},54,[232,977,978],{"class":238},"    \u003C",[232,980,981],{"class":242},"section",[232,983,965],{"class":246},[232,985,264],{"class":238},[232,987,988],{"class":267},"\"col col--trap\"",[232,990,250],{"class":238},[232,992,994,997,999,1001,1003,1006,1009,1011],{"class":234,"line":993},55,[232,995,996],{"class":238},"      \u003C",[232,998,14],{"class":242},[232,1000,965],{"class":246},[232,1002,264],{"class":238},[232,1004,1005],{"class":267},"\"col__tag\"",[232,1007,1008],{"class":238},">defeats zoom\u003C\u002F",[232,1010,14],{"class":242},[232,1012,250],{"class":238},[232,1014,1016,1018,1021,1023,1025,1028,1031,1033],{"class":234,"line":1015},56,[232,1017,996],{"class":238},[232,1019,1020],{"class":242},"h3",[232,1022,965],{"class":246},[232,1024,264],{"class":238},[232,1026,1027],{"class":267},"\"trap-heading\"",[232,1029,1030],{"class":238},">Resize me\u003C\u002F",[232,1032,1020],{"class":242},[232,1034,250],{"class":238},[232,1036,1038,1040,1042,1044,1046,1049,1052,1054],{"class":234,"line":1037},57,[232,1039,996],{"class":238},[232,1041,18],{"class":242},[232,1043,965],{"class":246},[232,1045,264],{"class":238},[232,1047,1048],{"class":267},"\"col__code\"",[232,1050,1051],{"class":238},">clamp(1rem, 4vw, 2.5rem)\u003C\u002F",[232,1053,18],{"class":242},[232,1055,250],{"class":238},[232,1057,1059,1062,1064],{"class":234,"line":1058},58,[232,1060,1061],{"class":238},"    \u003C\u002F",[232,1063,981],{"class":242},[232,1065,250],{"class":238},[232,1067,1069,1071,1073,1075,1077,1080],{"class":234,"line":1068},59,[232,1070,978],{"class":238},[232,1072,981],{"class":242},[232,1074,965],{"class":246},[232,1076,264],{"class":238},[232,1078,1079],{"class":267},"\"col col--good\"",[232,1081,250],{"class":238},[232,1083,1085,1087,1089,1091,1093,1095,1098,1100],{"class":234,"line":1084},60,[232,1086,996],{"class":238},[232,1088,14],{"class":242},[232,1090,965],{"class":246},[232,1092,264],{"class":238},[232,1094,1005],{"class":267},[232,1096,1097],{"class":238},">honours zoom\u003C\u002F",[232,1099,14],{"class":242},[232,1101,250],{"class":238},[232,1103,1105,1107,1109,1111,1113,1116,1118,1120],{"class":234,"line":1104},61,[232,1106,996],{"class":238},[232,1108,1020],{"class":242},[232,1110,965],{"class":246},[232,1112,264],{"class":238},[232,1114,1115],{"class":267},"\"good-heading\"",[232,1117,1030],{"class":238},[232,1119,1020],{"class":242},[232,1121,250],{"class":238},[232,1123,1125,1127,1129,1131,1133,1135,1138,1140],{"class":234,"line":1124},62,[232,1126,996],{"class":238},[232,1128,18],{"class":242},[232,1130,965],{"class":246},[232,1132,264],{"class":238},[232,1134,1048],{"class":267},[232,1136,1137],{"class":238},">clamp(1rem, 0.85rem + 1.2vw, 2.5rem)\u003C\u002F",[232,1139,18],{"class":242},[232,1141,250],{"class":238},[232,1143,1145,1147,1149],{"class":234,"line":1144},63,[232,1146,1061],{"class":238},[232,1148,981],{"class":242},[232,1150,250],{"class":238},[232,1152,1154,1157,1159],{"class":234,"line":1153},64,[232,1155,1156],{"class":238},"  \u003C\u002F",[232,1158,962],{"class":242},[232,1160,250],{"class":238},[232,1162,1164,1166,1168],{"class":234,"line":1163},65,[232,1165,930],{"class":238},[232,1167,951],{"class":242},[232,1169,250],{"class":238},[232,1171,1173,1175,1177],{"class":234,"line":1172},66,[232,1174,930],{"class":238},[232,1176,227],{"class":242},[232,1178,250],{"class":238},[202,1180],{},[42,1182,1184],{"id":1183},"key-technique-split-the-preferred-value","Key technique: split the preferred value",[14,1186,1187,1190,1191,1194,1195,1198,1199,1201,1202,1204],{},[18,1188,1189],{},"clamp(MIN, PREFERRED, MAX)"," returns ",[18,1192,1193],{},"MAX"," when the preferred value exceeds it, ",[18,1196,1197],{},"MIN"," when it falls below, and the preferred value the rest of the time. That middle band is where users spend nearly all of their reading, so it is the only part of the expression that matters for this criterion. Bounds expressed in ",[18,1200,94],{}," do not save a preferred value expressed in ",[18,1203,20],{},"; they only take over once you are outside the fluid range entirely, at which point the type has stopped being fluid.",[14,1206,1207,1208,1215,1216,1219,1220,1223,1224,1226,1227,1229],{},"So the rule is narrow and mechanical: ",[1209,1210,1211,1212,1214],"strong",{},"the preferred value must be a sum containing a ",[18,1213,94],{}," term."," Write ",[18,1217,1218],{},"Arem + Bvw",", never ",[18,1221,1222],{},"Bvw"," alone. The ",[18,1225,94],{}," term is the part zoom and root-font-size preferences can multiply; the ",[18,1228,20],{}," term supplies the slope that makes the type fluid at a fixed zoom level.",[14,1231,1232,1233,1235,1236,1238],{},"Deriving the pair for a target range is straightforward. To go from ",[18,1234,87],{}," at a 320px viewport to ",[18,1237,216],{}," at a 1280px viewport, with a 16px root:",[1240,1241,1242,1253,1262],"ul",{},[1243,1244,1245,1246,1249,1250,40],"li",{},"The slope is ",[18,1247,1248],{},"(40px − 16px) \u002F (1280px − 320px) = 0.025",", expressed as ",[18,1251,1252],{},"2.5vw",[1243,1254,1255,1256,1249,1259,40],{},"The intercept is ",[18,1257,1258],{},"16px − (0.025 × 320px) = 8px",[18,1260,1261],{},"0.5rem",[1243,1263,1264,1265,40],{},"The result is ",[18,1266,1267],{},"clamp(1rem, 0.5rem + 2.5vw, 2.5rem)",[14,1269,1270,1271,1273,1274,1276],{},"The diagnostic for an existing scale: at your median viewport width, work out how much of the computed size comes from the ",[18,1272,94],{}," term. If that share is near zero, the scale is fluid but not resizable. Raising the intercept and lowering the ",[18,1275,20],{}," coefficient — a flatter slope with a bigger base — trades a little responsiveness for a lot of zoom headroom, and is almost always the right trade.",[14,1278,1279,1280,1283,1284,1286,1287,1290,1291,1293,1294,1297,1298,1301,1302,1304],{},"Two related habits belong here. Never set ",[18,1281,1282],{},"font-size"," in ",[18,1285,405],{}," or ",[18,1288,1289],{},"%"," on the root element, because that overrides the user's browser font-size setting and breaks ",[18,1292,94],{}," at its source. And keep the ratio between your ",[18,1295,1296],{},"min"," and ",[18,1299,1300],{},"max"," modest; a heading that triples between narrow and wide viewports has a huge fluid band to defend, which makes the ",[18,1303,94],{}," share hard to keep high.",[202,1306],{},[42,1308,1310],{"id":1309},"how-to-test-it","How to test it",[14,1312,1313],{},"Four checks, in increasing order of how much they catch:",[1315,1316,1317,1329,1338,1350],"ol",{},[1243,1318,1319,1322,1323,1326,1327,40],{},[1209,1320,1321],{},"Full-page zoom to 200%."," In Chrome, Edge, Safari or Firefox, press Ctrl\u002FCmd and ",[18,1324,1325],{},"+"," until the indicator reads 200%. Text sized correctly roughly doubles. Compare against a paragraph you know is set in plain ",[18,1328,94],{},[1243,1330,1331,1334,1335,1337],{},[1209,1332,1333],{},"Text-only zoom."," In Firefox, enable View → Zoom → Zoom Text Only, then zoom. This isolates the failure mode that full-page zoom hides: nothing in the layout changes, so anything anchored to ",[18,1336,20],{}," does not move at all.",[1243,1339,1340,1343,1344,1346,1347,1349],{},[1209,1341,1342],{},"Root font size override."," In your browser's appearance settings, set the default font size to a large value. ",[18,1345,94],{},"-based text follows it; ",[18,1348,20],{},"-based text ignores it entirely. This is the setting a large number of real users actually change.",[1243,1351,1352,1355,1356,1358],{},[1209,1353,1354],{},"Computed value inspection."," Select the element in DevTools and read the Computed panel's resolved ",[18,1357,1282],{}," in pixels at 100% and at 200% zoom. This gives you the actual ratio rather than an impression, and it is the fastest way to audit a whole scale — walk the headings, record both numbers, and flag anything whose ratio is under about 1.8.",[14,1360,1361],{},"Check 1.4.4 together with 1.4.10 Reflow, which requires content to work at a 320 CSS-pixel-wide viewport without two-dimensional scrolling. They interact: fixing 1.4.4 by making text much larger can break 1.4.10 if the layout was not already fluid, and container-driven components help precisely because they respond to the box rather than to a viewport measurement that zoom has already distorted.",[202,1363],{},[42,1365,1367],{"id":1366},"variation-keeping-spacing-in-step","Variation: keeping spacing in step",[14,1369,1370],{},"Type that doubles inside a spacing scale that does not ends up cramped — lines nearly touching their containers, headings crowding the paragraphs beneath them. Apply the same split to the space scale so the rhythm scales with the type:",[223,1372,1376],{"className":1373,"code":1374,"language":1375,"meta":228,"style":228},"language-css shiki shiki-themes github-light-high-contrast github-dark-high-contrast",":root {\n  --space-s: clamp(0.75rem, 0.7rem + 0.25vw, 1rem);\n  --space-m: clamp(1.25rem, 1.1rem + 0.6vw, 2rem);\n}\n\n.prose > * + * { margin-block-start: var(--space-m); }\n\n\u002F* Line length must be bounded in a font-relative unit too. A max-width in\n   px keeps the measure constant in CSS pixels, so zoomed text wraps at the\n   same character count instead of gaining the shorter, easier line the\n   user asked for. The ch unit is font-relative and reads well here. *\u002F\n.prose { max-inline-size: 66ch; }\n","css",[18,1377,1378,1385,1422,1461,1466,1470,1503,1507,1512,1517,1522,1527],{"__ignoreMap":228},[232,1379,1380,1383],{"class":234,"line":235},[232,1381,1382],{"class":246},":root",[232,1384,560],{"class":238},[232,1386,1387,1390,1392,1394,1396,1399,1401,1403,1405,1407,1409,1412,1414,1416,1418,1420],{"class":234,"line":253},[232,1388,1389],{"class":905},"  --space-s",[232,1391,375],{"class":238},[232,1393,806],{"class":246},[232,1395,809],{"class":238},[232,1397,1398],{"class":246},"0.75",[232,1400,94],{"class":392},[232,1402,416],{"class":238},[232,1404,635],{"class":246},[232,1406,94],{"class":392},[232,1408,906],{"class":905},[232,1410,1411],{"class":246}," 0.25",[232,1413,20],{"class":392},[232,1415,416],{"class":238},[232,1417,389],{"class":246},[232,1419,94],{"class":392},[232,1421,829],{"class":238},[232,1423,1424,1427,1429,1431,1433,1436,1438,1440,1443,1445,1447,1450,1452,1454,1457,1459],{"class":234,"line":273},[232,1425,1426],{"class":905},"  --space-m",[232,1428,375],{"class":238},[232,1430,806],{"class":246},[232,1432,809],{"class":238},[232,1434,1435],{"class":246},"1.25",[232,1437,94],{"class":392},[232,1439,416],{"class":238},[232,1441,1442],{"class":246},"1.1",[232,1444,94],{"class":392},[232,1446,906],{"class":905},[232,1448,1449],{"class":246}," 0.6",[232,1451,20],{"class":392},[232,1453,416],{"class":238},[232,1455,1456],{"class":246},"2",[232,1458,94],{"class":392},[232,1460,829],{"class":238},[232,1462,1463],{"class":234,"line":283},[232,1464,1465],{"class":238},"}\n",[232,1467,1468],{"class":234,"line":301},[232,1469,429],{"emptyLinePlaceholder":428},[232,1471,1472,1475,1478,1481,1483,1485,1487,1490,1492,1495,1497,1500],{"class":234,"line":326},[232,1473,1474],{"class":246},".prose",[232,1476,1477],{"class":392}," >",[232,1479,1480],{"class":242}," *",[232,1482,906],{"class":392},[232,1484,1480],{"class":242},[232,1486,369],{"class":238},[232,1488,1489],{"class":246},"margin-block-start",[232,1491,375],{"class":238},[232,1493,1494],{"class":246},"var",[232,1496,809],{"class":238},[232,1498,1499],{"class":905},"--space-m",[232,1501,1502],{"class":238},"); }\n",[232,1504,1505],{"class":234,"line":340},[232,1506,429],{"emptyLinePlaceholder":428},[232,1508,1509],{"class":234,"line":350},[232,1510,1511],{"class":353},"\u002F* Line length must be bounded in a font-relative unit too. A max-width in\n",[232,1513,1514],{"class":234,"line":357},[232,1515,1516],{"class":353},"   px keeps the measure constant in CSS pixels, so zoomed text wraps at the\n",[232,1518,1519],{"class":234,"line":363},[232,1520,1521],{"class":353},"   same character count instead of gaining the shorter, easier line the\n",[232,1523,1524],{"class":234,"line":425},[232,1525,1526],{"class":353},"   user asked for. The ch unit is font-relative and reads well here. *\u002F\n",[232,1528,1529,1531,1533,1536,1538,1541,1544],{"class":234,"line":432},[232,1530,1474],{"class":246},[232,1532,369],{"class":238},[232,1534,1535],{"class":246},"max-inline-size",[232,1537,375],{"class":238},[232,1539,1540],{"class":246},"66",[232,1542,1543],{"class":392},"ch",[232,1545,422],{"class":238},[14,1547,1548,1549,1551,1552,1554,1555,1557,1558,1562,1563,40],{},"The ",[18,1550,1543],{}," unit is derived from the font's ",[18,1553,378],{}," glyph, so it scales with ",[18,1556,1282],{}," and therefore with zoom. The full treatment of this pattern is in ",[26,1559,1561],{"href":1560},"\u002Fmastering-container-queries-responsive-layouts\u002Ffluid-typography-with-clamp\u002Ffluid-space-scale-with-clamp\u002F","fluid space scale with clamp()",", and the decision of when to use a fluid scale at all rather than discrete steps is weighed in ",[26,1564,1566],{"href":1565},"\u002Fmastering-container-queries-responsive-layouts\u002Ffluid-typography-with-clamp\u002Fclamp-vs-media-query-typography\u002F","clamp() vs media-query typography",[202,1568],{},[42,1570,1572],{"id":1571},"browser-support","Browser support",[14,1574,1575,1578],{},[18,1576,1577],{},"clamp()"," is universally available — it shipped across all four engines years ago — so no guard is needed for the syntax. The zoom behaviour described here is not a bug or a vendor quirk — it follows from the CSS Values specification's definition of viewport-percentage units against the layout viewport, and every current engine behaves the same way.",[14,1580,1581,1582,1325,1585,1587,1588,1590,1591,1593],{},"Text-only zoom availability does differ. Firefox exposes it directly in the View menu; Chrome and Edge expose a page-level font-size setting rather than a per-page text zoom; Safari's ",[18,1583,1584],{},"Cmd",[18,1586,1325],{}," is full-page zoom, with text-only behaviour available through the Accessibility preferences. Mobile operating systems apply their own text scaling on top of all of this. Since none of these mechanisms touch ",[18,1589,20],{},", one scale written with a ",[18,1592,94],{}," term satisfies all of them at once.",[202,1595],{},[42,1597,1599],{"id":1598},"faq","FAQ",[14,1601,1602,1608,1609,1611],{},[1209,1603,1604,1605,1607],{},"Why does browser zoom not enlarge text sized in ",[18,1606,20],{}," units?","\nZooming shrinks the CSS layout viewport by the zoom factor, so ",[18,1610,78],{}," shrinks by the same factor and then gets scaled back up by it. The two cancel out and the rendered size stays roughly constant while everything around it grows.",[14,1613,1614,1620,1621,1623],{},[1209,1615,1616,1617,1619],{},"How much of the preferred value has to be in ",[18,1618,94],{},"?","\nEnough that the text still grows substantially under zoom. A practical target is for the ",[18,1622,94],{}," term to supply at least half the computed size at your most common viewport width, which the AAA guidance for text spacing and resize comfortably accommodates.",[14,1625,1626,1633,1634,1636,1637,1639,1640,1642],{},[1209,1627,1628,1629,1632],{},"Do container query units like ",[18,1630,1631],{},"cqi"," have the same zoom problem?","\nYes. Container units are a percentage of a container measured in CSS pixels, and that container shrinks under zoom exactly as the viewport does. Mix ",[18,1635,1631],{}," with a ",[18,1638,94],{}," term the same way you mix ",[18,1641,20],{}," with one.",[14,1644,1645,1654,1655,1657,1658,1214],{},[1209,1646,1647,1648,1650,1651,1653],{},"Does clamping the maximum in ",[18,1649,94],{}," fix a ",[18,1652,20],{},"-only preferred value?","\nOnly above the point where the maximum takes over. Between the two bounds the ",[18,1656,20],{}," term is what is being used, so that whole range still resists zoom. The preferred value itself needs the ",[18,1659,94],{},[42,1661,1663],{"id":1662},"related","Related",[1240,1665,1666,1672,1678,1684,1689],{},[1243,1667,1668,1671],{},[26,1669,1670],{"href":38},"Fluid Typography Without JavaScript"," — deriving the slope and intercept this page then audits.",[1243,1673,1674,1677],{},[26,1675,1676],{"href":1565},"clamp() vs Media-Query Typography"," — when a fluid band is worth defending at all.",[1243,1679,1680,1683],{},[26,1681,1682],{"href":1560},"Fluid Space Scale with clamp()"," — spacing that grows with the type.",[1243,1685,1686,1688],{},[26,1687,29],{"href":28}," — the parent guide for clamp-based sizing.",[1243,1690,1691,1695],{},[26,1692,1694],{"href":1693},"\u002Fcss-only-micro-interactions-animations\u002Faccessibility-in-css-animations\u002Fwcag-motion-success-criteria\u002F","WCAG Motion Success Criteria"," — the equivalent criterion-by-criterion read for animation.",[345,1697,1698],{},"html pre.shiki code .suds8, html code.shiki .suds8{--shiki-default:#0E1116;--shiki-dark:#F0F3F6}html pre.shiki code .sne4z, html code.shiki .sne4z{--shiki-default:#024C1A;--shiki-dark:#72F088}html pre.shiki code .s-5SL, html code.shiki .s-5SL{--shiki-default:#023B95;--shiki-dark:#91CBFF}html pre.shiki code .sT6z2, html code.shiki .sT6z2{--shiki-default:#032563;--shiki-dark:#ADDCFF}html pre.shiki code .sLBg1, html code.shiki .sLBg1{--shiki-default:#66707B;--shiki-dark:#BDC4CC}html pre.shiki code .sHUrx, html code.shiki .sHUrx{--shiki-default:#A0111F;--shiki-dark:#FF9492}html pre.shiki code .soyes, html code.shiki .soyes{--shiki-default:#702C00;--shiki-dark:#FFB757}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":228,"searchDepth":253,"depth":253,"links":1700},[1701,1702,1703,1704,1705,1706,1707,1708,1709],{"id":44,"depth":253,"text":45},{"id":69,"depth":253,"text":70},{"id":206,"depth":253,"text":207},{"id":1183,"depth":253,"text":1184},{"id":1309,"depth":253,"text":1310},{"id":1366,"depth":253,"text":1367},{"id":1571,"depth":253,"text":1572},{"id":1598,"depth":253,"text":1599},{"id":1662,"depth":253,"text":1663},"A clamp() scale written purely in vw units silently defeats browser zoom and fails WCAG 1.4.4. Add a rem term to the preferred value, and learn how to test it.","md",{"pageTitle":1713,"datePublished":1714,"dateModified":1714,"faq":1715},"Fluid Type, Accessibility and Zoom","2026-07-20",[1716,1719,1722,1725],{"q":1717,"a":1718},"Why does browser zoom not enlarge text sized in vw units?","Zooming shrinks the CSS layout viewport by the zoom factor, so 1vw shrinks by the same factor and then gets scaled back up by it. The two cancel out and the rendered size stays roughly constant while everything around it grows.",{"q":1720,"a":1721},"How much of the preferred value has to be in rem?","Enough that the text still grows substantially under zoom. A practical target is for the rem term to supply at least half the computed size at your most common viewport width, which the AAA guidance for text spacing and resize comfortably accommodates.",{"q":1723,"a":1724},"Do container query units like cqi have the same zoom problem?","Yes. Container units are a percentage of a container measured in CSS pixels, and that container shrinks under zoom exactly as the viewport does. Mix cqi with a rem term the same way you mix vw with one.",{"q":1726,"a":1727},"Does clamping the maximum in rem fix a vw-only preferred value?","Only above the point where the maximum takes over. Between the two bounds the vw term is what is being used, so that whole range still resists zoom. The preferred value itself needs the rem term.","\u002Fmastering-container-queries-responsive-layouts\u002Ffluid-typography-with-clamp\u002Ffluid-type-accessibility-and-zoom",{"title":5,"description":1710},"mastering-container-queries-responsive-layouts\u002Ffluid-typography-with-clamp\u002Ffluid-type-accessibility-and-zoom\u002Findex","RaQLaI1r5Nvk9RIE0F0ZeBivm6405xMuLdH1c7Ift1g",[1733,1736,1739,1742,1745,1748,1751,1754,1757,1760,1763,1766,1769,1772,1775,1778,1781,1784,1787,1790,1793,1796,1799,1802,1805,1808,1811,1814,1817,1820,1823,1826,1829,1832,1835,1838,1841,1844,1847,1850,1853,1856,1859,1862,1865,1868,1871,1873,1876,1879,1882,1885,1888,1891,1894,1897,1900,1903,1906,1909,1912,1915,1918,1921,1924,1927,1930,1933,1934,1937,1940,1942,1945,1948,1951,1954,1957,1960,1963,1966,1969,1972,1975,1978,1981,1984,1987,1990],{"path":1734,"title":1735},"\u002Fcss-only-micro-interactions-animations\u002Faccessibility-in-css-animations\u002Fcreating-accessible-focus-indicators","Creating Accessible Focus Indicators: Building a Ring That Survives Any Background",{"path":1737,"title":1738},"\u002Fcss-only-micro-interactions-animations\u002Faccessibility-in-css-animations","Accessibility in CSS Animations: Patterns, Specs & Best Practices",{"path":1740,"title":1741},"\u002Fcss-only-micro-interactions-animations\u002Faccessibility-in-css-animations\u002Fprefers-reduced-motion-recipes","prefers-reduced-motion Recipes: A Reduction for Every Common Effect",{"path":1743,"title":1744},"\u002Fcss-only-micro-interactions-animations\u002Faccessibility-in-css-animations\u002Freducing-motion-preferences-in-css","Reducing Motion Preferences in CSS: How the Setting Becomes a Media Feature",{"path":1746,"title":1747},"\u002Fcss-only-micro-interactions-animations\u002Faccessibility-in-css-animations\u002Ftarget-size-and-pointer-accessibility","Target Size and Pointer Accessibility: Meeting 2.5.8 Without Redesigning",{"path":1749,"title":1750},"\u002Fcss-only-micro-interactions-animations\u002Faccessibility-in-css-animations\u002Fvestibular-safe-animation-patterns","Vestibular-Safe Animation Patterns: Why Some Motion Makes People Sick",{"path":1752,"title":1753},"\u002Fcss-only-micro-interactions-animations\u002Faccessibility-in-css-animations\u002Fwcag-motion-success-criteria","WCAG Motion Success Criteria: What Each One Actually Requires",{"path":1755,"title":1756},"\u002Fcss-only-micro-interactions-animations\u002Fcontainer-aware-motion\u002Fcontainer-query-hover-affordances","Container Query Hover Affordances: When Narrow Means Persistent, Not Hidden",{"path":1758,"title":1759},"\u002Fcss-only-micro-interactions-animations\u002Fcontainer-aware-motion","Container-Aware Motion: Animation Sized by the Space a Component Actually Has",{"path":1761,"title":1762},"\u002Fcss-only-micro-interactions-animations\u002Fcontainer-aware-motion\u002Fmotion-that-scales-with-container-size","Motion That Scales with Container Size: cqi, cqb, and Derived Durations",{"path":1764,"title":1765},"\u002Fcss-only-micro-interactions-animations\u002Fcontainer-aware-motion\u002Fsuppressing-motion-in-small-containers","Suppressing Motion in Small Containers: Deliberately Animating Nothing",{"path":1767,"title":1768},"\u002Fcss-only-micro-interactions-animations\u002Fcss-animation-vs-web-animations-api","CSS Animation vs the Web Animations API: A Decision Guide",{"path":1770,"title":1771},"\u002Fcss-only-micro-interactions-animations\u002Fcss-animation-vs-web-animations-api\u002Fwhen-to-use-javascript-animation","When to Use JavaScript Instead of CSS for Animation",{"path":1773,"title":1774},"\u002Fcss-only-micro-interactions-animations\u002Fcss-custom-properties-architecture\u002Fanimating-custom-properties-with-at-property","Animating Custom Properties with @property: Typed Variables That Interpolate",{"path":1776,"title":1777},"\u002Fcss-only-micro-interactions-animations\u002Fcss-custom-properties-architecture\u002Ffluid-spacing-tokens-driving-transition-durations","Fluid Spacing Tokens That Drive Transition Durations",{"path":1779,"title":1780},"\u002Fcss-only-micro-interactions-animations\u002Fcss-custom-properties-architecture","CSS Custom Properties Architecture: Scalable Design Systems & Dynamic UI",{"path":1782,"title":1783},"\u002Fcss-only-micro-interactions-animations\u002Fcss-custom-properties-architecture\u002Fregistered-properties-and-type-safety","Registered Properties and Type Safety: @property as a Contract for Design Tokens",{"path":1785,"title":1786},"\u002Fcss-only-micro-interactions-animations\u002Fcss-transition-fundamentals\u002Fcss-transition-timing-functions","CSS Transition Timing Functions: ease, cubic-bezier(), steps() and linear()",{"path":1788,"title":1789},"\u002Fcss-only-micro-interactions-animations\u002Fcss-transition-fundamentals","CSS Transition Fundamentals: Architecture, Performance & Patterns",{"path":1791,"title":1792},"\u002Fcss-only-micro-interactions-animations\u002Fcss-transition-fundamentals\u002Fstarting-style-entry-animations","@starting-style: Animating an Element's Very First Style Change",{"path":1794,"title":1795},"\u002Fcss-only-micro-interactions-animations\u002Fcss-transition-fundamentals\u002Ftransitioning-display-with-allow-discrete","Transitioning display with transition-behavior: allow-discrete and @starting-style",{"path":1797,"title":1798},"\u002Fcss-only-micro-interactions-animations\u002Fhover-focus-state-design\u002Faccessible-css-only-tooltips","Accessible CSS-Only Tooltips and Hover Cards With  and ",{"path":1800,"title":1801},"\u002Fcss-only-micro-interactions-animations\u002Fhover-focus-state-design\u002Ffocus-visible-vs-focus-polyfill-alternatives"," vs :focus: Why It Replaced the Focus-Ring Polyfill",{"path":1803,"title":1804},"\u002Fcss-only-micro-interactions-animations\u002Fhover-focus-state-design\u002Ffocus-within-form-patterns"," Form Patterns: Styling a Group From the Field Inside It",{"path":1806,"title":1807},"\u002Fcss-only-micro-interactions-animations\u002Fhover-focus-state-design","Hover & Focus State Design: Spec-Compliant Patterns for Modern UIs",{"path":1809,"title":1810},"\u002Fcss-only-micro-interactions-animations\u002Fhover-focus-state-design\u002Fpointer-and-hover-media-queries","Pointer and Hover Media Queries: Gating Affordances by Input Capability",{"path":1812,"title":1813},"\u002Fcss-only-micro-interactions-animations\u002Fhover-focus-state-design\u002Fsmooth-hover-effects-without-javascript","Smooth Hover Effects Without JavaScript: CSS-Only Patterns for Modern UI",{"path":1815,"title":1816},"\u002Fcss-only-micro-interactions-animations","CSS-Only Micro-Interactions & Animations: Architecture, Performance & Implementation",{"path":1818,"title":1819},"\u002Fcss-only-micro-interactions-animations\u002Fkeyframe-animation-patterns\u002Fcontainer-query-triggered-keyframe-animations","Container-Query-Triggered Keyframe Animations: Animate Differently per Available Space",{"path":1821,"title":1822},"\u002Fcss-only-micro-interactions-animations\u002Fkeyframe-animation-patterns\u002Fcss-only-accordions-and-disclosure","CSS-Only Accordions and Disclosure Widgets with details and summary",{"path":1824,"title":1825},"\u002Fcss-only-micro-interactions-animations\u002Fkeyframe-animation-patterns\u002Fcss-only-loading-spinners-and-skeletons","CSS-Only Loading Spinners and Skeleton Shimmer Screens with @keyframes",{"path":1827,"title":1828},"\u002Fcss-only-micro-interactions-animations\u002Fkeyframe-animation-patterns\u002Fcss-only-toggle-switches-and-checkboxes","CSS-Only Toggle Switches and Checkboxes That Keep the Native Input",{"path":1830,"title":1831},"\u002Fcss-only-micro-interactions-animations\u002Fkeyframe-animation-patterns","Keyframe Animation Patterns: Spec-Compliant Architectures for Modern UIs",{"path":1833,"title":1834},"\u002Fcss-only-micro-interactions-animations\u002Fkeyframe-animation-patterns\u002Fstaggered-list-animations-with-custom-properties","Staggered List Animations Using --i Index Custom Properties",{"path":1836,"title":1837},"\u002Fcss-only-micro-interactions-animations\u002Fperformance-gpu-acceleration","Performance & GPU Acceleration in CSS: A Developer’s Blueprint",{"path":1839,"title":1840},"\u002Fcss-only-micro-interactions-animations\u002Fperformance-gpu-acceleration\u002Foptimizing-css-animations-for-60fps","Optimizing CSS Animations for 60fps: Budgeting the Frame",{"path":1842,"title":1843},"\u002Fcss-only-micro-interactions-animations\u002Fperformance-gpu-acceleration\u002Fprofiling-animations-in-devtools","Profiling CSS Animations in DevTools: Finding the Frame That Costs Too Much",{"path":1845,"title":1846},"\u002Fcss-only-micro-interactions-animations\u002Fperformance-gpu-acceleration\u002Fwill-change-and-the-compositor-thread","will-change and the Compositor Thread: Layer Promotion Without the Footguns",{"path":1848,"title":1849},"\u002Fcss-only-micro-interactions-animations\u002Fscroll-driven-animations","Scroll-Driven Animations: Binding Motion to Scroll Position in Pure CSS",{"path":1851,"title":1852},"\u002Fcss-only-micro-interactions-animations\u002Fscroll-driven-animations\u002Fscroll-driven-animation-fallbacks","Shipping Scroll-Driven Animations Without Breaking Anything",{"path":1854,"title":1855},"\u002Fcss-only-micro-interactions-animations\u002Fscroll-driven-animations\u002Fscroll-progress-bar-without-javascript","A Reading Progress Bar Driven by scroll(root)",{"path":1857,"title":1858},"\u002Fcss-only-micro-interactions-animations\u002Fscroll-driven-animations\u002Fscroll-triggered-reveal-animations","Reveal-on-Scroll with view() and animation-range",{"path":1860,"title":1861},"\u002Fcss-only-micro-interactions-animations\u002Fview-transitions-for-css-developers","View Transitions for CSS Developers: the Pseudo-Element Tree, Snapshots, and Named Elements",{"path":1863,"title":1864},"\u002Fcss-only-micro-interactions-animations\u002Fview-transitions-for-css-developers\u002Fsame-document-view-transitions","Writing a Same-Document View Transition: the CSS You Actually Author",{"path":1866,"title":1867},"\u002Fcss-only-micro-interactions-animations\u002Fview-transitions-for-css-developers\u002Fview-transition-names-shared-elements","view-transition-name and Shared Elements: Morphing a Thumbnail into a Hero",{"path":1869,"title":1870},"\u002Fcss-only-micro-interactions-animations\u002Fview-transitions-for-css-developers\u002Fview-transitions-with-reduced-motion","Honouring prefers-reduced-motion in View Transitions",{"path":408,"title":1872},"Modern CSS Layouts & Micro-Interactions",{"path":1874,"title":1875},"\u002Fmastering-container-queries-responsive-layouts\u002Fanchor-positioning-and-overlays\u002Fanchor-positioned-tooltips","Anchor-Positioned Tooltips: Tethering, Flipping, and Getting the Semantics Right",{"path":1877,"title":1878},"\u002Fmastering-container-queries-responsive-layouts\u002Fanchor-positioning-and-overlays\u002Fanchor-positioning-fallbacks","Anchor Positioning Fallbacks: A Graceful Floor for Older Browser Versions",{"path":1880,"title":1881},"\u002Fmastering-container-queries-responsive-layouts\u002Fanchor-positioning-and-overlays","CSS Anchor Positioning and Overlays: Tethering Elements Without JavaScript",{"path":1883,"title":1884},"\u002Fmastering-container-queries-responsive-layouts\u002Fanchor-positioning-and-overlays\u002Fpopover-attribute-and-css-styling","The popover Attribute: Top-Layer Overlays With No JavaScript",{"path":1886,"title":1887},"\u002Fmastering-container-queries-responsive-layouts\u002Fcontainer-query-fallbacks\u002Ffeature-detection-with-supports","Feature Detection with @supports: Syntax, Semantics, and the Traps",{"path":1889,"title":1890},"\u002Fmastering-container-queries-responsive-layouts\u002Fcontainer-query-fallbacks\u002Fhandling-container-query-fallbacks-for-older-browsers","Handling Container Query Fallbacks for Older Browsers",{"path":1892,"title":1893},"\u002Fmastering-container-queries-responsive-layouts\u002Fcontainer-query-fallbacks","Container Query Fallbacks: Spec-Compliant CSS Strategies for Legacy Browsers",{"path":1895,"title":1896},"\u002Fmastering-container-queries-responsive-layouts\u002Fcontainer-query-syntax-basics\u002Fcontainer-query-units-cqi-cqb-explained","Container Query Units Explained: cqi, cqb, cqw, cqh, cqmin and cqmax",{"path":1898,"title":1899},"\u002Fmastering-container-queries-responsive-layouts\u002Fcontainer-query-syntax-basics\u002Fcontainer-type-size-vs-inline-size","Choosing container-type: inline-size, size, or normal",{"path":1901,"title":1902},"\u002Fmastering-container-queries-responsive-layouts\u002Fcontainer-query-syntax-basics\u002Fcontainer-vs-media-queries-comparison","Container vs Media Queries: Choosing the Right Reference Box",{"path":1904,"title":1905},"\u002Fmastering-container-queries-responsive-layouts\u002Fcontainer-query-syntax-basics\u002Fhow-to-use-container-queries-in-production","How to Use Container Queries in Production Without a Rewrite",{"path":1907,"title":1908},"\u002Fmastering-container-queries-responsive-layouts\u002Fcontainer-query-syntax-basics","Container Query Syntax Basics",{"path":1910,"title":1911},"\u002Fmastering-container-queries-responsive-layouts\u002Fcontainer-query-syntax-basics\u002Fnesting-and-naming-container-queries","Nesting and Naming Container Queries: Targeting the Right Ancestor",{"path":1913,"title":1914},"\u002Fmastering-container-queries-responsive-layouts\u002Fcss-grid-and-subgrid-layouts\u002Fauto-fit-minmax-responsive-grids","Responsive Grids with repeat(auto-fit, minmax())",{"path":1916,"title":1917},"\u002Fmastering-container-queries-responsive-layouts\u002Fcss-grid-and-subgrid-layouts\u002Fbuilding-a-dashboard-with-subgrid","Building a Responsive Dashboard Where Cards Align with Subgrid",{"path":1919,"title":1920},"\u002Fmastering-container-queries-responsive-layouts\u002Fcss-grid-and-subgrid-layouts\u002Fholy-grail-layout-with-grid","The Holy-Grail Layout with CSS Grid and Container Queries",{"path":1922,"title":1923},"\u002Fmastering-container-queries-responsive-layouts\u002Fcss-grid-and-subgrid-layouts","CSS Grid and Subgrid Layouts for Responsive Interfaces",{"path":1925,"title":1926},"\u002Fmastering-container-queries-responsive-layouts\u002Fcss-grid-and-subgrid-layouts\u002Fsubgrid-vs-nested-grid","Subgrid vs an Independently Nested Grid",{"path":1928,"title":1929},"\u002Fmastering-container-queries-responsive-layouts\u002Ffluid-typography-with-clamp\u002Fclamp-vs-media-query-typography","clamp() Fluid Type vs Media-Query Step Typography: Tradeoffs and Anatomy",{"path":1931,"title":1932},"\u002Fmastering-container-queries-responsive-layouts\u002Ffluid-typography-with-clamp\u002Ffluid-space-scale-with-clamp","Building a Fluid Spacing Scale with clamp() and Container Units",{"path":1728,"title":5},{"path":1935,"title":1936},"\u002Fmastering-container-queries-responsive-layouts\u002Ffluid-typography-with-clamp\u002Ffluid-typography-without-javascript","Fluid Typography Without JavaScript: A Precision CSS Reference",{"path":1938,"title":1939},"\u002Fmastering-container-queries-responsive-layouts\u002Ffluid-typography-with-clamp","Fluid Typography with clamp(): A Practical Guide for Modern CSS",{"path":1941,"title":34},"\u002Fmastering-container-queries-responsive-layouts",{"path":1943,"title":1944},"\u002Fmastering-container-queries-responsive-layouts\u002Fintrinsic-sizing-techniques\u002Faspect-ratio-for-responsive-media","Using aspect-ratio for Responsive Media and Preventing Layout Shift",{"path":1946,"title":1947},"\u002Fmastering-container-queries-responsive-layouts\u002Fintrinsic-sizing-techniques","Intrinsic Sizing Techniques: Modern CSS Layouts for Responsive UI",{"path":1949,"title":1950},"\u002Fmastering-container-queries-responsive-layouts\u002Fintrinsic-sizing-techniques\u002Fmin-max-fit-content-explained","min-content, max-content, and fit-content() Explained",{"path":1952,"title":1953},"\u002Fmastering-container-queries-responsive-layouts\u002Fintrinsic-sizing-techniques\u002Fpreventing-flex-and-grid-overflow","The min-width: auto Trap in Flex and Grid Layouts",{"path":1955,"title":1956},"\u002Fmastering-container-queries-responsive-layouts\u002Fmodern-css-reset-strategies\u002Fcascade-layers-for-reset-and-tokens","Cascade Layers for Reset and Design Tokens",{"path":1958,"title":1959},"\u002Fmastering-container-queries-responsive-layouts\u002Fmodern-css-reset-strategies\u002Fcascade-layers-vs-specificity-hacks","Cascade Layers vs Specificity Hacks: Ordering Instead of Escalating",{"path":1961,"title":1962},"\u002Fmastering-container-queries-responsive-layouts\u002Fmodern-css-reset-strategies","Modern CSS Reset Strategies: A Spec-Compliant Foundation",{"path":1964,"title":1965},"\u002Fmastering-container-queries-responsive-layouts\u002Fresponsive-component-patterns\u002Fbuilding-responsive-cards-with-container-queries","Building Responsive Cards with Container Queries: A Production-Ready Guide",{"path":1967,"title":1968},"\u002Fmastering-container-queries-responsive-layouts\u002Fresponsive-component-patterns\u002Fcontainer-query-data-tables","Container Query Data Tables: Reflowing Tables to Cards",{"path":1970,"title":1971},"\u002Fmastering-container-queries-responsive-layouts\u002Fresponsive-component-patterns\u002Fcontainer-query-sidebar-layouts","Container Query Sidebar Layouts That Travel Between Contexts",{"path":1973,"title":1974},"\u002Fmastering-container-queries-responsive-layouts\u002Fresponsive-component-patterns","Responsive Component Patterns: Architecture & Implementation",{"path":1976,"title":1977},"\u002Fmastering-container-queries-responsive-layouts\u002Fresponsive-component-patterns\u002Fresponsive-forms-with-container-queries","Responsive Forms with Container Queries: Reflow Without Losing the Wiring",{"path":1979,"title":1980},"\u002Fmastering-container-queries-responsive-layouts\u002Fresponsive-component-patterns\u002Fresponsive-navigation-without-media-queries","Responsive Navigation Without Media Queries Using Container Queries",{"path":1982,"title":1983},"\u002Fmastering-container-queries-responsive-layouts\u002Fstyle-queries-and-container-state\u002Fcontainer-style-query-theming","Container Style Query Theming: One Token Instead of a Variant Class Matrix",{"path":1985,"title":1986},"\u002Fmastering-container-queries-responsive-layouts\u002Fstyle-queries-and-container-state","Style Queries and Container State: Token-Driven Component Variants",{"path":1988,"title":1989},"\u002Fmastering-container-queries-responsive-layouts\u002Fstyle-queries-and-container-state\u002Fstyle-queries-with-custom-properties","Style Queries With Custom Properties: Declaring, Inheriting, and Matching Tokens",{"path":1991,"title":1992},"\u002Fmastering-container-queries-responsive-layouts\u002Fstyle-queries-and-container-state\u002Fstyle-query-fallbacks-and-support","Style Query Fallbacks and Support: Designing for Older Browser Versions",1784566156645]