[{"data":1,"prerenderedAt":3374},["ShallowReactive",2],{"page-\u002Fcss-only-micro-interactions-animations\u002Fscroll-driven-animations":3,"content-all-pages":3111},{"id":4,"title":5,"body":6,"description":3093,"extension":3094,"meta":3095,"navigation":367,"path":3107,"seo":3108,"stem":3109,"__hash__":3110},"content\u002Fcss-only-micro-interactions-animations\u002Fscroll-driven-animations\u002Findex.md","Scroll-Driven Animations: Binding Motion to Scroll Position in Pure CSS",{"type":7,"value":8,"toc":3072},"minimark",[9,13,57,63,110,113,118,133,140,150,164,278,285,289,458,460,464,753,756,783,797,810,812,816,821,826,940,961,967,975,1087,1097,1103,1106,1171,1181,1184,1387,1391,1408,1585,1596,1602,1616,1706,1713,1715,1719,1722,1890,2434,2453,2455,2459,2462,2469,2506,2509,2527,2601,2613,2622,2624,2628,2631,2692,2694,2698,2809,2823,2825,2829,2960,2962,2966,2983,2999,3012,3021,3023,3027,3068],[10,11,5],"h1",{"id":12},"scroll-driven-animations-binding-motion-to-scroll-position-in-pure-css",[14,15,16,17,21,22,25,26,30,31,36,37,40,41,44,45,48,49,52,53,56],"p",{},"Scroll-linked motion has been a JavaScript problem for two decades: attach a listener, read ",[18,19,20],"code",{},"scrollTop",", do arithmetic, write a style, and hope the main thread is free enough to do it sixty times a second. CSS scroll-driven animations delete that whole loop. A regular ",[18,23,24],{},"@keyframes"," rule is bound to a ",[27,28,29],"em",{},"timeline"," that advances with scroll position rather than with the clock, and the browser evaluates it wherever it evaluates the scroll itself. This guide sits inside ",[32,33,35],"a",{"href":34},"\u002Fcss-only-micro-interactions-animations\u002F","CSS-Only Micro-Interactions & Animations"," and covers the whole surface: ",[18,38,39],{},"animation-timeline",", the anonymous ",[18,42,43],{},"scroll()"," and ",[18,46,47],{},"view()"," functions, named timelines, and the ",[18,50,51],{},"animation-range"," grammar that decides ",[27,54,55],{},"when"," along an element's travel the animation actually plays.",[14,58,59],{},[60,61,62],"strong",{},"Prerequisites — this guide assumes you can already:",[64,65,66,82,89,92,99],"ul",{},[67,68,69,70,72,73,76,77,81],"li",{},"Write a ",[18,71,24],{}," rule and attach it with the ",[18,74,75],{},"animation"," shorthand (see the ",[32,78,80],{"href":79},"\u002Fcss-only-micro-interactions-animations\u002Fkeyframe-animation-patterns\u002F","keyframe animation patterns"," guide).",[67,83,84,85,88],{},"Explain what ",[18,86,87],{},"animation-fill-mode: both"," does during a delay.",[67,90,91],{},"Recognise which CSS properties the compositor can animate without a layout pass.",[67,93,94,95,98],{},"Use ",[18,96,97],{},"@supports"," for feature detection.",[67,100,101,102,105,106,109],{},"Reason about a scroll container: an element with ",[18,103,104],{},"overflow"," other than ",[18,107,108],{},"visible"," that has content larger than its box.",[111,112],"hr",{},[114,115,117],"h2",{"id":116},"the-core-concept-an-animation-needs-a-timeline-not-a-clock","The Core Concept: An Animation Needs a Timeline, Not a Clock",[14,119,120,121,124,125,128,129,132],{},"Every CSS animation already has a timeline. Until scroll-driven animations existed you never had to name it, because there was only one: the ",[27,122,123],{},"document timeline",", whose current time is milliseconds since the document loaded. ",[18,126,127],{},"animation-duration: 2s"," means \"map the keyframes across two seconds of that timeline\". The CSS Scroll-Driven Animations specification generalises this. It introduces two new kinds of ",[27,130,131],{},"progress-based"," timeline whose current time is not a duration at all but a percentage between 0% and 100%, derived from geometry.",[14,134,135,136,139],{},"A ",[60,137,138],{},"scroll progress timeline"," is anchored to a scroll container. Its 0% is that container's initial scroll offset and its 100% is its maximum scroll offset. Scroll halfway down and the timeline reads 50%. There is no time involved: fling the scroller and the timeline jumps; scroll backwards and it runs backwards; stop and it freezes.",[14,141,135,142,145,146,149],{},[60,143,144],{},"view progress timeline"," is anchored to a ",[27,147,148],{},"subject element"," and describes that element's own journey through the scrollport of its nearest scroll container. Its 0% is the moment the subject first begins to intersect the scrollport, and its 100% is the moment it has completely left. Every element on the page gets its own view timeline with its own independent progress. This is the primitive that replaces IntersectionObserver for visual effects.",[14,151,152,153,155,156,159,160,163],{},"Both are attached with the same property: ",[18,154,39],{},". The ",[18,157,158],{},"\u003Ckeyframes>"," rule, the ",[18,161,162],{},"animation-name",", the keyframe stops — none of that changes. You are swapping out the thing that supplies progress, and nothing else.",[165,166,172,173,172,177,172,181,172,188,172,194,172,204,172,209,172,213,172,221,172,225,172,228,172,231,172,234,172,238,172,242,172,247,172,251,172,258,172,262,172,264,172,269,172,274],"svg",{"viewBox":167,"role":168,"ariaLabel":169,"xmlns":170,"style":171},"0 0 720 330","img","A ruler showing the cover range subdivided into entry, contain and exit, with a shorter highlighted reveal range","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;height:auto;max-width:720px;margin:2rem 0","\n  ",[174,175,176],"title",{},"view() range names along an element's pass",[178,179,180],"desc",{},"Three stacked bars over a shared axis: the full cover range, its entry, contain and exit sub-ranges, and a shorter highlighted range running from entry zero percent to cover forty percent.",[182,183,187],"text",{"x":184,"y":185,"style":186},"360","28","text-anchor:middle;fill:currentColor;font:700 17px sans-serif","The ranges of a view progress timeline",[182,189,193],{"x":190,"y":191,"style":192},"110","92","text-anchor:end;fill:currentColor;font:13px sans-serif;opacity:0.8","cover",[195,196],"rect",{"x":197,"y":198,"width":199,"height":200,"rx":201,"fill":202,"opacity":203},"120","70","570","34","6","#7aa2ff","0.16",[182,205,208],{"x":206,"y":191,"style":207},"405","text-anchor:middle;fill:currentColor;font:12px ui-monospace,monospace","cover 0% to cover 100%",[182,210,212],{"x":190,"y":211,"style":192},"152","phases",[195,214],{"x":197,"y":215,"width":216,"height":200,"rx":201,"fill":217,"stroke":218,"strokeWidth":219,"opacity":220},"130","188","none","currentColor","1.5","0.55",[182,222,224],{"x":223,"y":211,"style":207},"214","entry",[195,226],{"x":227,"y":215,"width":216,"height":200,"rx":201,"fill":217,"stroke":218,"strokeWidth":219,"opacity":220},"311",[182,229,230],{"x":206,"y":211,"style":207},"contain",[195,232],{"x":233,"y":215,"width":216,"height":200,"rx":201,"fill":217,"stroke":218,"strokeWidth":219,"opacity":220},"502",[182,235,237],{"x":236,"y":211,"style":207},"596","exit",[182,239,241],{"x":190,"y":240,"style":192},"212","reveal",[195,243],{"x":197,"y":244,"width":245,"height":200,"rx":201,"fill":202,"opacity":246},"190","228","0.34",[182,248,250],{"x":249,"y":240,"style":207},"234","entry 0% cover 40%",[252,253],"line",{"x1":197,"y1":254,"x2":255,"y2":254,"stroke":218,"strokeWidth":256,"opacity":257},"252","690","1","0.45",[252,259],{"x1":197,"y1":260,"x2":197,"y2":261,"stroke":218,"strokeWidth":256,"opacity":257},"246","258",[252,263],{"x1":255,"y1":260,"x2":255,"y2":261,"stroke":218,"strokeWidth":256,"opacity":257},[182,265,268],{"x":197,"y":266,"style":267},"276","text-anchor:middle;fill:currentColor;font:12px sans-serif;opacity:0.7","first pixel visible",[182,270,273],{"x":271,"y":266,"style":272},"700","text-anchor:end;fill:currentColor;font:12px sans-serif;opacity:0.7","last pixel gone",[182,275,277],{"x":184,"y":276,"style":267},"310","A range is a start name plus offset and an end name plus offset.",[14,279,280,281,284],{},"Here is that swap at its smallest. Scroll the frame and watch the badge track the scrollbar exactly — there is no listener, no throttle, no ",[18,282,283],{},"requestAnimationFrame",".",[286,287],"live-demo",{"name":288},"scroll-timeline-intro",[290,291,296],"pre",{"className":292,"code":293,"language":294,"meta":295,"style":295},"language-css shiki shiki-themes github-light-high-contrast github-dark-high-contrast",".badge {\n  \u002F* No duration is honoured; progress comes from the scroller.\n     `linear` and `both` are the two defaults you almost always want. *\u002F\n  animation: badge-spin linear both;\n  animation-timeline: scroll(root);\n}\n\n@keyframes badge-spin {\n  from { transform: rotate(0deg) scale(0.7); }\n  to   { transform: rotate(360deg) scale(1.15); }\n}\n","css","",[18,297,298,310,317,323,341,356,362,369,381,421,453],{"__ignoreMap":295},[299,300,302,306],"span",{"class":252,"line":301},1,[299,303,305],{"class":304},"s-5SL",".badge",[299,307,309],{"class":308},"suds8"," {\n",[299,311,313],{"class":252,"line":312},2,[299,314,316],{"class":315},"sLBg1","  \u002F* No duration is honoured; progress comes from the scroller.\n",[299,318,320],{"class":252,"line":319},3,[299,321,322],{"class":315},"     `linear` and `both` are the two defaults you almost always want. *\u002F\n",[299,324,326,329,332,335,338],{"class":252,"line":325},4,[299,327,328],{"class":304},"  animation",[299,330,331],{"class":308},": badge-spin ",[299,333,334],{"class":304},"linear",[299,336,337],{"class":304}," both",[299,339,340],{"class":308},";\n",[299,342,344,347,350,353],{"class":252,"line":343},5,[299,345,346],{"class":304},"  animation-timeline",[299,348,349],{"class":308},": ",[299,351,352],{"class":304},"scroll",[299,354,355],{"class":308},"(root);\n",[299,357,359],{"class":252,"line":358},6,[299,360,361],{"class":308},"}\n",[299,363,365],{"class":252,"line":364},7,[299,366,368],{"emptyLinePlaceholder":367},true,"\n",[299,370,372,375,379],{"class":252,"line":371},8,[299,373,24],{"class":374},"sHUrx",[299,376,378],{"class":377},"soyes"," badge-spin",[299,380,309],{"class":308},[299,382,384,387,390,393,395,398,401,404,407,410,413,415,418],{"class":252,"line":383},9,[299,385,386],{"class":304},"  from",[299,388,389],{"class":308}," { ",[299,391,392],{"class":304},"transform",[299,394,349],{"class":308},[299,396,397],{"class":304},"rotate",[299,399,400],{"class":308},"(",[299,402,403],{"class":304},"0",[299,405,406],{"class":374},"deg",[299,408,409],{"class":308},") ",[299,411,412],{"class":304},"scale",[299,414,400],{"class":308},[299,416,417],{"class":304},"0.7",[299,419,420],{"class":308},"); }\n",[299,422,424,427,430,432,434,436,438,440,442,444,446,448,451],{"class":252,"line":423},10,[299,425,426],{"class":304},"  to",[299,428,429],{"class":308},"   { ",[299,431,392],{"class":304},[299,433,349],{"class":308},[299,435,397],{"class":304},[299,437,400],{"class":308},[299,439,184],{"class":304},[299,441,406],{"class":374},[299,443,409],{"class":308},[299,445,412],{"class":304},[299,447,400],{"class":308},[299,449,450],{"class":304},"1.15",[299,452,420],{"class":308},[299,454,456],{"class":252,"line":455},11,[299,457,361],{"class":308},[111,459],{},[114,461,463],{"id":462},"syntax-and-parameters","Syntax and Parameters",[465,466,467,483],"table",{},[468,469,470],"thead",{},[471,472,473,477,480],"tr",{},[474,475,476],"th",{},"Token",[474,478,479],{},"Accepted values",[474,481,482],{},"Default",[484,485,486,513,535,560,580,600,617,638,655,672,690,711,727],"tbody",{},[471,487,488,493,508],{},[489,490,491],"td",{},[18,492,39],{},[489,494,495,498,499,498,501,498,504,498,506],{},[18,496,497],{},"auto",", ",[18,500,217],{},[18,502,503],{},"\u003Cdashed-ident>",[18,505,43],{},[18,507,47],{},[489,509,510,512],{},[18,511,497],{}," (the document timeline)",[471,514,515,520,531],{},[489,516,517,519],{},[18,518,43],{}," scroller",[489,521,522,498,525,498,528],{},[18,523,524],{},"nearest",[18,526,527],{},"root",[18,529,530],{},"self",[489,532,533],{},[18,534,524],{},[471,536,537,542,556],{},[489,538,539,541],{},[18,540,43],{}," axis",[489,543,544,498,547,498,550,498,553],{},[18,545,546],{},"block",[18,548,549],{},"inline",[18,551,552],{},"x",[18,554,555],{},"y",[489,557,558],{},[18,559,546],{},[471,561,562,566,576],{},[489,563,564,541],{},[18,565,47],{},[489,567,568,498,570,498,572,498,574],{},[18,569,546],{},[18,571,549],{},[18,573,552],{},[18,575,555],{},[489,577,578],{},[18,579,546],{},[471,581,582,587,596],{},[489,583,584,586],{},[18,585,47],{}," inset",[489,588,589,591,592,595],{},[18,590,497],{},", one or two ",[18,593,594],{},"\u003Clength-percentage>"," values",[489,597,598],{},[18,599,497],{},[471,601,602,607,613],{},[489,603,604],{},[18,605,606],{},"scroll-timeline-name",[489,608,609,498,611],{},[18,610,217],{},[18,612,503],{},[489,614,615],{},[18,616,217],{},[471,618,619,624,634],{},[489,620,621],{},[18,622,623],{},"scroll-timeline-axis",[489,625,626,498,628,498,630,498,632],{},[18,627,546],{},[18,629,549],{},[18,631,552],{},[18,633,555],{},[489,635,636],{},[18,637,546],{},[471,639,640,645,651],{},[489,641,642],{},[18,643,644],{},"view-timeline-name",[489,646,647,498,649],{},[18,648,217],{},[18,650,503],{},[489,652,653],{},[18,654,217],{},[471,656,657,662,668],{},[489,658,659],{},[18,660,661],{},"view-timeline-inset",[489,663,664,591,666,595],{},[18,665,497],{},[18,667,594],{},[489,669,670],{},[18,671,497],{},[471,673,674,679,686],{},[489,675,676],{},[18,677,678],{},"timeline-scope",[489,680,681,683,684],{},[18,682,217],{},", one or more ",[18,685,503],{},[489,687,688],{},[18,689,217],{},[471,691,692,697,707],{},[489,693,694],{},[18,695,696],{},"animation-range-start",[489,698,699,498,702,498,704],{},[18,700,701],{},"normal",[18,703,594],{},[18,705,706],{},"\u003Ctimeline-range-name> \u003Clength-percentage>?",[489,708,709],{},[18,710,701],{},[471,712,713,718,723],{},[489,714,715],{},[18,716,717],{},"animation-range-end",[489,719,720,721],{},"same grammar as ",[18,722,696],{},[489,724,725],{},[18,726,701],{},[471,728,729,734,750],{},[489,730,731],{},[18,732,733],{},"\u003Ctimeline-range-name>",[489,735,736,498,738,498,740,498,742,498,744,498,747],{},[18,737,193],{},[18,739,230],{},[18,741,224],{},[18,743,237],{},[18,745,746],{},"entry-crossing",[18,748,749],{},"exit-crossing",[489,751,752],{},"—",[14,754,755],{},"Three of these deserve prose before you use them.",[14,757,758,761,762,765,766,768,769,772,773,776,777,779,780,782],{},[18,759,760],{},"scroll(root)"," versus ",[18,763,764],{},"scroll(nearest)"," is the distinction that causes the most confusion. ",[18,767,524],{}," walks up the ancestor chain from the animated element and picks the first scroll container it finds — which, on a page with no nested scrollers, ",[27,770,771],{},"is"," the document, making the two look identical until the day someone wraps your component in an ",[18,774,775],{},"overflow: auto"," panel. Say ",[18,778,527],{}," when you mean the document and ",[18,781,524],{}," when you mean \"whatever panel I happen to be inside\".",[14,784,785,786,788,789,792,793,796],{},"The ",[18,787,47],{}," ",[60,790,791],{},"inset"," shrinks the notional scrollport used to compute the timeline. ",[18,794,795],{},"view(block 25% 0%)"," says: pretend the top quarter of the scrollport is not there. Since the timeline's 0% is defined by first intersection with the scrollport, insetting the top delays the whole timeline until the element is a quarter of the way up the screen. This is how you stop reveals from firing on content that is technically visible but sitting under a sticky header.",[14,798,799,801,802,805,806,809],{},[18,800,51],{}," is a shorthand for the start and end pair, and its grammar is ",[27,803,804],{},"two"," range names, not one: ",[18,807,808],{},"animation-range: entry 0% cover 40%"," means \"start when the entry phase is 0% complete and end when the cover phase is 40% complete\". Both halves are independent; mixing phase names across the two halves is normal and intended, not a mistake.",[111,811],{},[114,813,815],{"id":814},"step-by-step-implementation","Step-by-Step Implementation",[817,818,820],"h3",{"id":819},"step-1-bind-a-keyframe-rule-to-the-document-scroller","Step 1 — Bind a keyframe rule to the document scroller",[14,822,823,824,284],{},"Start with the simplest possible case: something that reflects overall document progress. The only new line is ",[18,825,39],{},[290,827,829],{"className":292,"code":828,"language":294,"meta":295,"style":295},".doc-indicator {\n  transform-origin: 0 50%;\n  animation: fill-bar linear;\n  animation-timeline: scroll(root block);\n}\n\n@keyframes fill-bar {\n  from { transform: scaleX(0); }\n  to   { transform: scaleX(1); }\n}\n",[18,830,831,838,855,866,882,886,890,899,918,936],{"__ignoreMap":295},[299,832,833,836],{"class":252,"line":301},[299,834,835],{"class":304},".doc-indicator",[299,837,309],{"class":308},[299,839,840,843,845,847,850,853],{"class":252,"line":312},[299,841,842],{"class":304},"  transform-origin",[299,844,349],{"class":308},[299,846,403],{"class":304},[299,848,849],{"class":304}," 50",[299,851,852],{"class":374},"%",[299,854,340],{"class":308},[299,856,857,859,862,864],{"class":252,"line":319},[299,858,328],{"class":304},[299,860,861],{"class":308},": fill-bar ",[299,863,334],{"class":304},[299,865,340],{"class":308},[299,867,868,870,872,874,877,879],{"class":252,"line":325},[299,869,346],{"class":304},[299,871,349],{"class":308},[299,873,352],{"class":304},[299,875,876],{"class":308},"(root ",[299,878,546],{"class":304},[299,880,881],{"class":308},");\n",[299,883,884],{"class":252,"line":343},[299,885,361],{"class":308},[299,887,888],{"class":252,"line":358},[299,889,368],{"emptyLinePlaceholder":367},[299,891,892,894,897],{"class":252,"line":364},[299,893,24],{"class":374},[299,895,896],{"class":377}," fill-bar",[299,898,309],{"class":308},[299,900,901,903,905,907,909,912,914,916],{"class":252,"line":371},[299,902,386],{"class":304},[299,904,389],{"class":308},[299,906,392],{"class":304},[299,908,349],{"class":308},[299,910,911],{"class":304},"scaleX",[299,913,400],{"class":308},[299,915,403],{"class":304},[299,917,420],{"class":308},[299,919,920,922,924,926,928,930,932,934],{"class":252,"line":383},[299,921,426],{"class":304},[299,923,429],{"class":308},[299,925,392],{"class":304},[299,927,349],{"class":308},[299,929,911],{"class":304},[299,931,400],{"class":308},[299,933,256],{"class":304},[299,935,420],{"class":308},[299,937,938],{"class":252,"line":423},[299,939,361],{"class":308},[14,941,942,943,946,947,950,951,953,954,957,958,960],{},"Note that ",[18,944,945],{},"animation-duration"," is absent. It would be ignored anyway; progress is geometric. What you ",[27,948,949],{},"must not"," omit is ",[18,952,334],{},". The default timing function is ",[18,955,956],{},"ease",", and an ",[18,959,956],{}," curve applied on top of a scroll mapping means the element lurches ahead of your finger at the start and lags behind it at the end. Shape the motion with keyframe percentages instead, and leave the timing function alone.",[817,962,964,965],{"id":963},"step-2-switch-to-a-per-element-timeline-with-view","Step 2 — Switch to a per-element timeline with ",[18,966,47],{},[14,968,969,971,972,974],{},[18,970,43],{}," gives every element on the page the same progress value. That is wrong for reveals, where each card should animate according to its own position. ",[18,973,47],{}," fixes this without any extra plumbing: written on the element itself, it creates a timeline whose subject is that element.",[290,976,978],{"className":292,"code":977,"language":294,"meta":295,"style":295},".reveal {\n  animation: rise linear both;\n  animation-timeline: view();\n}\n\n@keyframes rise {\n  from { opacity: 0; transform: translateY(30px); }\n  to   { opacity: 1; transform: translateY(0); }\n}\n",[18,979,980,987,1000,1007,1011,1015,1024,1057,1083],{"__ignoreMap":295},[299,981,982,985],{"class":252,"line":301},[299,983,984],{"class":304},".reveal",[299,986,309],{"class":308},[299,988,989,991,994,996,998],{"class":252,"line":312},[299,990,328],{"class":304},[299,992,993],{"class":308},": rise ",[299,995,334],{"class":304},[299,997,337],{"class":304},[299,999,340],{"class":308},[299,1001,1002,1004],{"class":252,"line":319},[299,1003,346],{"class":304},[299,1005,1006],{"class":308},": view();\n",[299,1008,1009],{"class":252,"line":325},[299,1010,361],{"class":308},[299,1012,1013],{"class":252,"line":343},[299,1014,368],{"emptyLinePlaceholder":367},[299,1016,1017,1019,1022],{"class":252,"line":358},[299,1018,24],{"class":374},[299,1020,1021],{"class":377}," rise",[299,1023,309],{"class":308},[299,1025,1026,1028,1030,1033,1035,1037,1040,1042,1044,1047,1049,1052,1055],{"class":252,"line":364},[299,1027,386],{"class":304},[299,1029,389],{"class":308},[299,1031,1032],{"class":304},"opacity",[299,1034,349],{"class":308},[299,1036,403],{"class":304},[299,1038,1039],{"class":308},"; ",[299,1041,392],{"class":304},[299,1043,349],{"class":308},[299,1045,1046],{"class":304},"translateY",[299,1048,400],{"class":308},[299,1050,1051],{"class":304},"30",[299,1053,1054],{"class":374},"px",[299,1056,420],{"class":308},[299,1058,1059,1061,1063,1065,1067,1069,1071,1073,1075,1077,1079,1081],{"class":252,"line":371},[299,1060,426],{"class":304},[299,1062,429],{"class":308},[299,1064,1032],{"class":304},[299,1066,349],{"class":308},[299,1068,256],{"class":304},[299,1070,1039],{"class":308},[299,1072,392],{"class":304},[299,1074,349],{"class":308},[299,1076,1046],{"class":304},[299,1078,400],{"class":308},[299,1080,403],{"class":304},[299,1082,420],{"class":308},[299,1084,1085],{"class":252,"line":383},[299,1086,361],{"class":308},[14,1088,1089,1090,1092,1093,1096],{},"At this point the animation runs across the full ",[18,1091,193],{}," range, meaning it plays forward as the element enters and ",[27,1094,1095],{},"backward"," as it leaves — the element fades out again at the top of the screen. That is often not what you want, which is what step three is for.",[817,1098,1100,1101],{"id":1099},"step-3-constrain-the-play-window-with-animation-range","Step 3 — Constrain the play window with ",[18,1102,51],{},[14,1104,1105],{},"Restrict the range so the animation completes early and stays completed.",[290,1107,1109],{"className":292,"code":1108,"language":294,"meta":295,"style":295},".reveal {\n  animation: rise linear both;\n  animation-timeline: view();\n  \u002F* Begin the instant the element's leading edge crosses in;\n     be finished by the time it is 40% of the way across the scrollport. *\u002F\n  animation-range: entry 0% cover 40%;\n}\n",[18,1110,1111,1117,1129,1135,1140,1145,1167],{"__ignoreMap":295},[299,1112,1113,1115],{"class":252,"line":301},[299,1114,984],{"class":304},[299,1116,309],{"class":308},[299,1118,1119,1121,1123,1125,1127],{"class":252,"line":312},[299,1120,328],{"class":304},[299,1122,993],{"class":308},[299,1124,334],{"class":304},[299,1126,337],{"class":304},[299,1128,340],{"class":308},[299,1130,1131,1133],{"class":252,"line":319},[299,1132,346],{"class":304},[299,1134,1006],{"class":308},[299,1136,1137],{"class":252,"line":325},[299,1138,1139],{"class":315},"  \u002F* Begin the instant the element's leading edge crosses in;\n",[299,1141,1142],{"class":252,"line":343},[299,1143,1144],{"class":315},"     be finished by the time it is 40% of the way across the scrollport. *\u002F\n",[299,1146,1147,1150,1153,1155,1157,1160,1163,1165],{"class":252,"line":358},[299,1148,1149],{"class":304},"  animation-range",[299,1151,1152],{"class":308},": entry ",[299,1154,403],{"class":304},[299,1156,852],{"class":374},[299,1158,1159],{"class":304}," cover",[299,1161,1162],{"class":304}," 40",[299,1164,852],{"class":374},[299,1166,340],{"class":308},[299,1168,1169],{"class":252,"line":364},[299,1170,361],{"class":308},[14,1172,1173,1174,1177,1178,1180],{},"Because the range ends at ",[18,1175,1176],{},"cover 40%",", everything past that point is \"after the animation\" — and ",[18,1179,87],{}," holds the final keyframe there. The card rises once and stays put. Here is the difference between an early-finishing range and one that spans the whole pass:",[286,1182],{"name":1183},"scroll-view-range-compare",[290,1185,1187],{"className":292,"code":1186,"language":294,"meta":295,"style":295},".box {\n  animation: box-in linear both;\n  animation-timeline: view();\n}\n\n\u002F* Done by the time the box is 40% up the scrollport — a one-way reveal. *\u002F\n.early { animation-range: entry 0% cover 40%; }\n\n\u002F* Runs across the element's entire pass through the scrollport, so it plays\n   forwards on the way in and backwards on the way out. *\u002F\n.full { animation-range: cover 0% cover 100%; }\n\n@keyframes box-in {\n  from { opacity: 0; transform: translateY(24px) scale(0.92); }\n  to   { opacity: 1; transform: translateY(0) scale(1); }\n}\n",[18,1188,1189,1196,1209,1215,1219,1223,1228,1252,1256,1261,1266,1293,1298,1308,1347,1382],{"__ignoreMap":295},[299,1190,1191,1194],{"class":252,"line":301},[299,1192,1193],{"class":304},".box",[299,1195,309],{"class":308},[299,1197,1198,1200,1203,1205,1207],{"class":252,"line":312},[299,1199,328],{"class":304},[299,1201,1202],{"class":308},": box-in ",[299,1204,334],{"class":304},[299,1206,337],{"class":304},[299,1208,340],{"class":308},[299,1210,1211,1213],{"class":252,"line":319},[299,1212,346],{"class":304},[299,1214,1006],{"class":308},[299,1216,1217],{"class":252,"line":325},[299,1218,361],{"class":308},[299,1220,1221],{"class":252,"line":343},[299,1222,368],{"emptyLinePlaceholder":367},[299,1224,1225],{"class":252,"line":358},[299,1226,1227],{"class":315},"\u002F* Done by the time the box is 40% up the scrollport — a one-way reveal. *\u002F\n",[299,1229,1230,1233,1235,1237,1239,1241,1243,1245,1247,1249],{"class":252,"line":364},[299,1231,1232],{"class":304},".early",[299,1234,389],{"class":308},[299,1236,51],{"class":304},[299,1238,1152],{"class":308},[299,1240,403],{"class":304},[299,1242,852],{"class":374},[299,1244,1159],{"class":304},[299,1246,1162],{"class":304},[299,1248,852],{"class":374},[299,1250,1251],{"class":308},"; }\n",[299,1253,1254],{"class":252,"line":371},[299,1255,368],{"emptyLinePlaceholder":367},[299,1257,1258],{"class":252,"line":383},[299,1259,1260],{"class":315},"\u002F* Runs across the element's entire pass through the scrollport, so it plays\n",[299,1262,1263],{"class":252,"line":423},[299,1264,1265],{"class":315},"   forwards on the way in and backwards on the way out. *\u002F\n",[299,1267,1268,1271,1273,1275,1277,1279,1282,1284,1286,1289,1291],{"class":252,"line":455},[299,1269,1270],{"class":304},".full",[299,1272,389],{"class":308},[299,1274,51],{"class":304},[299,1276,349],{"class":308},[299,1278,193],{"class":304},[299,1280,1281],{"class":304}," 0",[299,1283,852],{"class":374},[299,1285,1159],{"class":304},[299,1287,1288],{"class":304}," 100",[299,1290,852],{"class":374},[299,1292,1251],{"class":308},[299,1294,1296],{"class":252,"line":1295},12,[299,1297,368],{"emptyLinePlaceholder":367},[299,1299,1301,1303,1306],{"class":252,"line":1300},13,[299,1302,24],{"class":374},[299,1304,1305],{"class":377}," box-in",[299,1307,309],{"class":308},[299,1309,1311,1313,1315,1317,1319,1321,1323,1325,1327,1329,1331,1334,1336,1338,1340,1342,1345],{"class":252,"line":1310},14,[299,1312,386],{"class":304},[299,1314,389],{"class":308},[299,1316,1032],{"class":304},[299,1318,349],{"class":308},[299,1320,403],{"class":304},[299,1322,1039],{"class":308},[299,1324,392],{"class":304},[299,1326,349],{"class":308},[299,1328,1046],{"class":304},[299,1330,400],{"class":308},[299,1332,1333],{"class":304},"24",[299,1335,1054],{"class":374},[299,1337,409],{"class":308},[299,1339,412],{"class":304},[299,1341,400],{"class":308},[299,1343,1344],{"class":304},"0.92",[299,1346,420],{"class":308},[299,1348,1350,1352,1354,1356,1358,1360,1362,1364,1366,1368,1370,1372,1374,1376,1378,1380],{"class":252,"line":1349},15,[299,1351,426],{"class":304},[299,1353,429],{"class":308},[299,1355,1032],{"class":304},[299,1357,349],{"class":308},[299,1359,256],{"class":304},[299,1361,1039],{"class":308},[299,1363,392],{"class":304},[299,1365,349],{"class":308},[299,1367,1046],{"class":304},[299,1369,400],{"class":308},[299,1371,403],{"class":304},[299,1373,409],{"class":308},[299,1375,412],{"class":304},[299,1377,400],{"class":308},[299,1379,256],{"class":304},[299,1381,420],{"class":308},[299,1383,1385],{"class":252,"line":1384},16,[299,1386,361],{"class":308},[817,1388,1390],{"id":1389},"step-4-name-a-timeline-when-the-driver-is-not-the-animated-element","Step 4 — Name a timeline when the driver is not the animated element",[14,1392,1393,44,1395,1397,1398,1401,1402,1404,1405,1407],{},[18,1394,43],{},[18,1396,47],{}," are ",[27,1399,1400],{},"anonymous"," timelines: they can only describe the element they are written on, or its ancestor scroller. When element A's animation must be driven by element B's geometry, you need a named timeline. Element B declares ",[18,1403,644],{}," (or ",[18,1406,606],{},"), and element A references that name.",[290,1409,1411],{"className":292,"code":1410,"language":294,"meta":295,"style":295},"\u002F* The driver: a long section whose passage supplies the progress. *\u002F\n.chapter {\n  view-timeline-name: --chapter-pass;\n  view-timeline-axis: block;\n}\n\n\u002F* The driven element: a sibling caption, animated by the chapter's travel. *\u002F\n.chapter-caption {\n  animation: slide-label linear both;\n  animation-timeline: --chapter-pass;\n  animation-range: contain 0% contain 100%;\n}\n\n@keyframes slide-label {\n  from { transform: translateX(-1rem); opacity: 0.4; }\n  to   { transform: translateX(0); opacity: 1; }\n}\n",[18,1412,1413,1418,1425,1433,1444,1448,1452,1457,1464,1477,1483,1504,1508,1512,1521,1554,1580],{"__ignoreMap":295},[299,1414,1415],{"class":252,"line":301},[299,1416,1417],{"class":315},"\u002F* The driver: a long section whose passage supplies the progress. *\u002F\n",[299,1419,1420,1423],{"class":252,"line":312},[299,1421,1422],{"class":304},".chapter",[299,1424,309],{"class":308},[299,1426,1427,1430],{"class":252,"line":319},[299,1428,1429],{"class":304},"  view-timeline-name",[299,1431,1432],{"class":308},": --chapter-pass;\n",[299,1434,1435,1438,1440,1442],{"class":252,"line":325},[299,1436,1437],{"class":304},"  view-timeline-axis",[299,1439,349],{"class":308},[299,1441,546],{"class":304},[299,1443,340],{"class":308},[299,1445,1446],{"class":252,"line":343},[299,1447,361],{"class":308},[299,1449,1450],{"class":252,"line":358},[299,1451,368],{"emptyLinePlaceholder":367},[299,1453,1454],{"class":252,"line":364},[299,1455,1456],{"class":315},"\u002F* The driven element: a sibling caption, animated by the chapter's travel. *\u002F\n",[299,1458,1459,1462],{"class":252,"line":371},[299,1460,1461],{"class":304},".chapter-caption",[299,1463,309],{"class":308},[299,1465,1466,1468,1471,1473,1475],{"class":252,"line":383},[299,1467,328],{"class":304},[299,1469,1470],{"class":308},": slide-label ",[299,1472,334],{"class":304},[299,1474,337],{"class":304},[299,1476,340],{"class":308},[299,1478,1479,1481],{"class":252,"line":423},[299,1480,346],{"class":304},[299,1482,1432],{"class":308},[299,1484,1485,1487,1489,1491,1493,1495,1498,1500,1502],{"class":252,"line":455},[299,1486,1149],{"class":304},[299,1488,349],{"class":308},[299,1490,230],{"class":304},[299,1492,1281],{"class":304},[299,1494,852],{"class":374},[299,1496,1497],{"class":304}," contain",[299,1499,1288],{"class":304},[299,1501,852],{"class":374},[299,1503,340],{"class":308},[299,1505,1506],{"class":252,"line":1295},[299,1507,361],{"class":308},[299,1509,1510],{"class":252,"line":1300},[299,1511,368],{"emptyLinePlaceholder":367},[299,1513,1514,1516,1519],{"class":252,"line":1310},[299,1515,24],{"class":374},[299,1517,1518],{"class":377}," slide-label",[299,1520,309],{"class":308},[299,1522,1523,1525,1527,1529,1531,1534,1536,1539,1542,1545,1547,1549,1552],{"class":252,"line":1349},[299,1524,386],{"class":304},[299,1526,389],{"class":308},[299,1528,392],{"class":304},[299,1530,349],{"class":308},[299,1532,1533],{"class":304},"translateX",[299,1535,400],{"class":308},[299,1537,1538],{"class":304},"-1",[299,1540,1541],{"class":374},"rem",[299,1543,1544],{"class":308},"); ",[299,1546,1032],{"class":304},[299,1548,349],{"class":308},[299,1550,1551],{"class":304},"0.4",[299,1553,1251],{"class":308},[299,1555,1556,1558,1560,1562,1564,1566,1568,1570,1572,1574,1576,1578],{"class":252,"line":1384},[299,1557,426],{"class":304},[299,1559,429],{"class":308},[299,1561,392],{"class":304},[299,1563,349],{"class":308},[299,1565,1533],{"class":304},[299,1567,400],{"class":308},[299,1569,403],{"class":304},[299,1571,1544],{"class":308},[299,1573,1032],{"class":304},[299,1575,349],{"class":308},[299,1577,256],{"class":304},[299,1579,1251],{"class":308},[299,1581,1583],{"class":252,"line":1582},17,[299,1584,361],{"class":308},[14,1586,1587,1588,1591,1592,1595],{},"Timeline names resolve down the tree, not sideways: an element can reference a timeline declared on itself, on an ancestor, or on a ",[27,1589,1590],{},"preceding sibling"," of an ancestor. If the driver is somewhere else entirely, hoist the name into a common ancestor's scope with ",[18,1593,1594],{},"timeline-scope: --chapter-pass",", which makes the name visible to that ancestor's whole subtree.",[817,1597,1599,1600],{"id":1598},"step-5-gate-the-whole-thing-behind-supports","Step 5 — Gate the whole thing behind ",[18,1601,97],{},[14,1603,1604,1605,1607,1608,1611,1612,1615],{},"A browser that does not understand ",[18,1606,39],{}," will still parse ",[18,1609,1610],{},"animation: rise linear both"," and run it on the document timeline — with no duration, which resolves to ",[18,1613,1614],{},"0s",", which means the element snaps to its end state instantly. That is a benign failure, but only by accident. Make it deliberate:",[290,1617,1619],{"className":292,"code":1618,"language":294,"meta":295,"style":295},".reveal { opacity: 1; }\n\n@supports (animation-timeline: view()) {\n  .reveal {\n    animation: rise linear both;\n    animation-timeline: view();\n    animation-range: entry 0% cover 40%;\n  }\n}\n",[18,1620,1621,1635,1639,1651,1658,1671,1678,1697,1702],{"__ignoreMap":295},[299,1622,1623,1625,1627,1629,1631,1633],{"class":252,"line":301},[299,1624,984],{"class":304},[299,1626,389],{"class":308},[299,1628,1032],{"class":304},[299,1630,349],{"class":308},[299,1632,256],{"class":304},[299,1634,1251],{"class":308},[299,1636,1637],{"class":252,"line":312},[299,1638,368],{"emptyLinePlaceholder":367},[299,1640,1641,1643,1646,1648],{"class":252,"line":319},[299,1642,97],{"class":374},[299,1644,1645],{"class":308}," (",[299,1647,39],{"class":304},[299,1649,1650],{"class":308},": view()) {\n",[299,1652,1653,1656],{"class":252,"line":325},[299,1654,1655],{"class":304},"  .reveal",[299,1657,309],{"class":308},[299,1659,1660,1663,1665,1667,1669],{"class":252,"line":343},[299,1661,1662],{"class":304},"    animation",[299,1664,993],{"class":308},[299,1666,334],{"class":304},[299,1668,337],{"class":304},[299,1670,340],{"class":308},[299,1672,1673,1676],{"class":252,"line":358},[299,1674,1675],{"class":304},"    animation-timeline",[299,1677,1006],{"class":308},[299,1679,1680,1683,1685,1687,1689,1691,1693,1695],{"class":252,"line":364},[299,1681,1682],{"class":304},"    animation-range",[299,1684,1152],{"class":308},[299,1686,403],{"class":304},[299,1688,852],{"class":374},[299,1690,1159],{"class":304},[299,1692,1162],{"class":304},[299,1694,852],{"class":374},[299,1696,340],{"class":308},[299,1698,1699],{"class":252,"line":371},[299,1700,1701],{"class":308},"  }\n",[299,1703,1704],{"class":252,"line":383},[299,1705,361],{"class":308},[14,1707,785,1708,1712],{},[32,1709,1711],{"href":1710},"\u002Fcss-only-micro-interactions-animations\u002Fscroll-driven-animations\u002Fscroll-driven-animation-fallbacks\u002F","scroll-driven animation fallbacks"," page works through the full decision tree here, including what to do when the enhanced state and the base state disagree about layout.",[111,1714],{},[114,1716,1718],{"id":1717},"annotated-production-example-a-chapter-header-that-compresses","Annotated Production Example: A Chapter Header That Compresses",[14,1720,1721],{},"A realistic component: a sticky section header that shrinks, tightens its letter spacing, and gains a shadow as its section scrolls past. Done with a scroll listener this is a notorious jank source, because every frame reads layout and writes style. Done with a named view timeline it is four declarations.",[290,1723,1727],{"className":1724,"code":1725,"language":1726,"meta":295,"style":295},"language-html shiki shiki-themes github-light-high-contrast github-dark-high-contrast","\u003Csection class=\"chapter\">\n  \u003Cheader class=\"chapter__bar\">\n    \u003Ch2 class=\"chapter__title\">Deployment\u003C\u002Fh2>\n    \u003Cspan class=\"chapter__meta\">6 min read\u003C\u002Fspan>\n  \u003C\u002Fheader>\n  \u003Cdiv class=\"chapter__body\">\n    \u003Cp>Section content goes here, long enough to scroll.\u003C\u002Fp>\n    \u003Cp>More content.\u003C\u002Fp>\n    \u003Cp>Still more content.\u003C\u002Fp>\n  \u003C\u002Fdiv>\n\u003C\u002Fsection>\n","html",[18,1728,1729,1751,1768,1789,1809,1818,1834,1847,1860,1873,1881],{"__ignoreMap":295},[299,1730,1731,1734,1738,1741,1744,1748],{"class":252,"line":301},[299,1732,1733],{"class":308},"\u003C",[299,1735,1737],{"class":1736},"sne4z","section",[299,1739,1740],{"class":304}," class",[299,1742,1743],{"class":308},"=",[299,1745,1747],{"class":1746},"sT6z2","\"chapter\"",[299,1749,1750],{"class":308},">\n",[299,1752,1753,1756,1759,1761,1763,1766],{"class":252,"line":312},[299,1754,1755],{"class":308},"  \u003C",[299,1757,1758],{"class":1736},"header",[299,1760,1740],{"class":304},[299,1762,1743],{"class":308},[299,1764,1765],{"class":1746},"\"chapter__bar\"",[299,1767,1750],{"class":308},[299,1769,1770,1773,1775,1777,1779,1782,1785,1787],{"class":252,"line":319},[299,1771,1772],{"class":308},"    \u003C",[299,1774,114],{"class":1736},[299,1776,1740],{"class":304},[299,1778,1743],{"class":308},[299,1780,1781],{"class":1746},"\"chapter__title\"",[299,1783,1784],{"class":308},">Deployment\u003C\u002F",[299,1786,114],{"class":1736},[299,1788,1750],{"class":308},[299,1790,1791,1793,1795,1797,1799,1802,1805,1807],{"class":252,"line":325},[299,1792,1772],{"class":308},[299,1794,299],{"class":1736},[299,1796,1740],{"class":304},[299,1798,1743],{"class":308},[299,1800,1801],{"class":1746},"\"chapter__meta\"",[299,1803,1804],{"class":308},">6 min read\u003C\u002F",[299,1806,299],{"class":1736},[299,1808,1750],{"class":308},[299,1810,1811,1814,1816],{"class":252,"line":343},[299,1812,1813],{"class":308},"  \u003C\u002F",[299,1815,1758],{"class":1736},[299,1817,1750],{"class":308},[299,1819,1820,1822,1825,1827,1829,1832],{"class":252,"line":358},[299,1821,1755],{"class":308},[299,1823,1824],{"class":1736},"div",[299,1826,1740],{"class":304},[299,1828,1743],{"class":308},[299,1830,1831],{"class":1746},"\"chapter__body\"",[299,1833,1750],{"class":308},[299,1835,1836,1838,1840,1843,1845],{"class":252,"line":364},[299,1837,1772],{"class":308},[299,1839,14],{"class":1736},[299,1841,1842],{"class":308},">Section content goes here, long enough to scroll.\u003C\u002F",[299,1844,14],{"class":1736},[299,1846,1750],{"class":308},[299,1848,1849,1851,1853,1856,1858],{"class":252,"line":371},[299,1850,1772],{"class":308},[299,1852,14],{"class":1736},[299,1854,1855],{"class":308},">More content.\u003C\u002F",[299,1857,14],{"class":1736},[299,1859,1750],{"class":308},[299,1861,1862,1864,1866,1869,1871],{"class":252,"line":383},[299,1863,1772],{"class":308},[299,1865,14],{"class":1736},[299,1867,1868],{"class":308},">Still more content.\u003C\u002F",[299,1870,14],{"class":1736},[299,1872,1750],{"class":308},[299,1874,1875,1877,1879],{"class":252,"line":423},[299,1876,1813],{"class":308},[299,1878,1824],{"class":1736},[299,1880,1750],{"class":308},[299,1882,1883,1886,1888],{"class":252,"line":455},[299,1884,1885],{"class":308},"\u003C\u002F",[299,1887,1737],{"class":1736},[299,1889,1750],{"class":308},[290,1891,1893],{"className":292,"code":1892,"language":294,"meta":295,"style":295},".chapter {\n  \u002F* The section itself is the driver. Its pass through the scrollport\n     supplies progress to anything that names this timeline. *\u002F\n  view-timeline-name: --chapter;\n  view-timeline-axis: block;\n  position: relative;\n}\n\n.chapter__bar {\n  position: sticky;\n  top: 0;\n  display: flex;\n  align-items: baseline;\n  justify-content: space-between;\n  gap: 1rem;\n  padding: 1rem 1.25rem;\n  background: Canvas;\n  \u002F* Animating a custom property keeps the compressed state in one place. *\u002F\n  --compress: 0;\n}\n\n.chapter__title {\n  margin: 0;\n  font-size: 1.6rem;\n  \u002F* transform, not font-size: font-size would relayout every frame. *\u002F\n  transform: scale(calc(1 - 0.25 * var(--compress)));\n  transform-origin: left center;\n}\n\n.chapter__meta {\n  opacity: calc(1 - var(--compress));\n}\n\n@supports (animation-timeline: view()) {\n  .chapter__bar {\n    animation: compress linear both;\n    animation-timeline: --chapter;\n    \u002F* Only compress while the section is actually being read: from the\n       moment it fully covers the scrollport to the moment it starts leaving. *\u002F\n    animation-range: contain 0% exit 30%;\n  }\n\n  @keyframes compress {\n    from { --compress: 0; box-shadow: 0 0 0 rgba(0, 0, 0, 0); }\n    to   { --compress: 1; box-shadow: 0 2px 12px rgba(0, 0, 0, 0.18); }\n  }\n}\n",[18,1894,1895,1901,1906,1911,1918,1928,1940,1944,1948,1955,1966,1977,1989,2001,2013,2026,2044,2052,2058,2070,2075,2080,2088,2100,2115,2121,2160,2175,2180,2185,2193,2218,2223,2228,2239,2247,2261,2268,2274,2280,2302,2307,2312,2323,2371,2424,2429],{"__ignoreMap":295},[299,1896,1897,1899],{"class":252,"line":301},[299,1898,1422],{"class":304},[299,1900,309],{"class":308},[299,1902,1903],{"class":252,"line":312},[299,1904,1905],{"class":315},"  \u002F* The section itself is the driver. Its pass through the scrollport\n",[299,1907,1908],{"class":252,"line":319},[299,1909,1910],{"class":315},"     supplies progress to anything that names this timeline. *\u002F\n",[299,1912,1913,1915],{"class":252,"line":325},[299,1914,1429],{"class":304},[299,1916,1917],{"class":308},": --chapter;\n",[299,1919,1920,1922,1924,1926],{"class":252,"line":343},[299,1921,1437],{"class":304},[299,1923,349],{"class":308},[299,1925,546],{"class":304},[299,1927,340],{"class":308},[299,1929,1930,1933,1935,1938],{"class":252,"line":358},[299,1931,1932],{"class":304},"  position",[299,1934,349],{"class":308},[299,1936,1937],{"class":304},"relative",[299,1939,340],{"class":308},[299,1941,1942],{"class":252,"line":364},[299,1943,361],{"class":308},[299,1945,1946],{"class":252,"line":371},[299,1947,368],{"emptyLinePlaceholder":367},[299,1949,1950,1953],{"class":252,"line":383},[299,1951,1952],{"class":304},".chapter__bar",[299,1954,309],{"class":308},[299,1956,1957,1959,1961,1964],{"class":252,"line":423},[299,1958,1932],{"class":304},[299,1960,349],{"class":308},[299,1962,1963],{"class":304},"sticky",[299,1965,340],{"class":308},[299,1967,1968,1971,1973,1975],{"class":252,"line":455},[299,1969,1970],{"class":304},"  top",[299,1972,349],{"class":308},[299,1974,403],{"class":304},[299,1976,340],{"class":308},[299,1978,1979,1982,1984,1987],{"class":252,"line":1295},[299,1980,1981],{"class":304},"  display",[299,1983,349],{"class":308},[299,1985,1986],{"class":304},"flex",[299,1988,340],{"class":308},[299,1990,1991,1994,1996,1999],{"class":252,"line":1300},[299,1992,1993],{"class":304},"  align-items",[299,1995,349],{"class":308},[299,1997,1998],{"class":304},"baseline",[299,2000,340],{"class":308},[299,2002,2003,2006,2008,2011],{"class":252,"line":1310},[299,2004,2005],{"class":304},"  justify-content",[299,2007,349],{"class":308},[299,2009,2010],{"class":304},"space-between",[299,2012,340],{"class":308},[299,2014,2015,2018,2020,2022,2024],{"class":252,"line":1349},[299,2016,2017],{"class":304},"  gap",[299,2019,349],{"class":308},[299,2021,256],{"class":304},[299,2023,1541],{"class":374},[299,2025,340],{"class":308},[299,2027,2028,2031,2033,2035,2037,2040,2042],{"class":252,"line":1384},[299,2029,2030],{"class":304},"  padding",[299,2032,349],{"class":308},[299,2034,256],{"class":304},[299,2036,1541],{"class":374},[299,2038,2039],{"class":304}," 1.25",[299,2041,1541],{"class":374},[299,2043,340],{"class":308},[299,2045,2046,2049],{"class":252,"line":1582},[299,2047,2048],{"class":304},"  background",[299,2050,2051],{"class":308},": Canvas;\n",[299,2053,2055],{"class":252,"line":2054},18,[299,2056,2057],{"class":315},"  \u002F* Animating a custom property keeps the compressed state in one place. *\u002F\n",[299,2059,2061,2064,2066,2068],{"class":252,"line":2060},19,[299,2062,2063],{"class":377},"  --compress",[299,2065,349],{"class":308},[299,2067,403],{"class":304},[299,2069,340],{"class":308},[299,2071,2073],{"class":252,"line":2072},20,[299,2074,361],{"class":308},[299,2076,2078],{"class":252,"line":2077},21,[299,2079,368],{"emptyLinePlaceholder":367},[299,2081,2083,2086],{"class":252,"line":2082},22,[299,2084,2085],{"class":304},".chapter__title",[299,2087,309],{"class":308},[299,2089,2091,2094,2096,2098],{"class":252,"line":2090},23,[299,2092,2093],{"class":304},"  margin",[299,2095,349],{"class":308},[299,2097,403],{"class":304},[299,2099,340],{"class":308},[299,2101,2103,2106,2108,2111,2113],{"class":252,"line":2102},24,[299,2104,2105],{"class":304},"  font-size",[299,2107,349],{"class":308},[299,2109,2110],{"class":304},"1.6",[299,2112,1541],{"class":374},[299,2114,340],{"class":308},[299,2116,2118],{"class":252,"line":2117},25,[299,2119,2120],{"class":315},"  \u002F* transform, not font-size: font-size would relayout every frame. *\u002F\n",[299,2122,2124,2127,2129,2131,2133,2136,2138,2140,2143,2146,2149,2152,2154,2157],{"class":252,"line":2123},26,[299,2125,2126],{"class":304},"  transform",[299,2128,349],{"class":308},[299,2130,412],{"class":304},[299,2132,400],{"class":308},[299,2134,2135],{"class":304},"calc",[299,2137,400],{"class":308},[299,2139,256],{"class":304},[299,2141,2142],{"class":374}," -",[299,2144,2145],{"class":304}," 0.25",[299,2147,2148],{"class":374}," *",[299,2150,2151],{"class":304}," var",[299,2153,400],{"class":308},[299,2155,2156],{"class":377},"--compress",[299,2158,2159],{"class":308},")));\n",[299,2161,2163,2165,2167,2170,2173],{"class":252,"line":2162},27,[299,2164,842],{"class":304},[299,2166,349],{"class":308},[299,2168,2169],{"class":304},"left",[299,2171,2172],{"class":304}," center",[299,2174,340],{"class":308},[299,2176,2178],{"class":252,"line":2177},28,[299,2179,361],{"class":308},[299,2181,2183],{"class":252,"line":2182},29,[299,2184,368],{"emptyLinePlaceholder":367},[299,2186,2188,2191],{"class":252,"line":2187},30,[299,2189,2190],{"class":304},".chapter__meta",[299,2192,309],{"class":308},[299,2194,2196,2199,2201,2203,2205,2207,2209,2211,2213,2215],{"class":252,"line":2195},31,[299,2197,2198],{"class":304},"  opacity",[299,2200,349],{"class":308},[299,2202,2135],{"class":304},[299,2204,400],{"class":308},[299,2206,256],{"class":304},[299,2208,2142],{"class":374},[299,2210,2151],{"class":304},[299,2212,400],{"class":308},[299,2214,2156],{"class":377},[299,2216,2217],{"class":308},"));\n",[299,2219,2221],{"class":252,"line":2220},32,[299,2222,361],{"class":308},[299,2224,2226],{"class":252,"line":2225},33,[299,2227,368],{"emptyLinePlaceholder":367},[299,2229,2231,2233,2235,2237],{"class":252,"line":2230},34,[299,2232,97],{"class":374},[299,2234,1645],{"class":308},[299,2236,39],{"class":304},[299,2238,1650],{"class":308},[299,2240,2242,2245],{"class":252,"line":2241},35,[299,2243,2244],{"class":304},"  .chapter__bar",[299,2246,309],{"class":308},[299,2248,2250,2252,2255,2257,2259],{"class":252,"line":2249},36,[299,2251,1662],{"class":304},[299,2253,2254],{"class":308},": compress ",[299,2256,334],{"class":304},[299,2258,337],{"class":304},[299,2260,340],{"class":308},[299,2262,2264,2266],{"class":252,"line":2263},37,[299,2265,1675],{"class":304},[299,2267,1917],{"class":308},[299,2269,2271],{"class":252,"line":2270},38,[299,2272,2273],{"class":315},"    \u002F* Only compress while the section is actually being read: from the\n",[299,2275,2277],{"class":252,"line":2276},39,[299,2278,2279],{"class":315},"       moment it fully covers the scrollport to the moment it starts leaving. *\u002F\n",[299,2281,2283,2285,2287,2289,2291,2293,2296,2298,2300],{"class":252,"line":2282},40,[299,2284,1682],{"class":304},[299,2286,349],{"class":308},[299,2288,230],{"class":304},[299,2290,1281],{"class":304},[299,2292,852],{"class":374},[299,2294,2295],{"class":308}," exit ",[299,2297,1051],{"class":304},[299,2299,852],{"class":374},[299,2301,340],{"class":308},[299,2303,2305],{"class":252,"line":2304},41,[299,2306,1701],{"class":308},[299,2308,2310],{"class":252,"line":2309},42,[299,2311,368],{"emptyLinePlaceholder":367},[299,2313,2315,2318,2321],{"class":252,"line":2314},43,[299,2316,2317],{"class":374},"  @keyframes",[299,2319,2320],{"class":377}," compress",[299,2322,309],{"class":308},[299,2324,2326,2329,2331,2333,2335,2337,2339,2342,2344,2346,2348,2350,2353,2355,2357,2359,2361,2363,2365,2367,2369],{"class":252,"line":2325},44,[299,2327,2328],{"class":304},"    from",[299,2330,389],{"class":308},[299,2332,2156],{"class":377},[299,2334,349],{"class":308},[299,2336,403],{"class":304},[299,2338,1039],{"class":308},[299,2340,2341],{"class":304},"box-shadow",[299,2343,349],{"class":308},[299,2345,403],{"class":304},[299,2347,1281],{"class":304},[299,2349,1281],{"class":304},[299,2351,2352],{"class":304}," rgba",[299,2354,400],{"class":308},[299,2356,403],{"class":304},[299,2358,498],{"class":308},[299,2360,403],{"class":304},[299,2362,498],{"class":308},[299,2364,403],{"class":304},[299,2366,498],{"class":308},[299,2368,403],{"class":304},[299,2370,420],{"class":308},[299,2372,2374,2377,2379,2381,2383,2385,2387,2389,2391,2393,2396,2398,2401,2403,2405,2407,2409,2411,2413,2415,2417,2419,2422],{"class":252,"line":2373},45,[299,2375,2376],{"class":304},"    to",[299,2378,429],{"class":308},[299,2380,2156],{"class":377},[299,2382,349],{"class":308},[299,2384,256],{"class":304},[299,2386,1039],{"class":308},[299,2388,2341],{"class":304},[299,2390,349],{"class":308},[299,2392,403],{"class":304},[299,2394,2395],{"class":304}," 2",[299,2397,1054],{"class":374},[299,2399,2400],{"class":304}," 12",[299,2402,1054],{"class":374},[299,2404,2352],{"class":304},[299,2406,400],{"class":308},[299,2408,403],{"class":304},[299,2410,498],{"class":308},[299,2412,403],{"class":304},[299,2414,498],{"class":308},[299,2416,403],{"class":304},[299,2418,498],{"class":308},[299,2420,2421],{"class":304},"0.18",[299,2423,420],{"class":308},[299,2425,2427],{"class":252,"line":2426},46,[299,2428,1701],{"class":308},[299,2430,2432],{"class":252,"line":2431},47,[299,2433,361],{"class":308},[14,2435,2436,2437,2439,2440,2443,2444,2448,2449,2452],{},"Two details are doing real work. First, the animation targets ",[18,2438,2156],{}," rather than ",[18,2441,2442],{},"font-size",", so a single interpolated scalar feeds several visual properties; interpolating a custom property at all requires it to be registered, which is covered in ",[32,2445,2447],{"href":2446},"\u002Fcss-only-micro-interactions-animations\u002Fcss-custom-properties-architecture\u002Fanimating-custom-properties-with-at-property\u002F","animating custom properties with @property",". Second, the range is ",[18,2450,2451],{},"contain 0% exit 30%"," — the header compresses only while its section genuinely occupies the screen, so a short section never triggers a compression the reader has no time to perceive.",[111,2454],{},[114,2456,2458],{"id":2457},"performance-and-accessibility-notes","Performance and Accessibility Notes",[14,2460,2461],{},"The performance claim for scroll-driven animations is specific, and it is worth being precise about it because the marketing version overstates it.",[14,2463,2464,2465,2468],{},"What is genuinely off the main thread: the ",[27,2466,2467],{},"timeline evaluation",". The compositor already knows the scroll offset — it is the thing producing the scroll. Sampling a scroll progress timeline is arithmetic the compositor can do on its own thread, in the same frame as the scroll itself. A JavaScript scroll listener, by contrast, is dispatched to the main thread, which means the effect is always at least one frame behind the scroll and stops entirely whenever a long task blocks that thread. This is the difference readers feel: a JS progress bar visibly lags a fast flick; a CSS one does not.",[14,2470,2471,2472,2475,2476,498,2479,498,2482,2485,2486,2488,2489,2492,2493,498,2495,2497,2498,2501,2502,284],{},"What is ",[27,2473,2474],{},"not"," automatically off the main thread: the animated properties themselves. If your keyframes animate ",[18,2477,2478],{},"width",[18,2480,2481],{},"height",[18,2483,2484],{},"top",", or ",[18,2487,2442],{},", the compositor must hand the frame back to the main thread for layout, and you have reintroduced the jank you were trying to avoid — with a mechanism that now runs on ",[27,2490,2491],{},"every"," scroll frame rather than a throttled subset. Restrict scroll-driven keyframes to ",[18,2494,392],{},[18,2496,1032],{},", and ",[18,2499,2500],{},"filter",", exactly as you would for time-based animation. The reasoning behind that shortlist is laid out in ",[32,2503,2505],{"href":2504},"\u002Fcss-only-micro-interactions-animations\u002Fperformance-gpu-acceleration\u002Fwill-change-and-the-compositor-thread\u002F","will-change and the compositor thread",[14,2507,2508],{},"Animating a registered custom property, as in the example above, is a partial exception: the interpolation is cheap but the properties it feeds must still be compositor-friendly for the whole chain to stay off the main thread.",[14,2510,2511,2512,2515,2516,2519,2520,2522,2523,2526],{},"The accessibility obligation is heavier here than for ordinary animation, and it is not optional. Motion that is ",[27,2513,2514],{},"coupled to the scroll gesture"," is one of the strongest vestibular triggers there is, because the visual field moves in a way that contradicts what the user's proprioception predicts from their scroll input. Parallax — where different layers move at different rates — is the worst offender, and WCAG treats it as a genuine accessibility barrier rather than a taste question. Every scroll-driven effect that changes an element's ",[27,2517,2518],{},"position"," or ",[27,2521,412],{}," needs a ",[18,2524,2525],{},"prefers-reduced-motion: reduce"," branch that removes the positional component:",[290,2528,2530],{"className":292,"code":2529,"language":294,"meta":295,"style":295},"@media (prefers-reduced-motion: reduce) {\n  .reveal {\n    \u002F* Kill the timeline binding outright; the element is simply present. *\u002F\n    animation-timeline: none;\n    animation: none;\n    opacity: 1;\n    transform: none;\n  }\n}\n",[18,2531,2532,2540,2546,2551,2561,2571,2582,2593,2597],{"__ignoreMap":295},[299,2533,2534,2537],{"class":252,"line":301},[299,2535,2536],{"class":374},"@media",[299,2538,2539],{"class":308}," (prefers-reduced-motion: reduce) {\n",[299,2541,2542,2544],{"class":252,"line":312},[299,2543,1655],{"class":304},[299,2545,309],{"class":308},[299,2547,2548],{"class":252,"line":319},[299,2549,2550],{"class":315},"    \u002F* Kill the timeline binding outright; the element is simply present. *\u002F\n",[299,2552,2553,2555,2557,2559],{"class":252,"line":325},[299,2554,1675],{"class":304},[299,2556,349],{"class":308},[299,2558,217],{"class":304},[299,2560,340],{"class":308},[299,2562,2563,2565,2567,2569],{"class":252,"line":343},[299,2564,1662],{"class":304},[299,2566,349],{"class":308},[299,2568,217],{"class":304},[299,2570,340],{"class":308},[299,2572,2573,2576,2578,2580],{"class":252,"line":358},[299,2574,2575],{"class":304},"    opacity",[299,2577,349],{"class":308},[299,2579,256],{"class":304},[299,2581,340],{"class":308},[299,2583,2584,2587,2589,2591],{"class":252,"line":364},[299,2585,2586],{"class":304},"    transform",[299,2588,349],{"class":308},[299,2590,217],{"class":304},[299,2592,340],{"class":308},[299,2594,2595],{"class":252,"line":371},[299,2596,1701],{"class":308},[299,2598,2599],{"class":252,"line":383},[299,2600,361],{"class":308},[14,2602,2603,2604,2607,2608,2612],{},"Note that opacity-only scroll effects are usually acceptable under reduced motion, since nothing traverses the visual field — but a progress ",[27,2605,2606],{},"indicator"," that shrinks to nothing is not, because you have removed information. Prefer keeping the indicator and dropping the animation. The ",[32,2609,2611],{"href":2610},"\u002Fcss-only-micro-interactions-animations\u002Faccessibility-in-css-animations\u002Fvestibular-safe-animation-patterns\u002F","vestibular-safe animation patterns"," page catalogues which transformations are safe to keep.",[14,2614,2615,2616,2619,2620,284],{},"One more accessibility trap: never put content behind a scroll-driven reveal that starts at ",[18,2617,2618],{},"opacity: 0"," without a fallback. If the timeline never advances — because the container turned out not to be scrollable, because the user is on a browser without support, or because a print stylesheet flattened the page — that content is permanently invisible. Reveals must degrade to ",[27,2621,108],{},[111,2623],{},[114,2625,2627],{"id":2626},"devtools-debugging-workflow","DevTools Debugging Workflow",[14,2629,2630],{},"Chrome DevTools has first-class tooling for this, and it removes most of the guesswork.",[2632,2633,2634,2643,2658,2670,2676,2682],"ol",{},[67,2635,2636,2639,2640,2642],{},[60,2637,2638],{},"Open the Animations panel"," (Command Menu → \"Show Animations\"). Scroll-driven animations appear there with a ",[27,2641,352],{}," icon instead of a clock, and the panel shows the timeline's current progress as a percentage. If your animation does not appear at all, the timeline failed to resolve and the animation is silently inert.",[67,2644,2645,2648,2649,2651,2652,2654,2655,2657],{},[60,2646,2647],{},"Check for an inactive timeline."," In the Elements panel, an ",[18,2650,39],{}," whose name resolves to nothing gets no warning by default — but the Styles pane greys out the computed ",[18,2653,75],{}," block. The most common cause is a ",[18,2656,606],{}," declared on an element that is not actually a scroll container, or a name referenced from outside the declaring element's subtree.",[67,2659,2660,2663,2664,2666,2667,2669],{},[60,2661,2662],{},"Verify the scroller is scrollable."," In the Elements panel, a scroll container is badged ",[18,2665,352],{},". ",[18,2668,764],{}," binding to an unexpected ancestor is a frequent surprise; hover the badge to confirm which element the browser considers the scroller.",[67,2671,2672,2675],{},[60,2673,2674],{},"Scrub with the Animations panel timeline."," Dragging the playhead drives a scroll-driven animation the same way it drives a time-based one, so you can inspect any point of the range without touching the scrollbar.",[67,2677,2678,2681],{},[60,2679,2680],{},"Confirm compositing"," in the Performance panel: record a scroll, and look at the \"Compositor\" track. A correctly built scroll-driven animation shows compositor frames with no accompanying Layout or Recalculate Style entries. Any purple Layout bars mean a keyframe is touching a layout-inducing property.",[67,2683,2684,2687,2688,2691],{},[60,2685,2686],{},"Test with the emulated motion preference."," Rendering tab → \"Emulate CSS media feature prefers-reduced-motion\" → ",[18,2689,2690],{},"reduce",", then scroll the page again and confirm every positional effect is gone.",[111,2693],{},[114,2695,2697],{"id":2696},"browser-compatibility","Browser Compatibility",[465,2699,2700,2716],{},[468,2701,2702],{},[471,2703,2704,2707,2710,2713],{},[474,2705,2706],{},"Feature",[474,2708,2709],{},"Chrome \u002F Edge",[474,2711,2712],{},"Safari",[474,2714,2715],{},"Firefox",[484,2717,2718,2736,2750,2765,2782,2795],{},[471,2719,2720,2727,2730,2733],{},[489,2721,2722,2724,2725],{},[18,2723,39],{}," with ",[18,2726,43],{},[489,2728,2729],{},"115+",[489,2731,2732],{},"26+",[489,2734,2735],{},"Behind a flag",[471,2737,2738,2744,2746,2748],{},[489,2739,2740,2724,2742],{},[18,2741,39],{},[18,2743,47],{},[489,2745,2729],{},[489,2747,2732],{},[489,2749,2735],{},[471,2751,2752,2759,2761,2763],{},[489,2753,2754,2756,2757],{},[18,2755,606],{}," \u002F ",[18,2758,644],{},[489,2760,2729],{},[489,2762,2732],{},[489,2764,2735],{},[471,2766,2767,2776,2778,2780],{},[489,2768,2769,2756,2771,2756,2773],{},[18,2770,51],{},[18,2772,696],{},[18,2774,2775],{},"-end",[489,2777,2729],{},[489,2779,2732],{},[489,2781,2735],{},[471,2783,2784,2788,2791,2793],{},[489,2785,2786],{},[18,2787,678],{},[489,2789,2790],{},"116+",[489,2792,2732],{},[489,2794,2735],{},[471,2796,2797,2802,2804,2806],{},[489,2798,2799],{},[18,2800,2801],{},"@supports (animation-timeline: scroll())",[489,2803,2729],{},[489,2805,2732],{},[489,2807,2808],{},"Reports false while unflagged",[14,2810,2811,2812,2815,2816,2818,2819,2822],{},"As of mid-2026 Firefox ships the implementation behind ",[18,2813,2814],{},"layout.css.scroll-driven-animations.enabled",", so it must be treated as unsupported for production purposes — the useful consequence is that ",[18,2817,97],{}," correctly reports ",[18,2820,2821],{},"false"," there, so a properly gated enhancement does the right thing without any browser sniffing. Do not attempt to polyfill this with a script for parity; a JavaScript polyfill reinstates exactly the main-thread cost the feature exists to remove, and the ungated fallback is almost always acceptable. If you need a rule of thumb: treat scroll-driven animation as decoration for roughly three quarters of traffic and design the other quarter's experience first.",[111,2824],{},[114,2826,2828],{"id":2827},"common-pitfalls","Common Pitfalls",[465,2830,2831,2844],{},[468,2832,2833],{},[471,2834,2835,2838,2841],{},[474,2836,2837],{},"Pitfall",[474,2839,2840],{},"Cause",[474,2842,2843],{},"Resolution",[484,2845,2846,2865,2882,2902,2927,2943],{},[471,2847,2848,2851,2859],{},[489,2849,2850],{},"Element snaps to its end state instantly, no scroll response",[489,2852,2853,2855,2856,2858],{},[18,2854,39],{}," unsupported or unresolved, so the animation ran on the document timeline with a ",[18,2857,1614],{}," duration",[489,2860,2861,2862,2864],{},"Wrap in ",[18,2863,2801],{}," and give the base state sane values",[471,2866,2867,2870,2876],{},[489,2868,2869],{},"Motion runs ahead of the finger, then drags behind",[489,2871,2872,2873,2875],{},"The default ",[18,2874,956],{}," timing function is distorting the scroll-to-progress mapping",[489,2877,2878,2879,2881],{},"Always declare ",[18,2880,334],{},"; express curvature with keyframe percentage stops",[471,2883,2884,2887,2896],{},[489,2885,2886],{},"Reveal plays forwards on entry and backwards on exit",[489,2888,2889,2890,2892,2893,2895],{},"No ",[18,2891,51],{},", so the animation spans the full ",[18,2894,193],{}," range",[489,2897,2898,2899,2901],{},"Set ",[18,2900,808],{}," (or similar) so it completes early",[471,2903,2904,2907,2917],{},[489,2905,2906],{},"A card flashes at full opacity before animating",[489,2908,2909,2912,2913,2916],{},[18,2910,2911],{},"animation-fill-mode"," is missing, so the ",[18,2914,2915],{},"from"," keyframe is not applied before the range starts",[489,2918,2919,2920,2923,2924,2926],{},"Add ",[18,2921,2922],{},"both"," to the ",[18,2925,75],{}," shorthand",[471,2928,2929,2935,2938],{},[489,2930,2931,2934],{},[18,2932,2933],{},"animation-timeline: --name"," does nothing",[489,2936,2937],{},"The name is declared on an element outside the referencing element's resolution scope",[489,2939,2940,2941],{},"Move the declaration to an ancestor, or hoist it with ",[18,2942,678],{},[471,2944,2945,2948,2951],{},[489,2946,2947],{},"Effect works on desktop, freezes on mobile",[489,2949,2950],{},"The animated property forces layout, so the compositor cannot run it during a fling",[489,2952,2953,2954,498,2956,2497,2958],{},"Restrict keyframes to ",[18,2955,392],{},[18,2957,1032],{},[18,2959,2500],{},[111,2961],{},[114,2963,2965],{"id":2964},"faq","FAQ",[14,2967,2968,2971,2973,2974,2976,2977,2979,2980,2982],{},[60,2969,2970],{},"What is the difference between scroll() and view()?",[18,2972,43],{}," tracks how far a scroll container has been scrolled, from its start to its maximum offset. ",[18,2975,47],{}," tracks one specific element's own passage through that scrollport, so each element gets its own 0 to 100 percent. Use ",[18,2978,43],{}," for document-wide indicators and ",[18,2981,47],{}," for per-element reveals.",[14,2984,2985,2988,2989,2991,2992,2995,2996,2998],{},[60,2986,2987],{},"Why does my scroll-driven animation snap instead of interpolating?","\nAlmost always because the animation still has an easing function other than ",[18,2990,334],{},". A scroll timeline maps scroll position onto animation progress, and any non-linear timing function then distorts that mapping. Set ",[18,2993,2994],{},"animation-timing-function"," to ",[18,2997,334],{}," and shape the curve with keyframe stops instead.",[14,3000,3001,3004,3005,498,3007,2497,3009,3011],{},[60,3002,3003],{},"Do scroll-driven animations run off the main thread?","\nYes, when the animated properties are compositor-friendly ones such as ",[18,3006,392],{},[18,3008,1032],{},[18,3010,2500],{},". The timeline itself is evaluated by the compositor, so the effect keeps pace with the scroll gesture even while the main thread is busy. Animating layout-inducing properties forces the work back onto the main thread.",[14,3013,3014,3017,3018,3020],{},[60,3015,3016],{},"Does animation-duration do anything on a scroll timeline?","\nNo. Progress comes from scroll position, not elapsed time, so the declared duration is ignored for the mapping. It is still worth writing a value because it is what a browser without scroll-timeline support will fall back to when the declaration is not inside an ",[18,3019,97],{}," block.",[111,3022],{},[114,3024,3026],{"id":3025},"related","Related",[64,3028,3029,3034,3044,3054,3061],{},[67,3030,3031,3033],{},[32,3032,35],{"href":34}," — the parent guide covering transitions, keyframes, state design, and motion accessibility.",[67,3035,3036,3040,3041,3043],{},[32,3037,3039],{"href":3038},"\u002Fcss-only-micro-interactions-animations\u002Fscroll-driven-animations\u002Fscroll-progress-bar-without-javascript\u002F","Scroll progress bar without JavaScript"," — the smallest complete ",[18,3042,760],{}," build, next to the listener it replaces.",[67,3045,3046,3050,3051,3053],{},[32,3047,3049],{"href":3048},"\u002Fcss-only-micro-interactions-animations\u002Fscroll-driven-animations\u002Fscroll-triggered-reveal-animations\u002F","Scroll-triggered reveal animations"," — ",[18,3052,47],{}," reveals compared against IntersectionObserver.",[67,3055,3056,3060],{},[32,3057,3059],{"href":3058},"\u002Fcss-only-micro-interactions-animations\u002Fcss-animation-vs-web-animations-api\u002Fwhen-to-use-javascript-animation\u002F","When to use JavaScript animation"," — where the declarative model genuinely runs out and script is the right tool.",[67,3062,3063,3067],{},[32,3064,3066],{"href":3065},"\u002Fmastering-container-queries-responsive-layouts\u002Fcontainer-query-fallbacks\u002Ffeature-detection-with-supports\u002F","Feature detection with @supports"," — the detection discipline these enhancements depend on.",[3069,3070,3071],"style",{},"html pre.shiki code .s-5SL, html code.shiki .s-5SL{--shiki-default:#023B95;--shiki-dark:#91CBFF}html pre.shiki code .suds8, html code.shiki .suds8{--shiki-default:#0E1116;--shiki-dark:#F0F3F6}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);}html pre.shiki code .sne4z, html code.shiki .sne4z{--shiki-default:#024C1A;--shiki-dark:#72F088}html pre.shiki code .sT6z2, html code.shiki .sT6z2{--shiki-default:#032563;--shiki-dark:#ADDCFF}",{"title":295,"searchDepth":312,"depth":312,"links":3073},[3074,3075,3076,3086,3087,3088,3089,3090,3091,3092],{"id":116,"depth":312,"text":117},{"id":462,"depth":312,"text":463},{"id":814,"depth":312,"text":815,"children":3077},[3078,3079,3081,3083,3084],{"id":819,"depth":319,"text":820},{"id":963,"depth":319,"text":3080},"Step 2 — Switch to a per-element timeline with view()",{"id":1099,"depth":319,"text":3082},"Step 3 — Constrain the play window with animation-range",{"id":1389,"depth":319,"text":1390},{"id":1598,"depth":319,"text":3085},"Step 5 — Gate the whole thing behind @supports",{"id":1717,"depth":312,"text":1718},{"id":2457,"depth":312,"text":2458},{"id":2626,"depth":312,"text":2627},{"id":2696,"depth":312,"text":2697},{"id":2827,"depth":312,"text":2828},{"id":2964,"depth":312,"text":2965},{"id":3025,"depth":312,"text":3026},"A practitioner guide to CSS scroll-driven animations: animation-timeline, the scroll() and view() timelines, animation-range, and off-main-thread motion.","md",{"pageTitle":3096,"datePublished":3097,"dateModified":3097,"faq":3098},"Scroll-Driven Animations in CSS","2026-07-20",[3099,3101,3103,3105],{"q":2970,"a":3100},"scroll() tracks how far a scroll container has been scrolled, from its start to its maximum offset. view() tracks one specific element's own passage through that scrollport, so each element gets its own 0 to 100 percent. Use scroll() for document-wide indicators and view() for per-element reveals.",{"q":2987,"a":3102},"Almost always because the animation still has an easing function other than linear. A scroll timeline maps scroll position onto animation progress, and any non-linear timing function then distorts that mapping. Set animation-timing-function to linear and shape the curve with keyframe stops instead.",{"q":3003,"a":3104},"Yes, when the animated properties are compositor-friendly ones such as transform, opacity, and filter. The timeline itself is evaluated by the compositor, so the effect keeps pace with the scroll gesture even while the main thread is busy. Animating layout-inducing properties forces the work back onto the main thread.",{"q":3016,"a":3106},"No. Progress comes from scroll position, not elapsed time, so the declared duration is ignored for the mapping. It is still worth writing a value because it is what a browser without scroll-timeline support will fall back to when the declaration is not inside an @supports block.","\u002Fcss-only-micro-interactions-animations\u002Fscroll-driven-animations",{"title":5,"description":3093},"css-only-micro-interactions-animations\u002Fscroll-driven-animations\u002Findex","eCSdIqzd5Svhgs4x4cVTekmuxc0CxY4d2JTHaasW3Cw",[3112,3115,3118,3121,3124,3127,3130,3133,3136,3139,3142,3145,3148,3151,3154,3157,3160,3163,3166,3169,3172,3175,3178,3181,3184,3187,3190,3193,3196,3199,3202,3205,3208,3211,3214,3217,3220,3223,3226,3227,3230,3233,3236,3239,3242,3245,3248,3251,3254,3257,3260,3263,3266,3269,3272,3275,3278,3281,3284,3287,3290,3293,3296,3299,3302,3305,3308,3311,3314,3317,3320,3323,3326,3329,3332,3335,3338,3341,3344,3347,3350,3353,3356,3359,3362,3365,3368,3371],{"path":3113,"title":3114},"\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":3116,"title":3117},"\u002Fcss-only-micro-interactions-animations\u002Faccessibility-in-css-animations","Accessibility in CSS Animations: Patterns, Specs & Best Practices",{"path":3119,"title":3120},"\u002Fcss-only-micro-interactions-animations\u002Faccessibility-in-css-animations\u002Fprefers-reduced-motion-recipes","prefers-reduced-motion Recipes: A Reduction for Every Common Effect",{"path":3122,"title":3123},"\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":3125,"title":3126},"\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":3128,"title":3129},"\u002Fcss-only-micro-interactions-animations\u002Faccessibility-in-css-animations\u002Fvestibular-safe-animation-patterns","Vestibular-Safe Animation Patterns: Why Some Motion Makes People Sick",{"path":3131,"title":3132},"\u002Fcss-only-micro-interactions-animations\u002Faccessibility-in-css-animations\u002Fwcag-motion-success-criteria","WCAG Motion Success Criteria: What Each One Actually Requires",{"path":3134,"title":3135},"\u002Fcss-only-micro-interactions-animations\u002Fcontainer-aware-motion\u002Fcontainer-query-hover-affordances","Container Query Hover Affordances: When Narrow Means Persistent, Not Hidden",{"path":3137,"title":3138},"\u002Fcss-only-micro-interactions-animations\u002Fcontainer-aware-motion","Container-Aware Motion: Animation Sized by the Space a Component Actually Has",{"path":3140,"title":3141},"\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":3143,"title":3144},"\u002Fcss-only-micro-interactions-animations\u002Fcontainer-aware-motion\u002Fsuppressing-motion-in-small-containers","Suppressing Motion in Small Containers: Deliberately Animating Nothing",{"path":3146,"title":3147},"\u002Fcss-only-micro-interactions-animations\u002Fcss-animation-vs-web-animations-api","CSS Animation vs the Web Animations API: A Decision Guide",{"path":3149,"title":3150},"\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":3152,"title":3153},"\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":3155,"title":3156},"\u002Fcss-only-micro-interactions-animations\u002Fcss-custom-properties-architecture\u002Ffluid-spacing-tokens-driving-transition-durations","Fluid Spacing Tokens That Drive Transition Durations",{"path":3158,"title":3159},"\u002Fcss-only-micro-interactions-animations\u002Fcss-custom-properties-architecture","CSS Custom Properties Architecture: Scalable Design Systems & Dynamic UI",{"path":3161,"title":3162},"\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":3164,"title":3165},"\u002Fcss-only-micro-interactions-animations\u002Fcss-transition-fundamentals\u002Fcss-transition-timing-functions","CSS Transition Timing Functions: ease, cubic-bezier(), steps() and linear()",{"path":3167,"title":3168},"\u002Fcss-only-micro-interactions-animations\u002Fcss-transition-fundamentals","CSS Transition Fundamentals: Architecture, Performance & Patterns",{"path":3170,"title":3171},"\u002Fcss-only-micro-interactions-animations\u002Fcss-transition-fundamentals\u002Fstarting-style-entry-animations","@starting-style: Animating an Element's Very First Style Change",{"path":3173,"title":3174},"\u002Fcss-only-micro-interactions-animations\u002Fcss-transition-fundamentals\u002Ftransitioning-display-with-allow-discrete","Transitioning display with transition-behavior: allow-discrete and @starting-style",{"path":3176,"title":3177},"\u002Fcss-only-micro-interactions-animations\u002Fhover-focus-state-design\u002Faccessible-css-only-tooltips","Accessible CSS-Only Tooltips and Hover Cards With  and ",{"path":3179,"title":3180},"\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":3182,"title":3183},"\u002Fcss-only-micro-interactions-animations\u002Fhover-focus-state-design\u002Ffocus-within-form-patterns"," Form Patterns: Styling a Group From the Field Inside It",{"path":3185,"title":3186},"\u002Fcss-only-micro-interactions-animations\u002Fhover-focus-state-design","Hover & Focus State Design: Spec-Compliant Patterns for Modern UIs",{"path":3188,"title":3189},"\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":3191,"title":3192},"\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":3194,"title":3195},"\u002Fcss-only-micro-interactions-animations","CSS-Only Micro-Interactions & Animations: Architecture, Performance & Implementation",{"path":3197,"title":3198},"\u002Fcss-only-micro-interactions-animations\u002Fkeyframe-animation-patterns\u002Fcontainer-query-triggered-keyframe-animations","Container-Query-Triggered Keyframe Animations: Animate Differently per Available Space",{"path":3200,"title":3201},"\u002Fcss-only-micro-interactions-animations\u002Fkeyframe-animation-patterns\u002Fcss-only-accordions-and-disclosure","CSS-Only Accordions and Disclosure Widgets with details and summary",{"path":3203,"title":3204},"\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":3206,"title":3207},"\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":3209,"title":3210},"\u002Fcss-only-micro-interactions-animations\u002Fkeyframe-animation-patterns","Keyframe Animation Patterns: Spec-Compliant Architectures for Modern UIs",{"path":3212,"title":3213},"\u002Fcss-only-micro-interactions-animations\u002Fkeyframe-animation-patterns\u002Fstaggered-list-animations-with-custom-properties","Staggered List Animations Using --i Index Custom Properties",{"path":3215,"title":3216},"\u002Fcss-only-micro-interactions-animations\u002Fperformance-gpu-acceleration","Performance & GPU Acceleration in CSS: A Developer’s Blueprint",{"path":3218,"title":3219},"\u002Fcss-only-micro-interactions-animations\u002Fperformance-gpu-acceleration\u002Foptimizing-css-animations-for-60fps","Optimizing CSS Animations for 60fps: Budgeting the Frame",{"path":3221,"title":3222},"\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":3224,"title":3225},"\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":3107,"title":5},{"path":3228,"title":3229},"\u002Fcss-only-micro-interactions-animations\u002Fscroll-driven-animations\u002Fscroll-driven-animation-fallbacks","Shipping Scroll-Driven Animations Without Breaking Anything",{"path":3231,"title":3232},"\u002Fcss-only-micro-interactions-animations\u002Fscroll-driven-animations\u002Fscroll-progress-bar-without-javascript","A Reading Progress Bar Driven by scroll(root)",{"path":3234,"title":3235},"\u002Fcss-only-micro-interactions-animations\u002Fscroll-driven-animations\u002Fscroll-triggered-reveal-animations","Reveal-on-Scroll with view() and animation-range",{"path":3237,"title":3238},"\u002Fcss-only-micro-interactions-animations\u002Fview-transitions-for-css-developers","View Transitions for CSS Developers: the Pseudo-Element Tree, Snapshots, and Named Elements",{"path":3240,"title":3241},"\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":3243,"title":3244},"\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":3246,"title":3247},"\u002Fcss-only-micro-interactions-animations\u002Fview-transitions-for-css-developers\u002Fview-transitions-with-reduced-motion","Honouring prefers-reduced-motion in View Transitions",{"path":3249,"title":3250},"\u002F","Modern CSS Layouts & Micro-Interactions",{"path":3252,"title":3253},"\u002Fmastering-container-queries-responsive-layouts\u002Fanchor-positioning-and-overlays\u002Fanchor-positioned-tooltips","Anchor-Positioned Tooltips: Tethering, Flipping, and Getting the Semantics Right",{"path":3255,"title":3256},"\u002Fmastering-container-queries-responsive-layouts\u002Fanchor-positioning-and-overlays\u002Fanchor-positioning-fallbacks","Anchor Positioning Fallbacks: A Graceful Floor for Older Browser Versions",{"path":3258,"title":3259},"\u002Fmastering-container-queries-responsive-layouts\u002Fanchor-positioning-and-overlays","CSS Anchor Positioning and Overlays: Tethering Elements Without JavaScript",{"path":3261,"title":3262},"\u002Fmastering-container-queries-responsive-layouts\u002Fanchor-positioning-and-overlays\u002Fpopover-attribute-and-css-styling","The popover Attribute: Top-Layer Overlays With No JavaScript",{"path":3264,"title":3265},"\u002Fmastering-container-queries-responsive-layouts\u002Fcontainer-query-fallbacks\u002Ffeature-detection-with-supports","Feature Detection with @supports: Syntax, Semantics, and the Traps",{"path":3267,"title":3268},"\u002Fmastering-container-queries-responsive-layouts\u002Fcontainer-query-fallbacks\u002Fhandling-container-query-fallbacks-for-older-browsers","Handling Container Query Fallbacks for Older Browsers",{"path":3270,"title":3271},"\u002Fmastering-container-queries-responsive-layouts\u002Fcontainer-query-fallbacks","Container Query Fallbacks: Spec-Compliant CSS Strategies for Legacy Browsers",{"path":3273,"title":3274},"\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":3276,"title":3277},"\u002Fmastering-container-queries-responsive-layouts\u002Fcontainer-query-syntax-basics\u002Fcontainer-type-size-vs-inline-size","Choosing container-type: inline-size, size, or normal",{"path":3279,"title":3280},"\u002Fmastering-container-queries-responsive-layouts\u002Fcontainer-query-syntax-basics\u002Fcontainer-vs-media-queries-comparison","Container vs Media Queries: Choosing the Right Reference Box",{"path":3282,"title":3283},"\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":3285,"title":3286},"\u002Fmastering-container-queries-responsive-layouts\u002Fcontainer-query-syntax-basics","Container Query Syntax Basics",{"path":3288,"title":3289},"\u002Fmastering-container-queries-responsive-layouts\u002Fcontainer-query-syntax-basics\u002Fnesting-and-naming-container-queries","Nesting and Naming Container Queries: Targeting the Right Ancestor",{"path":3291,"title":3292},"\u002Fmastering-container-queries-responsive-layouts\u002Fcss-grid-and-subgrid-layouts\u002Fauto-fit-minmax-responsive-grids","Responsive Grids with repeat(auto-fit, minmax())",{"path":3294,"title":3295},"\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":3297,"title":3298},"\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":3300,"title":3301},"\u002Fmastering-container-queries-responsive-layouts\u002Fcss-grid-and-subgrid-layouts","CSS Grid and Subgrid Layouts for Responsive Interfaces",{"path":3303,"title":3304},"\u002Fmastering-container-queries-responsive-layouts\u002Fcss-grid-and-subgrid-layouts\u002Fsubgrid-vs-nested-grid","Subgrid vs an Independently Nested Grid",{"path":3306,"title":3307},"\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":3309,"title":3310},"\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":3312,"title":3313},"\u002Fmastering-container-queries-responsive-layouts\u002Ffluid-typography-with-clamp\u002Ffluid-type-accessibility-and-zoom","Fluid Type, Accessibility and Zoom: The vw-Only Trap",{"path":3315,"title":3316},"\u002Fmastering-container-queries-responsive-layouts\u002Ffluid-typography-with-clamp\u002Ffluid-typography-without-javascript","Fluid Typography Without JavaScript: A Precision CSS Reference",{"path":3318,"title":3319},"\u002Fmastering-container-queries-responsive-layouts\u002Ffluid-typography-with-clamp","Fluid Typography with clamp(): A Practical Guide for Modern CSS",{"path":3321,"title":3322},"\u002Fmastering-container-queries-responsive-layouts","Mastering Container Queries & Responsive Layouts",{"path":3324,"title":3325},"\u002Fmastering-container-queries-responsive-layouts\u002Fintrinsic-sizing-techniques\u002Faspect-ratio-for-responsive-media","Using aspect-ratio for Responsive Media and Preventing Layout Shift",{"path":3327,"title":3328},"\u002Fmastering-container-queries-responsive-layouts\u002Fintrinsic-sizing-techniques","Intrinsic Sizing Techniques: Modern CSS Layouts for Responsive UI",{"path":3330,"title":3331},"\u002Fmastering-container-queries-responsive-layouts\u002Fintrinsic-sizing-techniques\u002Fmin-max-fit-content-explained","min-content, max-content, and fit-content() Explained",{"path":3333,"title":3334},"\u002Fmastering-container-queries-responsive-layouts\u002Fintrinsic-sizing-techniques\u002Fpreventing-flex-and-grid-overflow","The min-width: auto Trap in Flex and Grid Layouts",{"path":3336,"title":3337},"\u002Fmastering-container-queries-responsive-layouts\u002Fmodern-css-reset-strategies\u002Fcascade-layers-for-reset-and-tokens","Cascade Layers for Reset and Design Tokens",{"path":3339,"title":3340},"\u002Fmastering-container-queries-responsive-layouts\u002Fmodern-css-reset-strategies\u002Fcascade-layers-vs-specificity-hacks","Cascade Layers vs Specificity Hacks: Ordering Instead of Escalating",{"path":3342,"title":3343},"\u002Fmastering-container-queries-responsive-layouts\u002Fmodern-css-reset-strategies","Modern CSS Reset Strategies: A Spec-Compliant Foundation",{"path":3345,"title":3346},"\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":3348,"title":3349},"\u002Fmastering-container-queries-responsive-layouts\u002Fresponsive-component-patterns\u002Fcontainer-query-data-tables","Container Query Data Tables: Reflowing Tables to Cards",{"path":3351,"title":3352},"\u002Fmastering-container-queries-responsive-layouts\u002Fresponsive-component-patterns\u002Fcontainer-query-sidebar-layouts","Container Query Sidebar Layouts That Travel Between Contexts",{"path":3354,"title":3355},"\u002Fmastering-container-queries-responsive-layouts\u002Fresponsive-component-patterns","Responsive Component Patterns: Architecture & Implementation",{"path":3357,"title":3358},"\u002Fmastering-container-queries-responsive-layouts\u002Fresponsive-component-patterns\u002Fresponsive-forms-with-container-queries","Responsive Forms with Container Queries: Reflow Without Losing the Wiring",{"path":3360,"title":3361},"\u002Fmastering-container-queries-responsive-layouts\u002Fresponsive-component-patterns\u002Fresponsive-navigation-without-media-queries","Responsive Navigation Without Media Queries Using Container Queries",{"path":3363,"title":3364},"\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":3366,"title":3367},"\u002Fmastering-container-queries-responsive-layouts\u002Fstyle-queries-and-container-state","Style Queries and Container State: Token-Driven Component Variants",{"path":3369,"title":3370},"\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":3372,"title":3373},"\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",1784566156667]