[{"data":1,"prerenderedAt":1691},["ShallowReactive",2],{"page-\u002Fcss-only-micro-interactions-animations\u002Fscroll-driven-animations\u002Fscroll-driven-animation-fallbacks\u002F":3,"content-all-pages":1428},{"id":4,"title":5,"body":6,"description":1410,"extension":1411,"meta":1412,"navigation":488,"path":1424,"seo":1425,"stem":1426,"__hash__":1427},"content\u002Fcss-only-micro-interactions-animations\u002Fscroll-driven-animations\u002Fscroll-driven-animation-fallbacks\u002Findex.md","Shipping Scroll-Driven Animations Without Breaking Anything",{"type":7,"value":8,"toc":1399},"minimark",[9,13,28,31,36,39,120,135,155,158,240,247,354,356,360,368,660,663,687,696,706,714,719,731,735,738,740,744,750,753,761,764,858,877,880,884,997,999,1003,1006,1304,1311,1313,1317,1330,1343,1349,1355,1357,1361,1395],[10,11,5],"h1",{"id":12},"shipping-scroll-driven-animations-without-breaking-anything",[14,15,16,17,21,22,27],"p",{},"Scroll-driven animations are the rare CSS feature where \"it just degrades\" is not automatically true. The failure mode is not a missing effect — it is an element that snaps straight to its final keyframe, which for a reveal means content that was supposed to fade in appears instantly, and for a progress indicator means a bar that is permanently full and lying to the reader. On top of that sits an accessibility obligation that is stronger than for ordinary animation, because motion welded to the scroll gesture is a known vestibular trigger. This page covers both halves of shipping safely: ",[18,19,20],"code",{},"@supports (animation-timeline: scroll())"," gating, and what the reduced-motion branch actually has to remove. It assumes the mechanics from the ",[23,24,26],"a",{"href":25},"\u002Fcss-only-micro-interactions-animations\u002Fscroll-driven-animations\u002F","scroll-driven animations"," guide.",[29,30],"hr",{},[32,33,35],"h2",{"id":34},"what-an-unsupported-browser-actually-renders","What an Unsupported Browser Actually Renders",[14,37,38],{},"Be precise about the failure, because the fix depends on it. Consider an ungated reveal:",[40,41,46],"pre",{"className":42,"code":43,"language":44,"meta":45,"style":45},"language-css shiki shiki-themes github-light-high-contrast github-dark-high-contrast",".card {\n  animation: reveal linear both;\n  animation-timeline: view();\n  animation-range: entry 0% cover 40%;\n}\n","css","",[18,47,48,61,79,88,114],{"__ignoreMap":45},[49,50,53,57],"span",{"class":51,"line":52},"line",1,[49,54,56],{"class":55},"s-5SL",".card",[49,58,60],{"class":59},"suds8"," {\n",[49,62,64,67,70,73,76],{"class":51,"line":63},2,[49,65,66],{"class":55},"  animation",[49,68,69],{"class":59},": reveal ",[49,71,72],{"class":55},"linear",[49,74,75],{"class":55}," both",[49,77,78],{"class":59},";\n",[49,80,82,85],{"class":51,"line":81},3,[49,83,84],{"class":55},"  animation-timeline",[49,86,87],{"class":59},": view();\n",[49,89,91,94,97,100,104,107,110,112],{"class":51,"line":90},4,[49,92,93],{"class":55},"  animation-range",[49,95,96],{"class":59},": entry ",[49,98,99],{"class":55},"0",[49,101,103],{"class":102},"sHUrx","%",[49,105,106],{"class":55}," cover",[49,108,109],{"class":55}," 40",[49,111,103],{"class":102},[49,113,78],{"class":59},[49,115,117],{"class":51,"line":116},5,[49,118,119],{"class":59},"}\n",[14,121,122,123,126,127,130,131,134],{},"A browser without scroll-timeline support parses this declaration by declaration. ",[18,124,125],{},"animation-timeline"," and ",[18,128,129],{},"animation-range"," are unknown properties, so they are discarded at parse time — the cascade drops them silently, exactly as it drops any typo. But ",[18,132,133],{},"animation: reveal linear both"," is perfectly valid CSS that predates all of this. It is kept, and it runs on the document timeline.",[14,136,137,138,141,142,145,146,149,150,154],{},"Which duration? The shorthand did not declare one, so ",[18,139,140],{},"animation-duration"," resets to its initial value of ",[18,143,144],{},"0s",". A zero-duration animation with ",[18,147,148],{},"fill-mode: both"," completes on the first frame and holds its final keyframe forever. So the unsupported outcome is: ",[151,152,153],"strong",{},"the element appears immediately in its revealed state",".",[14,156,157],{},"For a reveal, that is genuinely fine — arguably better than the enhancement. For anything where the final keyframe is not the correct resting state, it is a bug:",[159,160,161,177],"table",{},[162,163,164],"thead",{},[165,166,167,171,174],"tr",{},[168,169,170],"th",{},"Effect",[168,172,173],{},"Final keyframe",[168,175,176],{},"Unsupported result",[178,179,180,194,207,218,229],"tbody",{},[165,181,182,186,191],{},[183,184,185],"td",{},"Fade-and-rise reveal",[183,187,188],{},[18,189,190],{},"opacity: 1; translateY(0)",[183,192,193],{},"Correct — content simply appears",[165,195,196,199,204],{},[183,197,198],{},"Progress bar",[183,200,201],{},[18,202,203],{},"scaleX(1)",[183,205,206],{},"Permanently full bar, actively misleading",[165,208,209,212,215],{},[183,210,211],{},"Compressing sticky header",[183,213,214],{},"fully compressed",[183,216,217],{},"Header starts small and never grows",[165,219,220,223,226],{},[183,221,222],{},"Parallax layer",[183,224,225],{},"maximum offset",[183,227,228],{},"Layer sits at its extreme position, layout looks broken",[165,230,231,234,237],{},[183,232,233],{},"Horizontal scroll section",[183,235,236],{},"last panel shown",[183,238,239],{},"Reader lands mid-sequence with no way back",[14,241,242,243,246],{},"The rule that falls out of this table: ",[151,244,245],{},"gate the enhancement whenever the final keyframe is not a sane static state."," If it is, you can ship ungated and save yourself a block.",[248,249,255,256,255,260,255,264,255,271,255,283,255,288,255,293,255,299,255,303,255,309,255,311,255,314,255,320,255,325,255,330,255,333,255,336,255,339,255,343,255,346,255,349],"svg",{"viewBox":250,"role":251,"ariaLabel":252,"xmlns":253,"style":254},"0 0 720 380","img","A decision flow from base styles through a supports test and a motion preference test to three rendered outcomes","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;height:auto;max-width:720px;margin:2rem 0","\n  ",[257,258,259],"title",{},"Gating a scroll-driven enhancement",[261,262,263],"desc",{},"Base styles flow into a supports test, which branches into three outcomes: unsupported and static, supported with reduced motion, and supported with full scroll-driven motion.",[265,266,270],"text",{"x":267,"y":268,"style":269},"360","26","text-anchor:middle;fill:currentColor;font:700 17px sans-serif","Three states you are shipping",[272,273],"rect",{"x":274,"y":275,"width":276,"height":277,"rx":278,"fill":279,"stroke":280,"strokeWidth":281,"opacity":282},"190","46","340","44","8","none","currentColor","1.5","0.55",[265,284,287],{"x":267,"y":285,"style":286},"73","text-anchor:middle;fill:currentColor;font:13px sans-serif","base styles: visible, static, no motion",[51,289],{"x1":267,"y1":290,"x2":267,"y2":291,"stroke":280,"strokeWidth":281,"opacity":292},"90","126","0.5",[272,294],{"x":295,"y":291,"width":296,"height":277,"rx":278,"fill":297,"opacity":298},"150","420","#7aa2ff","0.18",[265,300,20],{"x":267,"y":301,"style":302},"153","text-anchor:middle;fill:currentColor;font:12px ui-monospace,monospace",[51,304],{"x1":267,"y1":305,"x2":306,"y2":307,"stroke":280,"strokeWidth":281,"opacity":308},"170","135","246","0.45",[51,310],{"x1":267,"y1":305,"x2":267,"y2":307,"stroke":280,"strokeWidth":281,"opacity":308},[51,312],{"x1":267,"y1":305,"x2":313,"y2":307,"stroke":280,"strokeWidth":281,"opacity":308},"585",[272,315],{"x":316,"y":307,"width":317,"height":318,"rx":319,"fill":279,"stroke":280,"strokeWidth":281,"opacity":292},"30","210","76","10",[265,321,324],{"x":306,"y":322,"style":323},"276","text-anchor:middle;fill:currentColor;font:600 13px sans-serif","no support",[265,326,329],{"x":306,"y":327,"style":328},"300","text-anchor:middle;fill:currentColor;font:12px sans-serif;opacity:0.75","base styles stand",[272,331],{"x":332,"y":307,"width":317,"height":318,"rx":319,"fill":279,"stroke":280,"strokeWidth":281,"opacity":292},"255",[265,334,335],{"x":267,"y":322,"style":323},"reduce requested",[265,337,338],{"x":267,"y":327,"style":328},"opacity only",[272,340],{"x":341,"y":307,"width":317,"height":318,"rx":319,"fill":297,"opacity":342},"480","0.16",[265,344,345],{"x":313,"y":322,"style":323},"full support",[265,347,348],{"x":313,"y":327,"style":328},"scroll-linked motion",[265,350,353],{"x":267,"y":351,"style":352},"356","text-anchor:middle;fill:currentColor;font:12px sans-serif;opacity:0.7","All three must be a usable page, not one design plus two accidents.",[29,355],{},[32,357,359],{"id":358},"the-gating-pattern","The Gating Pattern",[14,361,362,363,367],{},"The structure that works for every case is the same: declare the resting, correct-for-everyone state outside the block, and put ",[364,365,366],"em",{},"only"," the timeline binding inside it.",[40,369,371],{"className":42,"code":370,"language":44,"meta":45,"style":45},"\u002F* 1. The state every browser gets. This must be a complete, usable design. *\u002F\n.progress {\n  position: fixed;\n  inset-block-start: 0;\n  inset-inline: 0;\n  height: 5px;\n  transform-origin: 0 50%;\n  \u002F* Absent the enhancement there is no meaningful progress to show,\n     so the bar is not rendered at all rather than rendered wrong. *\u002F\n  display: none;\n}\n\n\u002F* 2. The enhancement, applied only where the timeline can actually run. *\u002F\n@supports (animation-timeline: scroll()) {\n  .progress {\n    display: block;\n    background: #4361c8;\n    animation: grow-progress linear;\n    animation-timeline: scroll(root block);\n  }\n\n  @keyframes grow-progress {\n    from { transform: scaleX(0); }\n    to   { transform: scaleX(1); }\n  }\n}\n",[18,372,373,379,386,399,410,421,437,454,460,466,478,483,490,496,515,523,536,549,562,580,586,591,603,628,650,655],{"__ignoreMap":45},[49,374,375],{"class":51,"line":52},[49,376,378],{"class":377},"sLBg1","\u002F* 1. The state every browser gets. This must be a complete, usable design. *\u002F\n",[49,380,381,384],{"class":51,"line":63},[49,382,383],{"class":55},".progress",[49,385,60],{"class":59},[49,387,388,391,394,397],{"class":51,"line":81},[49,389,390],{"class":55},"  position",[49,392,393],{"class":59},": ",[49,395,396],{"class":55},"fixed",[49,398,78],{"class":59},[49,400,401,404,406,408],{"class":51,"line":90},[49,402,403],{"class":55},"  inset-block-start",[49,405,393],{"class":59},[49,407,99],{"class":55},[49,409,78],{"class":59},[49,411,412,415,417,419],{"class":51,"line":116},[49,413,414],{"class":55},"  inset-inline",[49,416,393],{"class":59},[49,418,99],{"class":55},[49,420,78],{"class":59},[49,422,424,427,429,432,435],{"class":51,"line":423},6,[49,425,426],{"class":55},"  height",[49,428,393],{"class":59},[49,430,431],{"class":55},"5",[49,433,434],{"class":102},"px",[49,436,78],{"class":59},[49,438,440,443,445,447,450,452],{"class":51,"line":439},7,[49,441,442],{"class":55},"  transform-origin",[49,444,393],{"class":59},[49,446,99],{"class":55},[49,448,449],{"class":55}," 50",[49,451,103],{"class":102},[49,453,78],{"class":59},[49,455,457],{"class":51,"line":456},8,[49,458,459],{"class":377},"  \u002F* Absent the enhancement there is no meaningful progress to show,\n",[49,461,463],{"class":51,"line":462},9,[49,464,465],{"class":377},"     so the bar is not rendered at all rather than rendered wrong. *\u002F\n",[49,467,469,472,474,476],{"class":51,"line":468},10,[49,470,471],{"class":55},"  display",[49,473,393],{"class":59},[49,475,279],{"class":55},[49,477,78],{"class":59},[49,479,481],{"class":51,"line":480},11,[49,482,119],{"class":59},[49,484,486],{"class":51,"line":485},12,[49,487,489],{"emptyLinePlaceholder":488},true,"\n",[49,491,493],{"class":51,"line":492},13,[49,494,495],{"class":377},"\u002F* 2. The enhancement, applied only where the timeline can actually run. *\u002F\n",[49,497,499,502,505,507,509,512],{"class":51,"line":498},14,[49,500,501],{"class":102},"@supports",[49,503,504],{"class":59}," (",[49,506,125],{"class":55},[49,508,393],{"class":59},[49,510,511],{"class":55},"scroll",[49,513,514],{"class":59},"()) {\n",[49,516,518,521],{"class":51,"line":517},15,[49,519,520],{"class":55},"  .progress",[49,522,60],{"class":59},[49,524,526,529,531,534],{"class":51,"line":525},16,[49,527,528],{"class":55},"    display",[49,530,393],{"class":59},[49,532,533],{"class":55},"block",[49,535,78],{"class":59},[49,537,539,542,544,547],{"class":51,"line":538},17,[49,540,541],{"class":55},"    background",[49,543,393],{"class":59},[49,545,546],{"class":55},"#4361c8",[49,548,78],{"class":59},[49,550,552,555,558,560],{"class":51,"line":551},18,[49,553,554],{"class":55},"    animation",[49,556,557],{"class":59},": grow-progress ",[49,559,72],{"class":55},[49,561,78],{"class":59},[49,563,565,568,570,572,575,577],{"class":51,"line":564},19,[49,566,567],{"class":55},"    animation-timeline",[49,569,393],{"class":59},[49,571,511],{"class":55},[49,573,574],{"class":59},"(root ",[49,576,533],{"class":55},[49,578,579],{"class":59},");\n",[49,581,583],{"class":51,"line":582},20,[49,584,585],{"class":59},"  }\n",[49,587,589],{"class":51,"line":588},21,[49,590,489],{"emptyLinePlaceholder":488},[49,592,594,597,601],{"class":51,"line":593},22,[49,595,596],{"class":102},"  @keyframes",[49,598,600],{"class":599},"soyes"," grow-progress",[49,602,60],{"class":59},[49,604,606,609,612,615,617,620,623,625],{"class":51,"line":605},23,[49,607,608],{"class":55},"    from",[49,610,611],{"class":59}," { ",[49,613,614],{"class":55},"transform",[49,616,393],{"class":59},[49,618,619],{"class":55},"scaleX",[49,621,622],{"class":59},"(",[49,624,99],{"class":55},[49,626,627],{"class":59},"); }\n",[49,629,631,634,637,639,641,643,645,648],{"class":51,"line":630},24,[49,632,633],{"class":55},"    to",[49,635,636],{"class":59},"   { ",[49,638,614],{"class":55},[49,640,393],{"class":59},[49,642,619],{"class":55},[49,644,622],{"class":59},[49,646,647],{"class":55},"1",[49,649,627],{"class":59},[49,651,653],{"class":51,"line":652},25,[49,654,585],{"class":59},[49,656,658],{"class":51,"line":657},26,[49,659,119],{"class":59},[14,661,662],{},"Three details matter here.",[14,664,665,668,669,672,673,676,677,679,680,683,684,686],{},[151,666,667],{},"Test the function, not the property name."," ",[18,670,671],{},"@supports (animation-timeline: none)"," will pass in any engine that knows the property exists — including one that ships the property but not the ",[18,674,675],{},"scroll()"," timeline function. Always put a real timeline function in the test: ",[18,678,675],{}," or ",[18,681,682],{},"view()",". Testing ",[18,685,682],{}," specifically is worth doing if your effect depends on it, since the two shipped together everywhere so far but the guarantee is per-value, not per-property.",[14,688,689,668,692,695],{},[151,690,691],{},"Do not use the negated form as your only branch.",[18,693,694],{},"@supports not (animation-timeline: scroll())"," is legal, but writing both a positive and a negative block means maintaining two designs that must stay in sync. The base-plus-enhancement shape has exactly one thing that varies.",[14,697,698,705],{},[151,699,700,701,704],{},"Keep ",[18,702,703],{},"@keyframes"," inside the block when the animation is only ever scroll-driven."," Nested at-rules are allowed, and it keeps the enhancement in one place. If the same keyframe rule is shared with a time-based animation elsewhere, hoist it out.",[14,707,708,709,713],{},"The same discipline is documented on the layout side in ",[23,710,712],{"href":711},"\u002Fmastering-container-queries-responsive-layouts\u002Fcontainer-query-fallbacks\u002Ffeature-detection-with-supports\u002F","feature detection with @supports","; the difference here is only that the consequence of getting it wrong is louder.",[715,716,718],"h3",{"id":717},"when-not-to-gate","When not to gate",[14,720,721,722,726,727,730],{},"Gating has a cost: the styles inside the block are invisible to anyone reading the base rules, and it is easy to leave a value stranded there. Skip it when the enhancement's end state is also the correct static state — which is true for the entire family of fade-and-rise reveals covered in ",[23,723,725],{"href":724},"\u002Fcss-only-micro-interactions-animations\u002Fscroll-driven-animations\u002Fscroll-triggered-reveal-animations\u002F","scroll-triggered reveal animations",", provided the element's own declared styles are the ",[364,728,729],{},"visible"," ones and the hidden state lives only inside the keyframes. Under those conditions an unsupported browser renders a normal page, which is the whole goal.",[715,732,734],{"id":733},"what-about-a-polyfill","What about a polyfill?",[14,736,737],{},"There is a well-known JavaScript polyfill that implements scroll timelines using the Web Animations API and a scroll listener. It works. It also reinstates precisely the main-thread cost that scroll-driven animations exist to eliminate, and it does so on the browsers that are already the slowest path. For decorative motion this is a bad trade: you are shipping kilobytes of script and a scroll handler so that a minority of readers can see a fade they will not miss. Reach for it only when the effect is load-bearing for comprehension — a scroll-driven diagram that explains something, for instance — and even then, consider whether a static annotated version communicates the same thing more reliably. When motion genuinely must be identical everywhere, the honest choice is to write it in script deliberately rather than emulate a CSS feature.",[29,739],{},[32,741,743],{"id":742},"the-prefers-reduced-motion-obligation","The prefers-reduced-motion Obligation",[14,745,746,747,749],{},"This is not the same conversation as browser support, and conflating the two produces the common bug where an ",[18,748,501],{}," block ships the reduced-motion branch nothing at all.",[14,751,752],{},"Scroll-linked motion is a stronger vestibular trigger than ordinary animation, for a mechanical reason. When a user scrolls, their vestibular and proprioceptive systems predict a specific relationship between the input and the visual field: content translates by the scroll distance, uniformly. A scroll-driven animation deliberately breaks that relationship — a parallax layer moves at 0.6× the scroll rate, a card slides up while the page slides down, an element scales while everything around it does not. That mismatch between predicted and actual optic flow is what produces dizziness, nausea, and disorientation in people with vestibular disorders. It is also why parallax specifically has the worst reputation of any web motion pattern.",[14,754,755,756,760],{},"WCAG 2.1's Success Criterion 2.3.3 (Animation from Interactions, Level AAA) addresses this directly: motion animation triggered by interaction can be disabled unless it is essential. Scrolling is an interaction. The reasoning is unpacked further in the ",[23,757,759],{"href":758},"\u002Fcss-only-micro-interactions-animations\u002Faccessibility-in-css-animations\u002Fwcag-motion-success-criteria\u002F","WCAG motion success criteria"," page.",[14,762,763],{},"What the branch must remove:",[159,765,766,775],{},[162,767,768],{},[165,769,770,772],{},[168,771,170],{},[168,773,774],{},"Reduced-motion treatment",[178,776,777,792,801,810,818,826,836,848],{},[165,778,779,789],{},[183,780,781,784,785,788],{},[18,782,783],{},"translate"," \u002F ",[18,786,787],{},"translateY"," on scroll",[183,790,791],{},"Remove entirely",[165,793,794,799],{},[183,795,796,788],{},[18,797,798],{},"scale",[183,800,791],{},[165,802,803,808],{},[183,804,805,788],{},[18,806,807],{},"rotate",[183,809,791],{},[165,811,812,815],{},[183,813,814],{},"Parallax (differential layer rates)",[183,816,817],{},"Remove entirely; layers scroll with the page",[165,819,820,823],{},[183,821,822],{},"Scroll-hijacking \u002F horizontal panels",[183,824,825],{},"Remove; restore normal vertical scrolling",[165,827,828,833],{},[183,829,830,788],{},[18,831,832],{},"opacity",[183,834,835],{},"Usually safe to keep; consider a floor so nothing is fully hidden",[165,837,838,845],{},[183,839,840,841,844],{},"Colour or ",[18,842,843],{},"background"," shift on scroll",[183,846,847],{},"Safe to keep",[165,849,850,855],{},[183,851,852,853],{},"Progress indicator width or ",[18,854,619],{},[183,856,857],{},"Keep — it is information, not decoration",[14,859,860,861,864,865,868,869,872,873,154],{},"That last row is the one people get wrong most often, by wrapping the entire scroll-driven block in a ",[18,862,863],{},"no-preference"," query and thereby deleting a useful position indicator for the readers most likely to want it. Reduced motion means less ",[364,866,867],{},"movement",", not less ",[364,870,871],{},"information",". The distinction is developed at length in ",[23,874,876],{"href":875},"\u002Fcss-only-micro-interactions-animations\u002Faccessibility-in-css-animations\u002Freducing-motion-preferences-in-css\u002F","reducing motion preferences in CSS",[14,878,879],{},"Here is the opacity-only variant scrolling — nothing traverses the visual field, and nothing ever drops to fully invisible:",[881,882],"live-demo",{"name":883},"scroll-reduced-motion-reveal",[40,885,887],{"className":42,"code":886,"language":44,"meta":45,"style":45},".tile {\n  animation: soften linear both;\n  animation-timeline: view();\n  animation-range: entry 0% cover 30%;\n}\n\n\u002F* Opacity floors at 0.35 rather than 0 so the text is never fully absent\n   for a reader who cannot scroll it into range. *\u002F\n@keyframes soften {\n  from { opacity: 0.35; }\n  to   { opacity: 1; }\n}\n",[18,888,889,896,909,915,934,938,942,947,952,961,978,993],{"__ignoreMap":45},[49,890,891,894],{"class":51,"line":52},[49,892,893],{"class":55},".tile",[49,895,60],{"class":59},[49,897,898,900,903,905,907],{"class":51,"line":63},[49,899,66],{"class":55},[49,901,902],{"class":59},": soften ",[49,904,72],{"class":55},[49,906,75],{"class":55},[49,908,78],{"class":59},[49,910,911,913],{"class":51,"line":81},[49,912,84],{"class":55},[49,914,87],{"class":59},[49,916,917,919,921,923,925,927,930,932],{"class":51,"line":90},[49,918,93],{"class":55},[49,920,96],{"class":59},[49,922,99],{"class":55},[49,924,103],{"class":102},[49,926,106],{"class":55},[49,928,929],{"class":55}," 30",[49,931,103],{"class":102},[49,933,78],{"class":59},[49,935,936],{"class":51,"line":116},[49,937,119],{"class":59},[49,939,940],{"class":51,"line":423},[49,941,489],{"emptyLinePlaceholder":488},[49,943,944],{"class":51,"line":439},[49,945,946],{"class":377},"\u002F* Opacity floors at 0.35 rather than 0 so the text is never fully absent\n",[49,948,949],{"class":51,"line":456},[49,950,951],{"class":377},"   for a reader who cannot scroll it into range. *\u002F\n",[49,953,954,956,959],{"class":51,"line":462},[49,955,703],{"class":102},[49,957,958],{"class":599}," soften",[49,960,60],{"class":59},[49,962,963,966,968,970,972,975],{"class":51,"line":468},[49,964,965],{"class":55},"  from",[49,967,611],{"class":59},[49,969,832],{"class":55},[49,971,393],{"class":59},[49,973,974],{"class":55},"0.35",[49,976,977],{"class":59},"; }\n",[49,979,980,983,985,987,989,991],{"class":51,"line":480},[49,981,982],{"class":55},"  to",[49,984,636],{"class":59},[49,986,832],{"class":55},[49,988,393],{"class":59},[49,990,647],{"class":55},[49,992,977],{"class":59},[49,994,995],{"class":51,"line":485},[49,996,119],{"class":59},[29,998],{},[32,1000,1002],{"id":1001},"putting-both-guards-together","Putting Both Guards Together",[14,1004,1005],{},"The two conditions are independent, so nest them rather than trying to express them as one test. Order matters for readability: gate on support first, then narrow by preference inside.",[40,1007,1009],{"className":42,"code":1008,"language":44,"meta":45,"style":45},"\u002F* Base: visible, static, correct for everyone. *\u002F\n.card {\n  opacity: 1;\n  transform: none;\n}\n\n@supports (animation-timeline: view()) {\n  \u002F* Default enhancement: full scroll-linked reveal. *\u002F\n  .card {\n    animation: reveal linear both;\n    animation-timeline: view();\n    animation-range: entry 0% cover 40%;\n  }\n\n  @keyframes reveal {\n    from { opacity: 0; transform: translateY(28px); }\n    to   { opacity: 1; transform: translateY(0); }\n  }\n\n  \u002F* Narrowed enhancement: keep the scroll link, drop the movement. *\u002F\n  @media (prefers-reduced-motion: reduce) {\n    .card {\n      animation-name: reveal-soft;\n    }\n\n    @keyframes reveal-soft {\n      from { opacity: 0.4; transform: none; }\n      to   { opacity: 1; transform: none; }\n    }\n  }\n}\n",[18,1010,1011,1016,1022,1033,1044,1048,1052,1063,1068,1075,1087,1093,1112,1116,1120,1129,1159,1185,1189,1193,1198,1206,1213,1221,1226,1230,1240,1265,1289,1294,1299],{"__ignoreMap":45},[49,1012,1013],{"class":51,"line":52},[49,1014,1015],{"class":377},"\u002F* Base: visible, static, correct for everyone. *\u002F\n",[49,1017,1018,1020],{"class":51,"line":63},[49,1019,56],{"class":55},[49,1021,60],{"class":59},[49,1023,1024,1027,1029,1031],{"class":51,"line":81},[49,1025,1026],{"class":55},"  opacity",[49,1028,393],{"class":59},[49,1030,647],{"class":55},[49,1032,78],{"class":59},[49,1034,1035,1038,1040,1042],{"class":51,"line":90},[49,1036,1037],{"class":55},"  transform",[49,1039,393],{"class":59},[49,1041,279],{"class":55},[49,1043,78],{"class":59},[49,1045,1046],{"class":51,"line":116},[49,1047,119],{"class":59},[49,1049,1050],{"class":51,"line":423},[49,1051,489],{"emptyLinePlaceholder":488},[49,1053,1054,1056,1058,1060],{"class":51,"line":439},[49,1055,501],{"class":102},[49,1057,504],{"class":59},[49,1059,125],{"class":55},[49,1061,1062],{"class":59},": view()) {\n",[49,1064,1065],{"class":51,"line":456},[49,1066,1067],{"class":377},"  \u002F* Default enhancement: full scroll-linked reveal. *\u002F\n",[49,1069,1070,1073],{"class":51,"line":462},[49,1071,1072],{"class":55},"  .card",[49,1074,60],{"class":59},[49,1076,1077,1079,1081,1083,1085],{"class":51,"line":468},[49,1078,554],{"class":55},[49,1080,69],{"class":59},[49,1082,72],{"class":55},[49,1084,75],{"class":55},[49,1086,78],{"class":59},[49,1088,1089,1091],{"class":51,"line":480},[49,1090,567],{"class":55},[49,1092,87],{"class":59},[49,1094,1095,1098,1100,1102,1104,1106,1108,1110],{"class":51,"line":485},[49,1096,1097],{"class":55},"    animation-range",[49,1099,96],{"class":59},[49,1101,99],{"class":55},[49,1103,103],{"class":102},[49,1105,106],{"class":55},[49,1107,109],{"class":55},[49,1109,103],{"class":102},[49,1111,78],{"class":59},[49,1113,1114],{"class":51,"line":492},[49,1115,585],{"class":59},[49,1117,1118],{"class":51,"line":498},[49,1119,489],{"emptyLinePlaceholder":488},[49,1121,1122,1124,1127],{"class":51,"line":517},[49,1123,596],{"class":102},[49,1125,1126],{"class":599}," reveal",[49,1128,60],{"class":59},[49,1130,1131,1133,1135,1137,1139,1141,1144,1146,1148,1150,1152,1155,1157],{"class":51,"line":525},[49,1132,608],{"class":55},[49,1134,611],{"class":59},[49,1136,832],{"class":55},[49,1138,393],{"class":59},[49,1140,99],{"class":55},[49,1142,1143],{"class":59},"; ",[49,1145,614],{"class":55},[49,1147,393],{"class":59},[49,1149,787],{"class":55},[49,1151,622],{"class":59},[49,1153,1154],{"class":55},"28",[49,1156,434],{"class":102},[49,1158,627],{"class":59},[49,1160,1161,1163,1165,1167,1169,1171,1173,1175,1177,1179,1181,1183],{"class":51,"line":538},[49,1162,633],{"class":55},[49,1164,636],{"class":59},[49,1166,832],{"class":55},[49,1168,393],{"class":59},[49,1170,647],{"class":55},[49,1172,1143],{"class":59},[49,1174,614],{"class":55},[49,1176,393],{"class":59},[49,1178,787],{"class":55},[49,1180,622],{"class":59},[49,1182,99],{"class":55},[49,1184,627],{"class":59},[49,1186,1187],{"class":51,"line":551},[49,1188,585],{"class":59},[49,1190,1191],{"class":51,"line":564},[49,1192,489],{"emptyLinePlaceholder":488},[49,1194,1195],{"class":51,"line":582},[49,1196,1197],{"class":377},"  \u002F* Narrowed enhancement: keep the scroll link, drop the movement. *\u002F\n",[49,1199,1200,1203],{"class":51,"line":588},[49,1201,1202],{"class":102},"  @media",[49,1204,1205],{"class":59}," (prefers-reduced-motion: reduce) {\n",[49,1207,1208,1211],{"class":51,"line":593},[49,1209,1210],{"class":55},"    .card",[49,1212,60],{"class":59},[49,1214,1215,1218],{"class":51,"line":605},[49,1216,1217],{"class":55},"      animation-name",[49,1219,1220],{"class":59},": reveal-soft;\n",[49,1222,1223],{"class":51,"line":630},[49,1224,1225],{"class":59},"    }\n",[49,1227,1228],{"class":51,"line":652},[49,1229,489],{"emptyLinePlaceholder":488},[49,1231,1232,1235,1238],{"class":51,"line":657},[49,1233,1234],{"class":102},"    @keyframes",[49,1236,1237],{"class":599}," reveal-soft",[49,1239,60],{"class":59},[49,1241,1243,1246,1248,1250,1252,1255,1257,1259,1261,1263],{"class":51,"line":1242},27,[49,1244,1245],{"class":55},"      from",[49,1247,611],{"class":59},[49,1249,832],{"class":55},[49,1251,393],{"class":59},[49,1253,1254],{"class":55},"0.4",[49,1256,1143],{"class":59},[49,1258,614],{"class":55},[49,1260,393],{"class":59},[49,1262,279],{"class":55},[49,1264,977],{"class":59},[49,1266,1268,1271,1273,1275,1277,1279,1281,1283,1285,1287],{"class":51,"line":1267},28,[49,1269,1270],{"class":55},"      to",[49,1272,636],{"class":59},[49,1274,832],{"class":55},[49,1276,393],{"class":59},[49,1278,647],{"class":55},[49,1280,1143],{"class":59},[49,1282,614],{"class":55},[49,1284,393],{"class":59},[49,1286,279],{"class":55},[49,1288,977],{"class":59},[49,1290,1292],{"class":51,"line":1291},29,[49,1293,1225],{"class":59},[49,1295,1297],{"class":51,"line":1296},30,[49,1298,585],{"class":59},[49,1300,1302],{"class":51,"line":1301},31,[49,1303,119],{"class":59},[14,1305,1306,1307,1310],{},"Overriding only ",[18,1308,1309],{},"animation-name"," inside the media query is deliberate. The timeline, range, and fill mode are already correct; swapping the keyframe rule changes what is interpolated without duplicating the binding, so the two versions cannot drift apart. Verify the result by emulating the preference in DevTools rather than changing your operating system settings, and check that the page still reads correctly with the feature disabled entirely — the third state in the diagram above is the one nobody tests and the one a real proportion of readers get.",[29,1312],{},[32,1314,1316],{"id":1315},"faq","FAQ",[14,1318,1319,1322,1323,1325,1326,1329],{},[151,1320,1321],{},"What happens in a browser with no scroll-timeline support?","\nThe ",[18,1324,125],{}," declaration is dropped as unknown, but the ",[18,1327,1328],{},"animation"," shorthand is still valid and runs on the document timeline. With no duration declared that resolves to zero seconds, so the element jumps instantly to its final keyframe.",[14,1331,1332,1335,1336,1339,1340,1342],{},[151,1333,1334],{},"Can I feature-detect scroll-driven animations without @supports?","\nYou can test ",[18,1337,1338],{},"CSS.supports('animation-timeline', 'scroll()')"," in script, but there is rarely a reason to. ",[18,1341,501],{}," is evaluated at style time with no scripting dependency, so the enhancement applies before first paint rather than after hydration.",[14,1344,1345,1348],{},[151,1346,1347],{},"Is prefers-reduced-motion enough on its own, or do I need a site setting too?","\nThe media query is the baseline obligation and covers users who have configured the operating system. A visible in-page toggle is a reasonable addition for shared devices and for users who never found the OS setting, but it does not replace the query.",[14,1350,1351,1354],{},[151,1352,1353],{},"Should reduced motion disable a scroll-driven animation entirely?","\nOnly the part that moves things. Remove translation, scaling, rotation, and parallax, since scroll-coupled positional change is the vestibular trigger. An opacity or colour change tied to scroll is usually acceptable and often carries information worth keeping.",[29,1356],{},[32,1358,1360],{"id":1359},"related","Related",[1362,1363,1364,1371,1378,1384,1389],"ul",{},[1365,1366,1367,1370],"li",{},[23,1368,1369],{"href":25},"Scroll-Driven Animations"," — the mechanics these guards are wrapped around.",[1365,1372,1373,1377],{},[23,1374,1376],{"href":1375},"\u002Fcss-only-micro-interactions-animations\u002Fscroll-driven-animations\u002Fscroll-progress-bar-without-javascript\u002F","Scroll progress bar without JavaScript"," — the case where an ungated fallback is actively wrong.",[1365,1379,1380,1383],{},[23,1381,1382],{"href":875},"Reducing motion preferences in CSS"," — the general pattern for scaling motion back without losing meaning.",[1365,1385,1386,1388],{},[23,1387,759],{"href":758}," — the conformance requirements behind the reduced-motion branch.",[1365,1390,1391,1394],{},[23,1392,1393],{"href":711},"Feature detection with @supports"," — the same gating discipline applied to responsive layout.",[1396,1397,1398],"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 .sHUrx, html code.shiki .sHUrx{--shiki-default:#A0111F;--shiki-dark:#FF9492}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 .sLBg1, html code.shiki .sLBg1{--shiki-default:#66707B;--shiki-dark:#BDC4CC}html pre.shiki code .soyes, html code.shiki .soyes{--shiki-default:#702C00;--shiki-dark:#FFB757}",{"title":45,"searchDepth":63,"depth":63,"links":1400},[1401,1402,1406,1407,1408,1409],{"id":34,"depth":63,"text":35},{"id":358,"depth":63,"text":359,"children":1403},[1404,1405],{"id":717,"depth":81,"text":718},{"id":733,"depth":81,"text":734},{"id":742,"depth":63,"text":743},{"id":1001,"depth":63,"text":1002},{"id":1315,"depth":63,"text":1316},{"id":1359,"depth":63,"text":1360},"Ship scroll-driven animations safely: @supports (animation-timeline: scroll()) gating, what unsupported browsers render, and the prefers-reduced-motion duty.","md",{"pageTitle":1413,"datePublished":1414,"dateModified":1414,"faq":1415},"Scroll-Driven Animation Fallbacks","2026-07-20",[1416,1418,1420,1422],{"q":1321,"a":1417},"The animation-timeline declaration is dropped as unknown, but the animation shorthand is still valid and runs on the document timeline. With no duration declared that resolves to zero seconds, so the element jumps instantly to its final keyframe.",{"q":1334,"a":1419},"You can test CSS.supports('animation-timeline', 'scroll()') in script, but there is rarely a reason to. @supports is evaluated at style time with no scripting dependency, so the enhancement applies before first paint rather than after hydration.",{"q":1347,"a":1421},"The media query is the baseline obligation and covers users who have configured the operating system. A visible in-page toggle is a reasonable addition for shared devices and for users who never found the OS setting, but it does not replace the query.",{"q":1353,"a":1423},"Only the part that moves things. Remove translation, scaling, rotation, and parallax, since scroll-coupled positional change is the vestibular trigger. An opacity or colour change tied to scroll is usually acceptable and often carries information worth keeping.","\u002Fcss-only-micro-interactions-animations\u002Fscroll-driven-animations\u002Fscroll-driven-animation-fallbacks",{"title":5,"description":1410},"css-only-micro-interactions-animations\u002Fscroll-driven-animations\u002Fscroll-driven-animation-fallbacks\u002Findex","8CDQRx9cbOICfAdkbUfWUZ1jfbol9x1Px-KUrxArbec",[1429,1432,1435,1438,1441,1444,1447,1450,1453,1456,1459,1462,1465,1468,1471,1474,1477,1480,1483,1486,1489,1492,1495,1498,1501,1504,1507,1510,1513,1516,1519,1522,1525,1528,1531,1534,1537,1540,1543,1546,1547,1550,1553,1556,1559,1562,1565,1568,1571,1574,1577,1580,1583,1586,1589,1592,1595,1598,1601,1604,1607,1610,1613,1616,1619,1622,1625,1628,1631,1634,1637,1640,1643,1646,1649,1652,1655,1658,1661,1664,1667,1670,1673,1676,1679,1682,1685,1688],{"path":1430,"title":1431},"\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":1433,"title":1434},"\u002Fcss-only-micro-interactions-animations\u002Faccessibility-in-css-animations","Accessibility in CSS Animations: Patterns, Specs & Best Practices",{"path":1436,"title":1437},"\u002Fcss-only-micro-interactions-animations\u002Faccessibility-in-css-animations\u002Fprefers-reduced-motion-recipes","prefers-reduced-motion Recipes: A Reduction for Every Common Effect",{"path":1439,"title":1440},"\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":1442,"title":1443},"\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":1445,"title":1446},"\u002Fcss-only-micro-interactions-animations\u002Faccessibility-in-css-animations\u002Fvestibular-safe-animation-patterns","Vestibular-Safe Animation Patterns: Why Some Motion Makes People Sick",{"path":1448,"title":1449},"\u002Fcss-only-micro-interactions-animations\u002Faccessibility-in-css-animations\u002Fwcag-motion-success-criteria","WCAG Motion Success Criteria: What Each One Actually Requires",{"path":1451,"title":1452},"\u002Fcss-only-micro-interactions-animations\u002Fcontainer-aware-motion\u002Fcontainer-query-hover-affordances","Container Query Hover Affordances: When Narrow Means Persistent, Not Hidden",{"path":1454,"title":1455},"\u002Fcss-only-micro-interactions-animations\u002Fcontainer-aware-motion","Container-Aware Motion: Animation Sized by the Space a Component Actually Has",{"path":1457,"title":1458},"\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":1460,"title":1461},"\u002Fcss-only-micro-interactions-animations\u002Fcontainer-aware-motion\u002Fsuppressing-motion-in-small-containers","Suppressing Motion in Small Containers: Deliberately Animating Nothing",{"path":1463,"title":1464},"\u002Fcss-only-micro-interactions-animations\u002Fcss-animation-vs-web-animations-api","CSS Animation vs the Web Animations API: A Decision Guide",{"path":1466,"title":1467},"\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":1469,"title":1470},"\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":1472,"title":1473},"\u002Fcss-only-micro-interactions-animations\u002Fcss-custom-properties-architecture\u002Ffluid-spacing-tokens-driving-transition-durations","Fluid Spacing Tokens That Drive Transition Durations",{"path":1475,"title":1476},"\u002Fcss-only-micro-interactions-animations\u002Fcss-custom-properties-architecture","CSS Custom Properties Architecture: Scalable Design Systems & Dynamic UI",{"path":1478,"title":1479},"\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":1481,"title":1482},"\u002Fcss-only-micro-interactions-animations\u002Fcss-transition-fundamentals\u002Fcss-transition-timing-functions","CSS Transition Timing Functions: ease, cubic-bezier(), steps() and linear()",{"path":1484,"title":1485},"\u002Fcss-only-micro-interactions-animations\u002Fcss-transition-fundamentals","CSS Transition Fundamentals: Architecture, Performance & Patterns",{"path":1487,"title":1488},"\u002Fcss-only-micro-interactions-animations\u002Fcss-transition-fundamentals\u002Fstarting-style-entry-animations","@starting-style: Animating an Element's Very First Style Change",{"path":1490,"title":1491},"\u002Fcss-only-micro-interactions-animations\u002Fcss-transition-fundamentals\u002Ftransitioning-display-with-allow-discrete","Transitioning display with transition-behavior: allow-discrete and @starting-style",{"path":1493,"title":1494},"\u002Fcss-only-micro-interactions-animations\u002Fhover-focus-state-design\u002Faccessible-css-only-tooltips","Accessible CSS-Only Tooltips and Hover Cards With  and ",{"path":1496,"title":1497},"\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":1499,"title":1500},"\u002Fcss-only-micro-interactions-animations\u002Fhover-focus-state-design\u002Ffocus-within-form-patterns"," Form Patterns: Styling a Group From the Field Inside It",{"path":1502,"title":1503},"\u002Fcss-only-micro-interactions-animations\u002Fhover-focus-state-design","Hover & Focus State Design: Spec-Compliant Patterns for Modern UIs",{"path":1505,"title":1506},"\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":1508,"title":1509},"\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":1511,"title":1512},"\u002Fcss-only-micro-interactions-animations","CSS-Only Micro-Interactions & Animations: Architecture, Performance & Implementation",{"path":1514,"title":1515},"\u002Fcss-only-micro-interactions-animations\u002Fkeyframe-animation-patterns\u002Fcontainer-query-triggered-keyframe-animations","Container-Query-Triggered Keyframe Animations: Animate Differently per Available Space",{"path":1517,"title":1518},"\u002Fcss-only-micro-interactions-animations\u002Fkeyframe-animation-patterns\u002Fcss-only-accordions-and-disclosure","CSS-Only Accordions and Disclosure Widgets with details and summary",{"path":1520,"title":1521},"\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":1523,"title":1524},"\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":1526,"title":1527},"\u002Fcss-only-micro-interactions-animations\u002Fkeyframe-animation-patterns","Keyframe Animation Patterns: Spec-Compliant Architectures for Modern UIs",{"path":1529,"title":1530},"\u002Fcss-only-micro-interactions-animations\u002Fkeyframe-animation-patterns\u002Fstaggered-list-animations-with-custom-properties","Staggered List Animations Using --i Index Custom Properties",{"path":1532,"title":1533},"\u002Fcss-only-micro-interactions-animations\u002Fperformance-gpu-acceleration","Performance & GPU Acceleration in CSS: A Developer’s Blueprint",{"path":1535,"title":1536},"\u002Fcss-only-micro-interactions-animations\u002Fperformance-gpu-acceleration\u002Foptimizing-css-animations-for-60fps","Optimizing CSS Animations for 60fps: Budgeting the Frame",{"path":1538,"title":1539},"\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":1541,"title":1542},"\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":1544,"title":1545},"\u002Fcss-only-micro-interactions-animations\u002Fscroll-driven-animations","Scroll-Driven Animations: Binding Motion to Scroll Position in Pure CSS",{"path":1424,"title":5},{"path":1548,"title":1549},"\u002Fcss-only-micro-interactions-animations\u002Fscroll-driven-animations\u002Fscroll-progress-bar-without-javascript","A Reading Progress Bar Driven by scroll(root)",{"path":1551,"title":1552},"\u002Fcss-only-micro-interactions-animations\u002Fscroll-driven-animations\u002Fscroll-triggered-reveal-animations","Reveal-on-Scroll with view() and animation-range",{"path":1554,"title":1555},"\u002Fcss-only-micro-interactions-animations\u002Fview-transitions-for-css-developers","View Transitions for CSS Developers: the Pseudo-Element Tree, Snapshots, and Named Elements",{"path":1557,"title":1558},"\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":1560,"title":1561},"\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":1563,"title":1564},"\u002Fcss-only-micro-interactions-animations\u002Fview-transitions-for-css-developers\u002Fview-transitions-with-reduced-motion","Honouring prefers-reduced-motion in View Transitions",{"path":1566,"title":1567},"\u002F","Modern CSS Layouts & Micro-Interactions",{"path":1569,"title":1570},"\u002Fmastering-container-queries-responsive-layouts\u002Fanchor-positioning-and-overlays\u002Fanchor-positioned-tooltips","Anchor-Positioned Tooltips: Tethering, Flipping, and Getting the Semantics Right",{"path":1572,"title":1573},"\u002Fmastering-container-queries-responsive-layouts\u002Fanchor-positioning-and-overlays\u002Fanchor-positioning-fallbacks","Anchor Positioning Fallbacks: A Graceful Floor for Older Browser Versions",{"path":1575,"title":1576},"\u002Fmastering-container-queries-responsive-layouts\u002Fanchor-positioning-and-overlays","CSS Anchor Positioning and Overlays: Tethering Elements Without JavaScript",{"path":1578,"title":1579},"\u002Fmastering-container-queries-responsive-layouts\u002Fanchor-positioning-and-overlays\u002Fpopover-attribute-and-css-styling","The popover Attribute: Top-Layer Overlays With No JavaScript",{"path":1581,"title":1582},"\u002Fmastering-container-queries-responsive-layouts\u002Fcontainer-query-fallbacks\u002Ffeature-detection-with-supports","Feature Detection with @supports: Syntax, Semantics, and the Traps",{"path":1584,"title":1585},"\u002Fmastering-container-queries-responsive-layouts\u002Fcontainer-query-fallbacks\u002Fhandling-container-query-fallbacks-for-older-browsers","Handling Container Query Fallbacks for Older Browsers",{"path":1587,"title":1588},"\u002Fmastering-container-queries-responsive-layouts\u002Fcontainer-query-fallbacks","Container Query Fallbacks: Spec-Compliant CSS Strategies for Legacy Browsers",{"path":1590,"title":1591},"\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":1593,"title":1594},"\u002Fmastering-container-queries-responsive-layouts\u002Fcontainer-query-syntax-basics\u002Fcontainer-type-size-vs-inline-size","Choosing container-type: inline-size, size, or normal",{"path":1596,"title":1597},"\u002Fmastering-container-queries-responsive-layouts\u002Fcontainer-query-syntax-basics\u002Fcontainer-vs-media-queries-comparison","Container vs Media Queries: Choosing the Right Reference Box",{"path":1599,"title":1600},"\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":1602,"title":1603},"\u002Fmastering-container-queries-responsive-layouts\u002Fcontainer-query-syntax-basics","Container Query Syntax Basics",{"path":1605,"title":1606},"\u002Fmastering-container-queries-responsive-layouts\u002Fcontainer-query-syntax-basics\u002Fnesting-and-naming-container-queries","Nesting and Naming Container Queries: Targeting the Right Ancestor",{"path":1608,"title":1609},"\u002Fmastering-container-queries-responsive-layouts\u002Fcss-grid-and-subgrid-layouts\u002Fauto-fit-minmax-responsive-grids","Responsive Grids with repeat(auto-fit, minmax())",{"path":1611,"title":1612},"\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":1614,"title":1615},"\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":1617,"title":1618},"\u002Fmastering-container-queries-responsive-layouts\u002Fcss-grid-and-subgrid-layouts","CSS Grid and Subgrid Layouts for Responsive Interfaces",{"path":1620,"title":1621},"\u002Fmastering-container-queries-responsive-layouts\u002Fcss-grid-and-subgrid-layouts\u002Fsubgrid-vs-nested-grid","Subgrid vs an Independently Nested Grid",{"path":1623,"title":1624},"\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":1626,"title":1627},"\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":1629,"title":1630},"\u002Fmastering-container-queries-responsive-layouts\u002Ffluid-typography-with-clamp\u002Ffluid-type-accessibility-and-zoom","Fluid Type, Accessibility and Zoom: The vw-Only Trap",{"path":1632,"title":1633},"\u002Fmastering-container-queries-responsive-layouts\u002Ffluid-typography-with-clamp\u002Ffluid-typography-without-javascript","Fluid Typography Without JavaScript: A Precision CSS Reference",{"path":1635,"title":1636},"\u002Fmastering-container-queries-responsive-layouts\u002Ffluid-typography-with-clamp","Fluid Typography with clamp(): A Practical Guide for Modern CSS",{"path":1638,"title":1639},"\u002Fmastering-container-queries-responsive-layouts","Mastering Container Queries & Responsive Layouts",{"path":1641,"title":1642},"\u002Fmastering-container-queries-responsive-layouts\u002Fintrinsic-sizing-techniques\u002Faspect-ratio-for-responsive-media","Using aspect-ratio for Responsive Media and Preventing Layout Shift",{"path":1644,"title":1645},"\u002Fmastering-container-queries-responsive-layouts\u002Fintrinsic-sizing-techniques","Intrinsic Sizing Techniques: Modern CSS Layouts for Responsive UI",{"path":1647,"title":1648},"\u002Fmastering-container-queries-responsive-layouts\u002Fintrinsic-sizing-techniques\u002Fmin-max-fit-content-explained","min-content, max-content, and fit-content() Explained",{"path":1650,"title":1651},"\u002Fmastering-container-queries-responsive-layouts\u002Fintrinsic-sizing-techniques\u002Fpreventing-flex-and-grid-overflow","The min-width: auto Trap in Flex and Grid Layouts",{"path":1653,"title":1654},"\u002Fmastering-container-queries-responsive-layouts\u002Fmodern-css-reset-strategies\u002Fcascade-layers-for-reset-and-tokens","Cascade Layers for Reset and Design Tokens",{"path":1656,"title":1657},"\u002Fmastering-container-queries-responsive-layouts\u002Fmodern-css-reset-strategies\u002Fcascade-layers-vs-specificity-hacks","Cascade Layers vs Specificity Hacks: Ordering Instead of Escalating",{"path":1659,"title":1660},"\u002Fmastering-container-queries-responsive-layouts\u002Fmodern-css-reset-strategies","Modern CSS Reset Strategies: A Spec-Compliant Foundation",{"path":1662,"title":1663},"\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":1665,"title":1666},"\u002Fmastering-container-queries-responsive-layouts\u002Fresponsive-component-patterns\u002Fcontainer-query-data-tables","Container Query Data Tables: Reflowing Tables to Cards",{"path":1668,"title":1669},"\u002Fmastering-container-queries-responsive-layouts\u002Fresponsive-component-patterns\u002Fcontainer-query-sidebar-layouts","Container Query Sidebar Layouts That Travel Between Contexts",{"path":1671,"title":1672},"\u002Fmastering-container-queries-responsive-layouts\u002Fresponsive-component-patterns","Responsive Component Patterns: Architecture & Implementation",{"path":1674,"title":1675},"\u002Fmastering-container-queries-responsive-layouts\u002Fresponsive-component-patterns\u002Fresponsive-forms-with-container-queries","Responsive Forms with Container Queries: Reflow Without Losing the Wiring",{"path":1677,"title":1678},"\u002Fmastering-container-queries-responsive-layouts\u002Fresponsive-component-patterns\u002Fresponsive-navigation-without-media-queries","Responsive Navigation Without Media Queries Using Container Queries",{"path":1680,"title":1681},"\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":1683,"title":1684},"\u002Fmastering-container-queries-responsive-layouts\u002Fstyle-queries-and-container-state","Style Queries and Container State: Token-Driven Component Variants",{"path":1686,"title":1687},"\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":1689,"title":1690},"\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",1784566157184]