[{"data":1,"prerenderedAt":1840},["ShallowReactive",2],{"page-\u002Fcss-only-micro-interactions-animations\u002Fscroll-driven-animations\u002Fscroll-triggered-reveal-animations\u002F":3,"content-all-pages":1578},{"id":4,"title":5,"body":6,"description":1560,"extension":1561,"meta":1562,"navigation":457,"path":1574,"seo":1575,"stem":1576,"__hash__":1577},"content\u002Fcss-only-micro-interactions-animations\u002Fscroll-driven-animations\u002Fscroll-triggered-reveal-animations\u002Findex.md","Reveal-on-Scroll with view() and animation-range",{"type":7,"value":8,"toc":1551},"minimark",[9,13,36,39,44,52,59,62,69,76,79,182,184,188,191,195,1103,1117,1119,1123,1136,1141,1179,1203,1205,1209,1228,1349,1360,1406,1418,1420,1424,1449,1451,1455,1479,1485,1499,1505,1507,1511,1548],[10,11,5],"h1",{"id":12},"reveal-on-scroll-with-view-and-animation-range",[14,15,16,17,21,22,25,26,29,30,35],"p",{},"Reveal-on-scroll is the most requested scroll effect and historically the most over-engineered one: a library, an IntersectionObserver, a class toggle, a ",[18,19,20],"code",{},"will-change"," cleanup, and a mutation observer for content added later. What the effect actually needs is a statement that an element's opacity and offset are a function of its own position in the scrollport — which is exactly what ",[18,23,24],{},"animation-timeline: view()"," expresses. This page builds the complete reveal, explains why ",[18,27,28],{},"animation-fill-mode"," is not optional here (it is the single difference between a working reveal and a flashing one), and sets the technique against IntersectionObserver so you can tell when the script is still the right answer. It builds on the mechanics laid out in the ",[31,32,34],"a",{"href":33},"\u002Fcss-only-micro-interactions-animations\u002Fscroll-driven-animations\u002F","scroll-driven animations"," guide.",[37,38],"hr",{},[40,41,43],"h2",{"id":42},"why-a-timeline-beats-an-observer-here","Why a Timeline Beats an Observer Here",[14,45,46,47,51],{},"An IntersectionObserver is an ",[48,49,50],"em",{},"event"," source. It tells you that an element has crossed a threshold — 0%, 25%, whatever you configured — and hands you a callback on the main thread. From there you toggle a class, and a time-based CSS animation plays for 400ms regardless of what the reader does next. Three consequences follow from that shape, and all three are things people then spend effort papering over.",[14,53,54,55,58],{},"The reveal is decoupled from the gesture. Scroll fast and every card in the viewport fires at once, then plays out on its own clock while the page has already moved on. Scroll back up and nothing rewinds, because the observer only fired once. Any sense that the motion is ",[48,56,57],{},"responding"," to the reader is gone; it is a canned clip triggered by proximity.",[14,60,61],{},"The callback competes for the main thread. Threshold crossings are delivered between frames, so during heavy work they arrive late and in clumps, which is why observer-driven reveals so often appear in a burst after a stall rather than smoothly.",[14,63,64,65,68],{},"And there is state to manage. Elements added after the observer was created need to be observed; elements removed need to be unobserved; server-rendered content is un-revealed until hydration; and the un-revealed state is ",[18,66,67],{},"opacity: 0",", so a script failure means invisible content.",[14,70,71,72,75],{},"A view progress timeline replaces all of that with a mapping. The element's progress through the scrollport ",[48,73,74],{},"is"," the animation's progress, sampled by the compositor in the same frame that applies the scroll. Scroll slowly and the card rises slowly; drag the scrollbar backwards and — if your range allows it — the card lowers again. There is nothing to observe, nothing to unobserve, and nothing to hydrate.",[14,77,78],{},"The observer still wins in two cases. First, when the trigger must do something that is not a style: fire analytics, start a video, lazily request data. A timeline changes property values; it cannot call a function. Second, when you need a genuine one-shot latch — \"animate the first time this is seen, never again, even after a reload-free navigation\" — because a timeline is stateless by design and will happily replay if the element re-enters. If you catch yourself trying to force a timeline to latch, the observer is the honest tool.",[80,81,87,88,87,92,87,96,87,103,87,109,87,112,87,116,87,127,87,130,87,133,87,142,87,147,87,151,87,156,87,161,87,166,87,171,87,174,87,177],"svg",{"viewBox":82,"role":83,"ariaLabel":84,"xmlns":85,"style":86},"0 0 720 360","img","Three snapshots of a card moving up through the scrollport with its reveal progress at each position","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;height:auto;max-width:720px;margin:2rem 0","\n  ",[89,90,91],"title",{},"A card's reveal progress across the scrollport",[93,94,95],"desc",{},"Three scrollport frames showing a card entering at the bottom, rising to the middle, and settling in the upper area, with the reveal complete and held at the third position.",[97,98,102],"text",{"x":99,"y":100,"style":101},"360","26","text-anchor:middle;fill:currentColor;font:700 17px sans-serif","animation-range: entry 0% cover 40%",[97,104,108],{"x":105,"y":106,"style":107},"130","60","text-anchor:middle;fill:currentColor;font:600 13px sans-serif;opacity:0.85","entering",[97,110,111],{"x":99,"y":106,"style":107},"rising",[97,113,115],{"x":114,"y":106,"style":107},"590","settled",[117,118],"rect",{"x":119,"y":120,"width":121,"height":121,"rx":122,"fill":123,"stroke":124,"strokeWidth":125,"opacity":126},"30","70","200","10","none","currentColor","1.5","0.5",[117,128],{"x":129,"y":120,"width":121,"height":121,"rx":122,"fill":123,"stroke":124,"strokeWidth":125,"opacity":126},"260",[117,131],{"x":132,"y":120,"width":121,"height":121,"rx":122,"fill":123,"stroke":124,"strokeWidth":125,"opacity":126},"490",[117,134],{"x":135,"y":136,"width":137,"height":138,"rx":139,"fill":140,"opacity":141},"55","228","150","34","6","#7aa2ff","0.12",[97,143,146],{"x":105,"y":144,"style":145},"250","text-anchor:middle;fill:currentColor;font:12px sans-serif;opacity:0.6","opacity 0",[117,148],{"x":149,"y":137,"width":137,"height":138,"rx":139,"fill":140,"opacity":150},"285","0.24",[97,152,155],{"x":99,"y":153,"style":154},"172","text-anchor:middle;fill:currentColor;font:12px sans-serif;opacity:0.8","opacity 0.5",[117,157],{"x":158,"y":159,"width":137,"height":138,"rx":139,"fill":140,"opacity":160},"515","94","0.4",[97,162,165],{"x":114,"y":163,"style":164},"116","text-anchor:middle;fill:currentColor;font:12px sans-serif","opacity 1",[97,167,170],{"x":105,"y":168,"style":169},"296","text-anchor:middle;fill:currentColor;font:12px ui-monospace,monospace","entry 0%",[97,172,173],{"x":99,"y":168,"style":169},"cover 20%",[97,175,176],{"x":114,"y":168,"style":169},"cover 40%",[97,178,181],{"x":99,"y":179,"style":180},"336","text-anchor:middle;fill:currentColor;font:12px sans-serif;opacity:0.7","Past the range end, fill-mode both holds the final keyframe.",[37,183],{},[40,185,187],{"id":186},"complete-working-implementation","Complete Working Implementation",[14,189,190],{},"Scroll the frame. Each card rises and fades in as it crosses into view, then stays where it is — it does not fade back out at the top, because the range ends early and the fill mode holds it.",[192,193],"live-demo",{"name":194},"scroll-reveal-cards",[196,197,202],"pre",{"className":198,"code":199,"language":200,"meta":201,"style":201},"language-html shiki shiki-themes github-light-high-contrast github-dark-high-contrast","\u003C!doctype html>\n\u003Chtml lang=\"en\">\n\u003Chead>\n\u003Cmeta charset=\"utf-8\">\n\u003Cmeta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n\u003Ctitle>Reveal on scroll\u003C\u002Ftitle>\n\u003Cstyle>\n  body { margin: 0; font: 16px\u002F1.6 system-ui, sans-serif; }\n  .feed { display: grid; gap: 1.5rem; max-width: 34rem;\n          margin: 0 auto; padding: 2rem 1.25rem 60vh; }\n\n  .card {\n    padding: 1rem 1.1rem;\n    border-radius: 12px;\n    border: 1px solid #d7dced;\n    background: #f4f6fb;\n\n    \u002F* `both` is load-bearing. It applies the `from` keyframe before the\n       range starts and holds the `to` keyframe after it ends. Without it\n       the card renders fully visible, snaps to hidden when the range\n       opens, and snaps back at the end. *\u002F\n    animation: reveal linear both;\n\n    \u002F* An anonymous view timeline whose subject is this card. Every card\n       gets its own independent 0%-100%, with no naming or wiring. *\u002F\n    animation-timeline: view();\n\n    \u002F* Start the moment the card's leading edge crosses into the\n       scrollport; be finished once the card is 40% of the way across it. *\u002F\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* Positional motion tied to the scroll gesture is a vestibular trigger.\n     Under reduced motion the cards are simply present. *\u002F\n  @media (prefers-reduced-motion: reduce) {\n    .card {\n      animation: none;\n      animation-timeline: none;\n      opacity: 1;\n      transform: none;\n    }\n  }\n\u003C\u002Fstyle>\n\u003C\u002Fhead>\n\u003Cbody>\n  \u003Cdiv class=\"feed\">\n    \u003Carticle class=\"card\">\u003Ch3>Ingest\u003C\u002Fh3>\u003Cp>Events land in the queue.\u003C\u002Fp>\u003C\u002Farticle>\n    \u003Carticle class=\"card\">\u003Ch3>Normalise\u003C\u002Fh3>\u003Cp>Fields are coerced to the schema.\u003C\u002Fp>\u003C\u002Farticle>\n    \u003Carticle class=\"card\">\u003Ch3>Enrich\u003C\u002Fh3>\u003Cp>Lookups attach account metadata.\u003C\u002Fp>\u003C\u002Farticle>\n    \u003Carticle class=\"card\">\u003Ch3>Fan out\u003C\u002Fh3>\u003Cp>Subscribers receive the record.\u003C\u002Fp>\u003C\u002Farticle>\n    \u003Carticle class=\"card\">\u003Ch3>Archive\u003C\u002Fh3>\u003Cp>Raw payloads go to cold storage.\u003C\u002Fp>\u003C\u002Farticle>\n  \u003C\u002Fdiv>\n\u003C\u002Fbody>\n\u003C\u002Fhtml>\n","html","",[18,203,204,224,244,254,272,297,311,321,372,414,452,459,468,488,503,523,536,541,548,554,560,566,583,588,594,600,609,614,620,626,650,656,661,673,709,738,743,748,754,760,769,777,789,801,813,825,831,836,846,855,865,884,927,964,1001,1038,1075,1085,1094],{"__ignoreMap":201},[205,206,209,213,217,221],"span",{"class":207,"line":208},"line",1,[205,210,212],{"class":211},"suds8","\u003C!",[205,214,216],{"class":215},"sne4z","doctype",[205,218,220],{"class":219},"s-5SL"," html",[205,222,223],{"class":211},">\n",[205,225,227,230,232,235,238,242],{"class":207,"line":226},2,[205,228,229],{"class":211},"\u003C",[205,231,200],{"class":215},[205,233,234],{"class":219}," lang",[205,236,237],{"class":211},"=",[205,239,241],{"class":240},"sT6z2","\"en\"",[205,243,223],{"class":211},[205,245,247,249,252],{"class":207,"line":246},3,[205,248,229],{"class":211},[205,250,251],{"class":215},"head",[205,253,223],{"class":211},[205,255,257,259,262,265,267,270],{"class":207,"line":256},4,[205,258,229],{"class":211},[205,260,261],{"class":215},"meta",[205,263,264],{"class":219}," charset",[205,266,237],{"class":211},[205,268,269],{"class":240},"\"utf-8\"",[205,271,223],{"class":211},[205,273,275,277,279,282,284,287,290,292,295],{"class":207,"line":274},5,[205,276,229],{"class":211},[205,278,261],{"class":215},[205,280,281],{"class":219}," name",[205,283,237],{"class":211},[205,285,286],{"class":240},"\"viewport\"",[205,288,289],{"class":219}," content",[205,291,237],{"class":211},[205,293,294],{"class":240},"\"width=device-width,initial-scale=1\"",[205,296,223],{"class":211},[205,298,300,302,304,307,309],{"class":207,"line":299},6,[205,301,229],{"class":211},[205,303,89],{"class":215},[205,305,306],{"class":211},">Reveal on scroll\u003C\u002F",[205,308,89],{"class":215},[205,310,223],{"class":211},[205,312,314,316,319],{"class":207,"line":313},7,[205,315,229],{"class":211},[205,317,318],{"class":215},"style",[205,320,223],{"class":211},[205,322,324,327,330,333,336,339,342,345,347,350,354,357,360,363,366,369],{"class":207,"line":323},8,[205,325,326],{"class":215},"  body",[205,328,329],{"class":211}," { ",[205,331,332],{"class":219},"margin",[205,334,335],{"class":211},": ",[205,337,338],{"class":219},"0",[205,340,341],{"class":211},"; ",[205,343,344],{"class":219},"font",[205,346,335],{"class":211},[205,348,349],{"class":219},"16",[205,351,353],{"class":352},"sHUrx","px",[205,355,356],{"class":211},"\u002F",[205,358,359],{"class":219},"1.6",[205,361,362],{"class":219}," system-ui",[205,364,365],{"class":211},", ",[205,367,368],{"class":219},"sans-serif",[205,370,371],{"class":211},"; }\n",[205,373,375,378,380,383,385,388,390,393,395,397,400,402,405,407,409,411],{"class":207,"line":374},9,[205,376,377],{"class":219},"  .feed",[205,379,329],{"class":211},[205,381,382],{"class":219},"display",[205,384,335],{"class":211},[205,386,387],{"class":219},"grid",[205,389,341],{"class":211},[205,391,392],{"class":219},"gap",[205,394,335],{"class":211},[205,396,125],{"class":219},[205,398,399],{"class":352},"rem",[205,401,341],{"class":211},[205,403,404],{"class":219},"max-width",[205,406,335],{"class":211},[205,408,138],{"class":219},[205,410,399],{"class":352},[205,412,413],{"class":211},";\n",[205,415,417,420,422,424,427,429,432,434,437,439,442,444,447,450],{"class":207,"line":416},10,[205,418,419],{"class":219},"          margin",[205,421,335],{"class":211},[205,423,338],{"class":219},[205,425,426],{"class":219}," auto",[205,428,341],{"class":211},[205,430,431],{"class":219},"padding",[205,433,335],{"class":211},[205,435,436],{"class":219},"2",[205,438,399],{"class":352},[205,440,441],{"class":219}," 1.25",[205,443,399],{"class":352},[205,445,446],{"class":219}," 60",[205,448,449],{"class":352},"vh",[205,451,371],{"class":211},[205,453,455],{"class":207,"line":454},11,[205,456,458],{"emptyLinePlaceholder":457},true,"\n",[205,460,462,465],{"class":207,"line":461},12,[205,463,464],{"class":219},"  .card",[205,466,467],{"class":211}," {\n",[205,469,471,474,476,479,481,484,486],{"class":207,"line":470},13,[205,472,473],{"class":219},"    padding",[205,475,335],{"class":211},[205,477,478],{"class":219},"1",[205,480,399],{"class":352},[205,482,483],{"class":219}," 1.1",[205,485,399],{"class":352},[205,487,413],{"class":211},[205,489,491,494,496,499,501],{"class":207,"line":490},14,[205,492,493],{"class":219},"    border-radius",[205,495,335],{"class":211},[205,497,498],{"class":219},"12",[205,500,353],{"class":352},[205,502,413],{"class":211},[205,504,506,509,511,513,515,518,521],{"class":207,"line":505},15,[205,507,508],{"class":219},"    border",[205,510,335],{"class":211},[205,512,478],{"class":219},[205,514,353],{"class":352},[205,516,517],{"class":219}," solid",[205,519,520],{"class":219}," #d7dced",[205,522,413],{"class":211},[205,524,526,529,531,534],{"class":207,"line":525},16,[205,527,528],{"class":219},"    background",[205,530,335],{"class":211},[205,532,533],{"class":219},"#f4f6fb",[205,535,413],{"class":211},[205,537,539],{"class":207,"line":538},17,[205,540,458],{"emptyLinePlaceholder":457},[205,542,544],{"class":207,"line":543},18,[205,545,547],{"class":546},"sLBg1","    \u002F* `both` is load-bearing. It applies the `from` keyframe before the\n",[205,549,551],{"class":207,"line":550},19,[205,552,553],{"class":546},"       range starts and holds the `to` keyframe after it ends. Without it\n",[205,555,557],{"class":207,"line":556},20,[205,558,559],{"class":546},"       the card renders fully visible, snaps to hidden when the range\n",[205,561,563],{"class":207,"line":562},21,[205,564,565],{"class":546},"       opens, and snaps back at the end. *\u002F\n",[205,567,569,572,575,578,581],{"class":207,"line":568},22,[205,570,571],{"class":219},"    animation",[205,573,574],{"class":211},": reveal ",[205,576,577],{"class":219},"linear",[205,579,580],{"class":219}," both",[205,582,413],{"class":211},[205,584,586],{"class":207,"line":585},23,[205,587,458],{"emptyLinePlaceholder":457},[205,589,591],{"class":207,"line":590},24,[205,592,593],{"class":546},"    \u002F* An anonymous view timeline whose subject is this card. Every card\n",[205,595,597],{"class":207,"line":596},25,[205,598,599],{"class":546},"       gets its own independent 0%-100%, with no naming or wiring. *\u002F\n",[205,601,603,606],{"class":207,"line":602},26,[205,604,605],{"class":219},"    animation-timeline",[205,607,608],{"class":211},": view();\n",[205,610,612],{"class":207,"line":611},27,[205,613,458],{"emptyLinePlaceholder":457},[205,615,617],{"class":207,"line":616},28,[205,618,619],{"class":546},"    \u002F* Start the moment the card's leading edge crosses into the\n",[205,621,623],{"class":207,"line":622},29,[205,624,625],{"class":546},"       scrollport; be finished once the card is 40% of the way across it. *\u002F\n",[205,627,629,632,635,637,640,643,646,648],{"class":207,"line":628},30,[205,630,631],{"class":219},"    animation-range",[205,633,634],{"class":211},": entry ",[205,636,338],{"class":219},[205,638,639],{"class":352},"%",[205,641,642],{"class":219}," cover",[205,644,645],{"class":219}," 40",[205,647,639],{"class":352},[205,649,413],{"class":211},[205,651,653],{"class":207,"line":652},31,[205,654,655],{"class":211},"  }\n",[205,657,659],{"class":207,"line":658},32,[205,660,458],{"emptyLinePlaceholder":457},[205,662,664,667,671],{"class":207,"line":663},33,[205,665,666],{"class":352},"  @keyframes",[205,668,670],{"class":669},"soyes"," reveal",[205,672,467],{"class":211},[205,674,676,679,681,684,686,688,690,693,695,698,701,704,706],{"class":207,"line":675},34,[205,677,678],{"class":219},"    from",[205,680,329],{"class":211},[205,682,683],{"class":219},"opacity",[205,685,335],{"class":211},[205,687,338],{"class":219},[205,689,341],{"class":211},[205,691,692],{"class":219},"transform",[205,694,335],{"class":211},[205,696,697],{"class":219},"translateY",[205,699,700],{"class":211},"(",[205,702,703],{"class":219},"28",[205,705,353],{"class":352},[205,707,708],{"class":211},"); }\n",[205,710,712,715,718,720,722,724,726,728,730,732,734,736],{"class":207,"line":711},35,[205,713,714],{"class":219},"    to",[205,716,717],{"class":211},"   { ",[205,719,683],{"class":219},[205,721,335],{"class":211},[205,723,478],{"class":219},[205,725,341],{"class":211},[205,727,692],{"class":219},[205,729,335],{"class":211},[205,731,697],{"class":219},[205,733,700],{"class":211},[205,735,338],{"class":219},[205,737,708],{"class":211},[205,739,741],{"class":207,"line":740},36,[205,742,655],{"class":211},[205,744,746],{"class":207,"line":745},37,[205,747,458],{"emptyLinePlaceholder":457},[205,749,751],{"class":207,"line":750},38,[205,752,753],{"class":546},"  \u002F* Positional motion tied to the scroll gesture is a vestibular trigger.\n",[205,755,757],{"class":207,"line":756},39,[205,758,759],{"class":546},"     Under reduced motion the cards are simply present. *\u002F\n",[205,761,763,766],{"class":207,"line":762},40,[205,764,765],{"class":352},"  @media",[205,767,768],{"class":211}," (prefers-reduced-motion: reduce) {\n",[205,770,772,775],{"class":207,"line":771},41,[205,773,774],{"class":219},"    .card",[205,776,467],{"class":211},[205,778,780,783,785,787],{"class":207,"line":779},42,[205,781,782],{"class":219},"      animation",[205,784,335],{"class":211},[205,786,123],{"class":219},[205,788,413],{"class":211},[205,790,792,795,797,799],{"class":207,"line":791},43,[205,793,794],{"class":219},"      animation-timeline",[205,796,335],{"class":211},[205,798,123],{"class":219},[205,800,413],{"class":211},[205,802,804,807,809,811],{"class":207,"line":803},44,[205,805,806],{"class":219},"      opacity",[205,808,335],{"class":211},[205,810,478],{"class":219},[205,812,413],{"class":211},[205,814,816,819,821,823],{"class":207,"line":815},45,[205,817,818],{"class":219},"      transform",[205,820,335],{"class":211},[205,822,123],{"class":219},[205,824,413],{"class":211},[205,826,828],{"class":207,"line":827},46,[205,829,830],{"class":211},"    }\n",[205,832,834],{"class":207,"line":833},47,[205,835,655],{"class":211},[205,837,839,842,844],{"class":207,"line":838},48,[205,840,841],{"class":211},"\u003C\u002F",[205,843,318],{"class":215},[205,845,223],{"class":211},[205,847,849,851,853],{"class":207,"line":848},49,[205,850,841],{"class":211},[205,852,251],{"class":215},[205,854,223],{"class":211},[205,856,858,860,863],{"class":207,"line":857},50,[205,859,229],{"class":211},[205,861,862],{"class":215},"body",[205,864,223],{"class":211},[205,866,868,871,874,877,879,882],{"class":207,"line":867},51,[205,869,870],{"class":211},"  \u003C",[205,872,873],{"class":215},"div",[205,875,876],{"class":219}," class",[205,878,237],{"class":211},[205,880,881],{"class":240},"\"feed\"",[205,883,223],{"class":211},[205,885,887,890,893,895,897,900,903,906,909,911,913,915,918,920,923,925],{"class":207,"line":886},52,[205,888,889],{"class":211},"    \u003C",[205,891,892],{"class":215},"article",[205,894,876],{"class":219},[205,896,237],{"class":211},[205,898,899],{"class":240},"\"card\"",[205,901,902],{"class":211},">\u003C",[205,904,905],{"class":215},"h3",[205,907,908],{"class":211},">Ingest\u003C\u002F",[205,910,905],{"class":215},[205,912,902],{"class":211},[205,914,14],{"class":215},[205,916,917],{"class":211},">Events land in the queue.\u003C\u002F",[205,919,14],{"class":215},[205,921,922],{"class":211},">\u003C\u002F",[205,924,892],{"class":215},[205,926,223],{"class":211},[205,928,930,932,934,936,938,940,942,944,947,949,951,953,956,958,960,962],{"class":207,"line":929},53,[205,931,889],{"class":211},[205,933,892],{"class":215},[205,935,876],{"class":219},[205,937,237],{"class":211},[205,939,899],{"class":240},[205,941,902],{"class":211},[205,943,905],{"class":215},[205,945,946],{"class":211},">Normalise\u003C\u002F",[205,948,905],{"class":215},[205,950,902],{"class":211},[205,952,14],{"class":215},[205,954,955],{"class":211},">Fields are coerced to the schema.\u003C\u002F",[205,957,14],{"class":215},[205,959,922],{"class":211},[205,961,892],{"class":215},[205,963,223],{"class":211},[205,965,967,969,971,973,975,977,979,981,984,986,988,990,993,995,997,999],{"class":207,"line":966},54,[205,968,889],{"class":211},[205,970,892],{"class":215},[205,972,876],{"class":219},[205,974,237],{"class":211},[205,976,899],{"class":240},[205,978,902],{"class":211},[205,980,905],{"class":215},[205,982,983],{"class":211},">Enrich\u003C\u002F",[205,985,905],{"class":215},[205,987,902],{"class":211},[205,989,14],{"class":215},[205,991,992],{"class":211},">Lookups attach account metadata.\u003C\u002F",[205,994,14],{"class":215},[205,996,922],{"class":211},[205,998,892],{"class":215},[205,1000,223],{"class":211},[205,1002,1004,1006,1008,1010,1012,1014,1016,1018,1021,1023,1025,1027,1030,1032,1034,1036],{"class":207,"line":1003},55,[205,1005,889],{"class":211},[205,1007,892],{"class":215},[205,1009,876],{"class":219},[205,1011,237],{"class":211},[205,1013,899],{"class":240},[205,1015,902],{"class":211},[205,1017,905],{"class":215},[205,1019,1020],{"class":211},">Fan out\u003C\u002F",[205,1022,905],{"class":215},[205,1024,902],{"class":211},[205,1026,14],{"class":215},[205,1028,1029],{"class":211},">Subscribers receive the record.\u003C\u002F",[205,1031,14],{"class":215},[205,1033,922],{"class":211},[205,1035,892],{"class":215},[205,1037,223],{"class":211},[205,1039,1041,1043,1045,1047,1049,1051,1053,1055,1058,1060,1062,1064,1067,1069,1071,1073],{"class":207,"line":1040},56,[205,1042,889],{"class":211},[205,1044,892],{"class":215},[205,1046,876],{"class":219},[205,1048,237],{"class":211},[205,1050,899],{"class":240},[205,1052,902],{"class":211},[205,1054,905],{"class":215},[205,1056,1057],{"class":211},">Archive\u003C\u002F",[205,1059,905],{"class":215},[205,1061,902],{"class":211},[205,1063,14],{"class":215},[205,1065,1066],{"class":211},">Raw payloads go to cold storage.\u003C\u002F",[205,1068,14],{"class":215},[205,1070,922],{"class":211},[205,1072,892],{"class":215},[205,1074,223],{"class":211},[205,1076,1078,1081,1083],{"class":207,"line":1077},57,[205,1079,1080],{"class":211},"  \u003C\u002F",[205,1082,873],{"class":215},[205,1084,223],{"class":211},[205,1086,1088,1090,1092],{"class":207,"line":1087},58,[205,1089,841],{"class":211},[205,1091,862],{"class":215},[205,1093,223],{"class":211},[205,1095,1097,1099,1101],{"class":207,"line":1096},59,[205,1098,841],{"class":211},[205,1100,200],{"class":215},[205,1102,223],{"class":211},[14,1104,1105,1106,1109,1110,1113,1114,1116],{},"Note the ",[18,1107,1108],{},"60vh"," of bottom padding on ",[18,1111,1112],{},".feed",". Without it the last card never travels far enough through the scrollport for ",[18,1115,176],{}," to be reached, and it stays permanently half-revealed. This is the most common bug in view-timeline reveals and it has nothing to do with the CSS — it is a consequence of the page ending before the element's pass does.",[37,1118],{},[40,1120,1122],{"id":1121},"the-key-technique-why-fill-mode-is-not-optional","The Key Technique: Why fill-mode Is Not Optional",[14,1124,1125,1126,1129,1130,1132,1133,1135],{},"A time-based animation with a delay is ",[48,1127,1128],{},"pending"," before it starts: the element sits in its own declared styles until the clock reaches the delay. A range-bounded scroll-driven animation behaves the same way, but the \"before\" and \"after\" regions are now enormous — they are every scroll position outside ",[18,1131,170],{}," to ",[18,1134,176],{},", which for most of a long page is the majority of the element's life.",[14,1137,1138,1140],{},[18,1139,28],{}," decides what happens in those two regions:",[1142,1143,1144,1157,1163,1169],"ul",{},[1145,1146,1147,1149,1150,1152,1153,1156],"li",{},[18,1148,123],{}," (the default): outside the range the element renders with its normal, un-animated styles. A card declared without ",[18,1151,67],{}," therefore sits at full opacity below the fold, snaps to invisible the instant its range opens, animates in, and then snaps ",[48,1154,1155],{},"back"," to full opacity when the range closes. Two visible flashes for the price of one.",[1145,1158,1159,1162],{},[18,1160,1161],{},"forwards",": the final keyframe is held after the range ends — the settled state survives — but the element is still fully visible before the range opens.",[1145,1164,1165,1168],{},[18,1166,1167],{},"backwards",": the first keyframe applies before the range opens, so the card is correctly hidden below the fold, but after the range closes it reverts to its declared styles.",[1145,1170,1171,1174,1175,1178],{},[18,1172,1173],{},"both",": the union. Hidden before, revealed after. This is the only correct value for a reveal, and it is why the shorthand reads ",[18,1176,1177],{},"animation: reveal linear both",".",[14,1180,1181,1182,1184,1185,1188,1189,1191,1192,1195,1196,1198,1199,1202],{},"The alternative sometimes proposed — set ",[18,1183,67],{}," on ",[18,1186,1187],{},".card"," itself and use ",[18,1190,1161],{}," — half works and is worse. It makes the hidden state the element's ",[48,1193,1194],{},"real"," style, which means the card is invisible to anyone whose browser did not apply the animation at all, whether through lack of support, a stylesheet that failed to load, or print. With ",[18,1197,1173],{},", the declared style stays ",[18,1200,1201],{},"opacity: 1"," and the hidden state exists only inside the animation, so every failure mode degrades to visible content. Reveals must fail open.",[37,1204],{},[40,1206,1208],{"id":1207},"variation-staggering-the-reveal-without-delays","Variation: Staggering the Reveal Without Delays",[14,1210,1211,1212,1215,1216,1220,1221,1223,1224,1227],{},"On a time-based entrance you stagger with ",[18,1213,1214],{},"animation-delay",", as in the ",[31,1217,1219],{"href":1218},"\u002Fcss-only-micro-interactions-animations\u002Fkeyframe-animation-patterns\u002Fstaggered-list-animations-with-custom-properties\u002F","staggered list animations"," pattern. That does not work on a scroll timeline — ",[18,1222,1214],{}," has no meaning when progress is geometric. The equivalent lever is the ",[48,1225,1226],{},"range",": give consecutive items slightly later ranges, and because they also sit at different heights, the effect compounds into a cascade as the group scrolls in.",[196,1229,1233],{"className":1230,"code":1231,"language":1232,"meta":201,"style":201},"language-css shiki shiki-themes github-light-high-contrast github-dark-high-contrast",".card { animation: reveal linear both; animation-timeline: view(); }\n\n.card:nth-child(3n + 1) { animation-range: entry 0% cover 30%; }\n.card:nth-child(3n + 2) { animation-range: entry 10% cover 40%; }\n.card:nth-child(3n + 3) { animation-range: entry 20% cover 50%; }\n","css",[18,1234,1235,1258,1262,1293,1320],{"__ignoreMap":201},[205,1236,1237,1239,1241,1244,1246,1248,1250,1252,1255],{"class":207,"line":208},[205,1238,1187],{"class":219},[205,1240,329],{"class":211},[205,1242,1243],{"class":219},"animation",[205,1245,574],{"class":211},[205,1247,577],{"class":219},[205,1249,580],{"class":219},[205,1251,341],{"class":211},[205,1253,1254],{"class":219},"animation-timeline",[205,1256,1257],{"class":211},": view(); }\n",[205,1259,1260],{"class":207,"line":226},[205,1261,458],{"emptyLinePlaceholder":457},[205,1263,1264,1267,1269,1272,1275,1278,1280,1282,1284,1286,1289,1291],{"class":207,"line":246},[205,1265,1266],{"class":219},".card:nth-child",[205,1268,700],{"class":211},[205,1270,1271],{"class":219},"3n + 1",[205,1273,1274],{"class":211},") { ",[205,1276,1277],{"class":219},"animation-range",[205,1279,634],{"class":211},[205,1281,338],{"class":219},[205,1283,639],{"class":352},[205,1285,642],{"class":219},[205,1287,1288],{"class":219}," 30",[205,1290,639],{"class":352},[205,1292,371],{"class":211},[205,1294,1295,1297,1299,1302,1304,1306,1308,1310,1312,1314,1316,1318],{"class":207,"line":256},[205,1296,1266],{"class":219},[205,1298,700],{"class":211},[205,1300,1301],{"class":219},"3n + 2",[205,1303,1274],{"class":211},[205,1305,1277],{"class":219},[205,1307,634],{"class":211},[205,1309,122],{"class":219},[205,1311,639],{"class":352},[205,1313,642],{"class":219},[205,1315,645],{"class":219},[205,1317,639],{"class":352},[205,1319,371],{"class":211},[205,1321,1322,1324,1326,1329,1331,1333,1335,1338,1340,1342,1345,1347],{"class":207,"line":274},[205,1323,1266],{"class":219},[205,1325,700],{"class":211},[205,1327,1328],{"class":219},"3n + 3",[205,1330,1274],{"class":211},[205,1332,1277],{"class":219},[205,1334,634],{"class":211},[205,1336,1337],{"class":219},"20",[205,1339,639],{"class":352},[205,1341,642],{"class":219},[205,1343,1344],{"class":219}," 50",[205,1346,639],{"class":352},[205,1348,371],{"class":211},[14,1350,1351,1352,1355,1356,1359],{},"An alternative that scales better to a grid is a ",[18,1353,1354],{},"view()"," inset. ",[18,1357,1358],{},"view(block 20% 0%)"," shrinks the notional scrollport from the top by 20%, so the timeline for that element starts later in absolute scroll terms:",[196,1361,1363],{"className":1230,"code":1362,"language":1232,"meta":201,"style":201},".card:nth-child(even) {\n  animation-timeline: view(block 20% 0%);\n}\n",[18,1364,1365,1377,1401],{"__ignoreMap":201},[205,1366,1367,1369,1371,1374],{"class":207,"line":208},[205,1368,1266],{"class":219},[205,1370,700],{"class":211},[205,1372,1373],{"class":219},"even",[205,1375,1376],{"class":211},") {\n",[205,1378,1379,1382,1385,1388,1391,1393,1396,1398],{"class":207,"line":226},[205,1380,1381],{"class":219},"  animation-timeline",[205,1383,1384],{"class":211},": view(",[205,1386,1387],{"class":219},"block",[205,1389,1390],{"class":219}," 20",[205,1392,639],{"class":352},[205,1394,1395],{"class":219}," 0",[205,1397,639],{"class":352},[205,1399,1400],{"class":211},");\n",[205,1402,1403],{"class":207,"line":246},[205,1404,1405],{"class":211},"}\n",[14,1407,1408,1409,1412,1413,1417],{},"Insets are also the fix for sticky headers: if a 64px bar covers the top of the scrollport, ",[18,1410,1411],{},"view(block 0 64px)"," stops elements from being considered \"in view\" while they are underneath it. Both variants pair naturally with size-aware components — a grid built the way ",[31,1414,1416],{"href":1415},"\u002Fmastering-container-queries-responsive-layouts\u002Fresponsive-component-patterns\u002Fbuilding-responsive-cards-with-container-queries\u002F","responsive cards with container queries"," describes will reflow from three columns to one, and a range-based stagger stays coherent through that reflow in a way that a fixed per-item delay does not.",[37,1419],{},[40,1421,1423],{"id":1422},"browser-support","Browser Support",[14,1425,1426,1428,1429,1431,1432,1435,1436,1440,1441,1444,1445,1178],{},[18,1427,24],{}," and the ",[18,1430,1277],{}," properties shipped together in Chrome and Edge 115 and in Safari 26. Firefox has an implementation behind a preference as of mid-2026 and should be planned for as unsupported. Because the correct build declares the visible state on the element and hides it only inside the animation, an unsupported browser shows every card immediately with no guard at all — which is the right outcome. Add ",[18,1433,1434],{},"@supports (animation-timeline: view())"," only when the enhanced version changes layout rather than just opacity and transform, and see ",[31,1437,1439],{"href":1438},"\u002Fcss-only-micro-interactions-animations\u002Fscroll-driven-animations\u002Fscroll-driven-animation-fallbacks\u002F","scroll-driven animation fallbacks"," for how to structure that. Keep the ",[18,1442,1443],{},"prefers-reduced-motion"," branch regardless of support, since it is an obligation about the effect, not about the feature — the reasoning is set out in ",[31,1446,1448],{"href":1447},"\u002Fcss-only-micro-interactions-animations\u002Faccessibility-in-css-animations\u002Fvestibular-safe-animation-patterns\u002F","vestibular-safe animation patterns",[37,1450],{},[40,1452,1454],{"id":1453},"faq","FAQ",[14,1456,1457,1461,1462,1464,1465,1468,1469,1471,1472,1474,1475,1478],{},[1458,1459,1460],"strong",{},"Why is animation-fill-mode both required for a view() reveal?","\nThe animation only exists inside its range. Before the range starts and after it ends the element renders with its own styles, so without fill ",[18,1463,1173],{}," it appears fully visible, jumps to the ",[18,1466,1467],{},"from"," keyframe when the range begins, and jumps back at the end. Fill ",[18,1470,1173],{}," applies the ",[18,1473,1467],{}," state before and holds the ",[18,1476,1477],{},"to"," state after.",[14,1480,1481,1484],{},[1458,1482,1483],{},"How is view() different from IntersectionObserver?","\nIntersectionObserver reports a threshold crossing as a discrete event on the main thread, and you write the animation yourself. A view timeline gives continuous scroll-linked progress evaluated by the compositor, with no event, no class toggle, and no script.",[14,1486,1487,1490,1491,1494,1495,1498],{},[1458,1488,1489],{},"Does animation-range entry 0% cover 40% mean two different phases?","\nYes, and that is intentional. The start is measured in the ",[18,1492,1493],{},"entry"," phase and the end in the ",[18,1496,1497],{},"cover"," phase, so the reveal begins as the element's leading edge crosses in and finishes once it is forty percent of the way across the scrollport.",[14,1500,1501,1504],{},[1458,1502,1503],{},"Why does my reveal never fire for content near the bottom of the page?","\nBecause that element never travels far enough through the scrollport for its range to complete. Add bottom padding to the page, shorten the range, or add a scroll-driven fallback so the final elements are visible without any scrolling.",[37,1506],{},[40,1508,1510],{"id":1509},"related","Related",[1142,1512,1513,1519,1530,1536,1542],{},[1145,1514,1515,1518],{},[31,1516,1517],{"href":33},"Scroll-Driven Animations"," — the full reference for timelines, ranges, and named timeline scoping.",[1145,1520,1521,1525,1526,1529],{},[31,1522,1524],{"href":1523},"\u002Fcss-only-micro-interactions-animations\u002Fscroll-driven-animations\u002Fscroll-progress-bar-without-javascript\u002F","Scroll progress bar without JavaScript"," — the document-wide ",[18,1527,1528],{},"scroll()"," counterpart to this per-element pattern.",[1145,1531,1532,1535],{},[31,1533,1534],{"href":1438},"Scroll-driven animation fallbacks"," — shipping these reveals to browsers that cannot run them.",[1145,1537,1538,1541],{},[31,1539,1540],{"href":1218},"Staggered list animations with custom properties"," — the time-based stagger this page's range-based version replaces.",[1145,1543,1544,1547],{},[31,1545,1546],{"href":1415},"Building responsive cards with container queries"," — the card grid these reveals are usually applied to.",[318,1549,1550],{},"html pre.shiki code .suds8, html code.shiki .suds8{--shiki-default:#0E1116;--shiki-dark:#F0F3F6}html pre.shiki code .sne4z, html code.shiki .sne4z{--shiki-default:#024C1A;--shiki-dark:#72F088}html pre.shiki code .s-5SL, html code.shiki .s-5SL{--shiki-default:#023B95;--shiki-dark:#91CBFF}html pre.shiki code .sT6z2, html code.shiki .sT6z2{--shiki-default:#032563;--shiki-dark:#ADDCFF}html pre.shiki code .sHUrx, html code.shiki .sHUrx{--shiki-default:#A0111F;--shiki-dark:#FF9492}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}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":201,"searchDepth":226,"depth":226,"links":1552},[1553,1554,1555,1556,1557,1558,1559],{"id":42,"depth":226,"text":43},{"id":186,"depth":226,"text":187},{"id":1121,"depth":226,"text":1122},{"id":1207,"depth":226,"text":1208},{"id":1422,"depth":226,"text":1423},{"id":1453,"depth":226,"text":1454},{"id":1509,"depth":226,"text":1510},"Reveal elements on scroll with animation-timeline: view() and animation-range, why animation-fill-mode is mandatory, and how it compares to IntersectionObserver.","md",{"pageTitle":1563,"datePublished":1564,"dateModified":1564,"faq":1565},"Scroll-Triggered Reveal Animations","2026-07-20",[1566,1568,1570,1572],{"q":1460,"a":1567},"The animation only exists inside its range. Before the range starts and after it ends the element renders with its own styles, so without fill both it appears fully visible, jumps to the from keyframe when the range begins, and jumps back at the end. Fill both applies the from state before and holds the to state after.",{"q":1483,"a":1569},"IntersectionObserver reports a threshold crossing as a discrete event on the main thread, and you write the animation yourself. A view timeline gives continuous scroll-linked progress evaluated by the compositor, with no event, no class toggle, and no script.",{"q":1489,"a":1571},"Yes, and that is intentional. The start is measured in the entry phase and the end in the cover phase, so the reveal begins as the element's leading edge crosses in and finishes once it is forty percent of the way across the scrollport.",{"q":1503,"a":1573},"Because that element never travels far enough through the scrollport for its range to complete. Add bottom padding to the page, shorten the range, or add a scroll-driven fallback so the final elements are visible without any scrolling.","\u002Fcss-only-micro-interactions-animations\u002Fscroll-driven-animations\u002Fscroll-triggered-reveal-animations",{"title":5,"description":1560},"css-only-micro-interactions-animations\u002Fscroll-driven-animations\u002Fscroll-triggered-reveal-animations\u002Findex","7dn2WUtJUVUOZ_ol7B8NcXHLd0RAmMLOA7eoSyep5SU",[1579,1582,1585,1588,1591,1594,1597,1600,1603,1606,1609,1612,1615,1618,1621,1624,1627,1630,1633,1636,1639,1642,1645,1648,1651,1654,1657,1660,1663,1666,1669,1672,1675,1678,1681,1684,1687,1690,1693,1696,1699,1702,1703,1706,1709,1712,1715,1717,1720,1723,1726,1729,1732,1735,1738,1741,1744,1747,1750,1753,1756,1759,1762,1765,1768,1771,1774,1777,1780,1783,1786,1789,1792,1795,1798,1801,1804,1807,1810,1813,1816,1819,1822,1825,1828,1831,1834,1837],{"path":1580,"title":1581},"\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":1583,"title":1584},"\u002Fcss-only-micro-interactions-animations\u002Faccessibility-in-css-animations","Accessibility in CSS Animations: Patterns, Specs & Best Practices",{"path":1586,"title":1587},"\u002Fcss-only-micro-interactions-animations\u002Faccessibility-in-css-animations\u002Fprefers-reduced-motion-recipes","prefers-reduced-motion Recipes: A Reduction for Every Common Effect",{"path":1589,"title":1590},"\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":1592,"title":1593},"\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":1595,"title":1596},"\u002Fcss-only-micro-interactions-animations\u002Faccessibility-in-css-animations\u002Fvestibular-safe-animation-patterns","Vestibular-Safe Animation Patterns: Why Some Motion Makes People Sick",{"path":1598,"title":1599},"\u002Fcss-only-micro-interactions-animations\u002Faccessibility-in-css-animations\u002Fwcag-motion-success-criteria","WCAG Motion Success Criteria: What Each One Actually Requires",{"path":1601,"title":1602},"\u002Fcss-only-micro-interactions-animations\u002Fcontainer-aware-motion\u002Fcontainer-query-hover-affordances","Container Query Hover Affordances: When Narrow Means Persistent, Not Hidden",{"path":1604,"title":1605},"\u002Fcss-only-micro-interactions-animations\u002Fcontainer-aware-motion","Container-Aware Motion: Animation Sized by the Space a Component Actually Has",{"path":1607,"title":1608},"\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":1610,"title":1611},"\u002Fcss-only-micro-interactions-animations\u002Fcontainer-aware-motion\u002Fsuppressing-motion-in-small-containers","Suppressing Motion in Small Containers: Deliberately Animating Nothing",{"path":1613,"title":1614},"\u002Fcss-only-micro-interactions-animations\u002Fcss-animation-vs-web-animations-api","CSS Animation vs the Web Animations API: A Decision Guide",{"path":1616,"title":1617},"\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":1619,"title":1620},"\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":1622,"title":1623},"\u002Fcss-only-micro-interactions-animations\u002Fcss-custom-properties-architecture\u002Ffluid-spacing-tokens-driving-transition-durations","Fluid Spacing Tokens That Drive Transition Durations",{"path":1625,"title":1626},"\u002Fcss-only-micro-interactions-animations\u002Fcss-custom-properties-architecture","CSS Custom Properties Architecture: Scalable Design Systems & Dynamic UI",{"path":1628,"title":1629},"\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":1631,"title":1632},"\u002Fcss-only-micro-interactions-animations\u002Fcss-transition-fundamentals\u002Fcss-transition-timing-functions","CSS Transition Timing Functions: ease, cubic-bezier(), steps() and linear()",{"path":1634,"title":1635},"\u002Fcss-only-micro-interactions-animations\u002Fcss-transition-fundamentals","CSS Transition Fundamentals: Architecture, Performance & Patterns",{"path":1637,"title":1638},"\u002Fcss-only-micro-interactions-animations\u002Fcss-transition-fundamentals\u002Fstarting-style-entry-animations","@starting-style: Animating an Element's Very First Style Change",{"path":1640,"title":1641},"\u002Fcss-only-micro-interactions-animations\u002Fcss-transition-fundamentals\u002Ftransitioning-display-with-allow-discrete","Transitioning display with transition-behavior: allow-discrete and @starting-style",{"path":1643,"title":1644},"\u002Fcss-only-micro-interactions-animations\u002Fhover-focus-state-design\u002Faccessible-css-only-tooltips","Accessible CSS-Only Tooltips and Hover Cards With  and ",{"path":1646,"title":1647},"\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":1649,"title":1650},"\u002Fcss-only-micro-interactions-animations\u002Fhover-focus-state-design\u002Ffocus-within-form-patterns"," Form Patterns: Styling a Group From the Field Inside It",{"path":1652,"title":1653},"\u002Fcss-only-micro-interactions-animations\u002Fhover-focus-state-design","Hover & Focus State Design: Spec-Compliant Patterns for Modern UIs",{"path":1655,"title":1656},"\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":1658,"title":1659},"\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":1661,"title":1662},"\u002Fcss-only-micro-interactions-animations","CSS-Only Micro-Interactions & Animations: Architecture, Performance & Implementation",{"path":1664,"title":1665},"\u002Fcss-only-micro-interactions-animations\u002Fkeyframe-animation-patterns\u002Fcontainer-query-triggered-keyframe-animations","Container-Query-Triggered Keyframe Animations: Animate Differently per Available Space",{"path":1667,"title":1668},"\u002Fcss-only-micro-interactions-animations\u002Fkeyframe-animation-patterns\u002Fcss-only-accordions-and-disclosure","CSS-Only Accordions and Disclosure Widgets with details and summary",{"path":1670,"title":1671},"\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":1673,"title":1674},"\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":1676,"title":1677},"\u002Fcss-only-micro-interactions-animations\u002Fkeyframe-animation-patterns","Keyframe Animation Patterns: Spec-Compliant Architectures for Modern UIs",{"path":1679,"title":1680},"\u002Fcss-only-micro-interactions-animations\u002Fkeyframe-animation-patterns\u002Fstaggered-list-animations-with-custom-properties","Staggered List Animations Using --i Index Custom Properties",{"path":1682,"title":1683},"\u002Fcss-only-micro-interactions-animations\u002Fperformance-gpu-acceleration","Performance & GPU Acceleration in CSS: A Developer’s Blueprint",{"path":1685,"title":1686},"\u002Fcss-only-micro-interactions-animations\u002Fperformance-gpu-acceleration\u002Foptimizing-css-animations-for-60fps","Optimizing CSS Animations for 60fps: Budgeting the Frame",{"path":1688,"title":1689},"\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":1691,"title":1692},"\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":1694,"title":1695},"\u002Fcss-only-micro-interactions-animations\u002Fscroll-driven-animations","Scroll-Driven Animations: Binding Motion to Scroll Position in Pure CSS",{"path":1697,"title":1698},"\u002Fcss-only-micro-interactions-animations\u002Fscroll-driven-animations\u002Fscroll-driven-animation-fallbacks","Shipping Scroll-Driven Animations Without Breaking Anything",{"path":1700,"title":1701},"\u002Fcss-only-micro-interactions-animations\u002Fscroll-driven-animations\u002Fscroll-progress-bar-without-javascript","A Reading Progress Bar Driven by scroll(root)",{"path":1574,"title":5},{"path":1704,"title":1705},"\u002Fcss-only-micro-interactions-animations\u002Fview-transitions-for-css-developers","View Transitions for CSS Developers: the Pseudo-Element Tree, Snapshots, and Named Elements",{"path":1707,"title":1708},"\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":1710,"title":1711},"\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":1713,"title":1714},"\u002Fcss-only-micro-interactions-animations\u002Fview-transitions-for-css-developers\u002Fview-transitions-with-reduced-motion","Honouring prefers-reduced-motion in View Transitions",{"path":356,"title":1716},"Modern CSS Layouts & Micro-Interactions",{"path":1718,"title":1719},"\u002Fmastering-container-queries-responsive-layouts\u002Fanchor-positioning-and-overlays\u002Fanchor-positioned-tooltips","Anchor-Positioned Tooltips: Tethering, Flipping, and Getting the Semantics Right",{"path":1721,"title":1722},"\u002Fmastering-container-queries-responsive-layouts\u002Fanchor-positioning-and-overlays\u002Fanchor-positioning-fallbacks","Anchor Positioning Fallbacks: A Graceful Floor for Older Browser Versions",{"path":1724,"title":1725},"\u002Fmastering-container-queries-responsive-layouts\u002Fanchor-positioning-and-overlays","CSS Anchor Positioning and Overlays: Tethering Elements Without JavaScript",{"path":1727,"title":1728},"\u002Fmastering-container-queries-responsive-layouts\u002Fanchor-positioning-and-overlays\u002Fpopover-attribute-and-css-styling","The popover Attribute: Top-Layer Overlays With No JavaScript",{"path":1730,"title":1731},"\u002Fmastering-container-queries-responsive-layouts\u002Fcontainer-query-fallbacks\u002Ffeature-detection-with-supports","Feature Detection with @supports: Syntax, Semantics, and the Traps",{"path":1733,"title":1734},"\u002Fmastering-container-queries-responsive-layouts\u002Fcontainer-query-fallbacks\u002Fhandling-container-query-fallbacks-for-older-browsers","Handling Container Query Fallbacks for Older Browsers",{"path":1736,"title":1737},"\u002Fmastering-container-queries-responsive-layouts\u002Fcontainer-query-fallbacks","Container Query Fallbacks: Spec-Compliant CSS Strategies for Legacy Browsers",{"path":1739,"title":1740},"\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":1742,"title":1743},"\u002Fmastering-container-queries-responsive-layouts\u002Fcontainer-query-syntax-basics\u002Fcontainer-type-size-vs-inline-size","Choosing container-type: inline-size, size, or normal",{"path":1745,"title":1746},"\u002Fmastering-container-queries-responsive-layouts\u002Fcontainer-query-syntax-basics\u002Fcontainer-vs-media-queries-comparison","Container vs Media Queries: Choosing the Right Reference Box",{"path":1748,"title":1749},"\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":1751,"title":1752},"\u002Fmastering-container-queries-responsive-layouts\u002Fcontainer-query-syntax-basics","Container Query Syntax Basics",{"path":1754,"title":1755},"\u002Fmastering-container-queries-responsive-layouts\u002Fcontainer-query-syntax-basics\u002Fnesting-and-naming-container-queries","Nesting and Naming Container Queries: Targeting the Right Ancestor",{"path":1757,"title":1758},"\u002Fmastering-container-queries-responsive-layouts\u002Fcss-grid-and-subgrid-layouts\u002Fauto-fit-minmax-responsive-grids","Responsive Grids with repeat(auto-fit, minmax())",{"path":1760,"title":1761},"\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":1763,"title":1764},"\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":1766,"title":1767},"\u002Fmastering-container-queries-responsive-layouts\u002Fcss-grid-and-subgrid-layouts","CSS Grid and Subgrid Layouts for Responsive Interfaces",{"path":1769,"title":1770},"\u002Fmastering-container-queries-responsive-layouts\u002Fcss-grid-and-subgrid-layouts\u002Fsubgrid-vs-nested-grid","Subgrid vs an Independently Nested Grid",{"path":1772,"title":1773},"\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":1775,"title":1776},"\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":1778,"title":1779},"\u002Fmastering-container-queries-responsive-layouts\u002Ffluid-typography-with-clamp\u002Ffluid-type-accessibility-and-zoom","Fluid Type, Accessibility and Zoom: The vw-Only Trap",{"path":1781,"title":1782},"\u002Fmastering-container-queries-responsive-layouts\u002Ffluid-typography-with-clamp\u002Ffluid-typography-without-javascript","Fluid Typography Without JavaScript: A Precision CSS Reference",{"path":1784,"title":1785},"\u002Fmastering-container-queries-responsive-layouts\u002Ffluid-typography-with-clamp","Fluid Typography with clamp(): A Practical Guide for Modern CSS",{"path":1787,"title":1788},"\u002Fmastering-container-queries-responsive-layouts","Mastering Container Queries & Responsive Layouts",{"path":1790,"title":1791},"\u002Fmastering-container-queries-responsive-layouts\u002Fintrinsic-sizing-techniques\u002Faspect-ratio-for-responsive-media","Using aspect-ratio for Responsive Media and Preventing Layout Shift",{"path":1793,"title":1794},"\u002Fmastering-container-queries-responsive-layouts\u002Fintrinsic-sizing-techniques","Intrinsic Sizing Techniques: Modern CSS Layouts for Responsive UI",{"path":1796,"title":1797},"\u002Fmastering-container-queries-responsive-layouts\u002Fintrinsic-sizing-techniques\u002Fmin-max-fit-content-explained","min-content, max-content, and fit-content() Explained",{"path":1799,"title":1800},"\u002Fmastering-container-queries-responsive-layouts\u002Fintrinsic-sizing-techniques\u002Fpreventing-flex-and-grid-overflow","The min-width: auto Trap in Flex and Grid Layouts",{"path":1802,"title":1803},"\u002Fmastering-container-queries-responsive-layouts\u002Fmodern-css-reset-strategies\u002Fcascade-layers-for-reset-and-tokens","Cascade Layers for Reset and Design Tokens",{"path":1805,"title":1806},"\u002Fmastering-container-queries-responsive-layouts\u002Fmodern-css-reset-strategies\u002Fcascade-layers-vs-specificity-hacks","Cascade Layers vs Specificity Hacks: Ordering Instead of Escalating",{"path":1808,"title":1809},"\u002Fmastering-container-queries-responsive-layouts\u002Fmodern-css-reset-strategies","Modern CSS Reset Strategies: A Spec-Compliant Foundation",{"path":1811,"title":1812},"\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":1814,"title":1815},"\u002Fmastering-container-queries-responsive-layouts\u002Fresponsive-component-patterns\u002Fcontainer-query-data-tables","Container Query Data Tables: Reflowing Tables to Cards",{"path":1817,"title":1818},"\u002Fmastering-container-queries-responsive-layouts\u002Fresponsive-component-patterns\u002Fcontainer-query-sidebar-layouts","Container Query Sidebar Layouts That Travel Between Contexts",{"path":1820,"title":1821},"\u002Fmastering-container-queries-responsive-layouts\u002Fresponsive-component-patterns","Responsive Component Patterns: Architecture & Implementation",{"path":1823,"title":1824},"\u002Fmastering-container-queries-responsive-layouts\u002Fresponsive-component-patterns\u002Fresponsive-forms-with-container-queries","Responsive Forms with Container Queries: Reflow Without Losing the Wiring",{"path":1826,"title":1827},"\u002Fmastering-container-queries-responsive-layouts\u002Fresponsive-component-patterns\u002Fresponsive-navigation-without-media-queries","Responsive Navigation Without Media Queries Using Container Queries",{"path":1829,"title":1830},"\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":1832,"title":1833},"\u002Fmastering-container-queries-responsive-layouts\u002Fstyle-queries-and-container-state","Style Queries and Container State: Token-Driven Component Variants",{"path":1835,"title":1836},"\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":1838,"title":1839},"\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",1784566157252]