The essentials
Quick reference
One focused task per row. Jump to the related section for complete, working examples.
| Use | Syntax | Examples |
|---|---|---|
| Attach after the shorthand | .subject {
animation: reveal linear both;
animation-timeline: view();
} | View examples |
| Fill a progress timeline | .subject { animation-duration: auto; } | View examples |
| Track document scrolling | .progress { animation-timeline: scroll(root block); } | View examples |
| Track the nearest scroller | .indicator {
animation-timeline: scroll(nearest block);
} | View examples |
| Track the element itself | .scroller { animation-timeline: scroll(self inline); } | View examples |
| Track visibility through a scrollport | .card { animation-timeline: view(); } | View examples |
| Set view axis and insets | .card { animation-timeline: view(block 15% 25%); } | View examples |
| Name a scroll timeline | .reader { scroll-timeline-name: --reading; } | View examples |
| Choose a named scroll axis | .reader { scroll-timeline-axis: block; } | View examples |
| Declare a named scroll timeline | .reader { scroll-timeline: --reading block; } | View examples |
| Consume a named timeline | .reader__progress { animation-timeline: --reading; } | View examples |
| Localize a repeated timeline name | .reader-widget { timeline-scope: --reading; } | View examples |
| Name a view timeline | .chapter { view-timeline-name: --chapter; } | View examples |
| Choose a named view axis | .chapter { view-timeline-axis: block; } | View examples |
| Adjust the visibility range | .chapter { view-timeline-inset: 10% 20%; } | View examples |
| Declare a named view timeline | .chapter { view-timeline: --chapter block 10% 20%; } | View examples |
| Animate across entry | .card { animation-range: entry; } | View examples |
| Choose range endpoints | .card { animation-range: entry 10% cover 35%; } | View examples |
| Set range endpoints separately | .card {
animation-range-start: entry 10%;
animation-range-end: cover 35%;
} | View examples |
| Guard the enhancement | @supports (animation-timeline: view()) {
.reveal { animation-timeline: view(); };
} | View examples |
| Honor reduced motion | @media (prefers-reduced-motion: reduce) {
.reveal { animation: none; };
} | View examples |
| Animate transform and opacity | @keyframes reveal {
from { opacity: 0; transform: translateY(2rem); };
} | View examples |
| Snap scrolling independently | .rail { scroll-snap-type: inline proximity; } | View examples |
| Start a View Transition | document.startViewTransition(update); | View examples |
Scroll-driven animations replace elapsed clock time with progress through a scroll range. A scroll progress timeline tracks a scroll container from its start to its maximum offset; a view progress timeline tracks a subject as it crosses a scrollport. Use them to reinforce reading position or spatial continuity, not to gate content or navigation. Keep the unenhanced state complete and visible, respect reduced-motion preferences, and test syntax support because anonymous functions, named timelines, ranges, and scoping can ship at different times.
Step by step
Detailed examples
Treat scrolling as finite animation progress
A progress-based timeline has a bounded duration represented as 0% through 100%, rather than milliseconds advancing on the document clock. Scrolling forward and backward moves the animation in the corresponding direction. For readable, portable CSS, declare ordinary animation behavior first and set animation-timeline explicitly afterward; current Level 2 grammar can include a timeline in the shorthand, but the longhand makes support and list matching easier to audit. animation-range is a reset-only subproperty, so set it after animation. An auto duration fills the available attachment range.
@keyframes fill-reading-progress {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
.reading-progress {
transform-origin: left;
animation: fill-reading-progress linear both;
animation-duration: auto;
animation-timeline: scroll(root block);
} Select the right anonymous scroll source
scroll() creates an anonymous scroll progress timeline. nearest selects the nearest ancestor scroll container in the requested axis, root selects the document viewport's scroller, and self selects the element itself. The default is nearest block. A source without a scrollable overflow range produces an inactive timeline, so verify both constrained size and overflow. Prefer logical block or inline axes unless the effect is intentionally tied to physical x or y directions.
.page-progress {
animation: grow-inline linear both;
animation-duration: auto;
animation-timeline: scroll(root block);
}
.carousel__progress {
animation: grow-inline linear both;
animation-duration: auto;
animation-timeline: scroll(nearest inline);
}
@keyframes grow-inline {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
} Track each subject with view()
view() creates a view progress timeline for the animated element as its principal box crosses the nearest ancestor scrollport in the chosen axis. A selector matching several cards creates an independent timeline for every card. One or two inset values move the visibility range inward from the start and end edges; negative values expand it. With no animation-range, the animation maps to the full cover range, which often lasts longer than a simple entrance effect needs.
@keyframes reveal-card {
from { opacity: 0; transform: translateY(2rem); }
to { opacity: 1; transform: translateY(0); }
}
.card {
animation: reveal-card linear both;
animation-duration: auto;
animation-timeline: view(block 12% 18%);
animation-range: entry 0% cover 35%;
} Reuse and scope named scroll timelines
Named timelines separate the source from one or more animated descendants. Define a dashed identifier on the scroll container with scroll-timeline, then reference it with animation-timeline. Name lookup follows tree scope rules and can produce surprising matches when repeated components reuse a name. timeline-scope localizes selected names to a component subtree; older scroll-timeline-attachment and view-timeline-attachment proposals have been removed from the current draft.
.reader-widget {
timeline-scope: --reading;
}
.reader-widget__scroller {
max-block-size: 24rem;
overflow-y: auto;
scroll-timeline: --reading block;
}
.reader-widget__progress {
animation: fill-reading-progress linear both;
animation-duration: auto;
animation-timeline: --reading;
} Name a subject's view timeline when another element consumes it
A named view timeline belongs to the element whose visibility supplies progress. The name, axis, and inset are available as longhands or the view-timeline shorthand. Attach a different in-scope element when an indicator, caption, or descendant should react to that subject. auto insets derive from the scroll container's scroll-padding; explicit positive insets shrink the visibility range and percentages resolve against the relevant scrollport dimension.
.chapter {
view-timeline: --chapter block 10% 20%;
}
.chapter__marker {
transform-origin: left;
animation: fill-reading-progress linear both;
animation-duration: auto;
animation-timeline: --chapter;
animation-range: entry 0% exit 100%;
} Map keyframes to meaningful visibility ranges
animation-range restricts an animation's attachment interval. View timelines expose named ranges including cover, contain, entry, exit, entry-crossing, and exit-crossing. A range name alone expands to its 0% and 100% endpoints; two endpoints can cross named ranges, such as entry 10% to cover 35%. These percentages are relative to the named range, not necessarily the complete timeline. Use the longhands when the cascade must override one endpoint independently.
.card {
animation: reveal-card linear both;
animation-duration: auto;
animation-timeline: view();
animation-range-start: entry 10%;
animation-range-end: cover 35%;
}
.badge {
animation: reveal-card linear both;
animation-duration: auto;
animation-timeline: view();
animation-range: entry;
} Build a visible fallback and reduce nonessential motion
Browser support can differ among scroll(), view(), named timeline properties, timeline-scope, insets, and named ranges. Start with the final readable state, then add initial keyframe values only inside an appropriate @supports block; a parsing result does not guarantee identical behavior in every engine, so test target browsers and content sizes. Under prefers-reduced-motion:reduce, remove parallax, large travel, rotation, and scrubbed decorative effects. Never make text, controls, completion, or focus depend on reaching an animation position.
.reveal { opacity: 1; transform: none; }
@supports (animation-timeline: view()) and (animation-range: entry) {
.reveal {
animation: reveal-card linear both;
animation-duration: auto;
animation-timeline: view();
animation-range: entry;
}
}
@media (prefers-reduced-motion: reduce) {
.reveal { animation: none; opacity: 1; transform: none; }
} Keep per-frame work small and diagnose inactive timelines
Scroll-driven timing can avoid JavaScript scroll handlers, but it does not make expensive effects free. Transform and opacity often offer the best opportunity for compositor execution; filters, large painted areas, shadows, masks, and layout-affecting properties can still require costly painting or layout. Do not scatter permanent will-change declarations across long pages. When an effect is frozen, inspect whether the chosen source actually overflows, whether the axis matches, whether a named timeline is in scope, and whether a later animation shorthand reset animation-range or replaced coordinated values.
@keyframes reveal-efficiently {
from {
opacity: 0;
transform: translateY(1.5rem) scale(.98);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
.article-card {
animation: reveal-efficiently linear both;
animation-duration: auto;
animation-timeline: view();
animation-range: entry 0% cover 30%;
} Local code tester
Scroll a reading meter and independent card timelines
Scroll inside the panel to see one named scroll timeline fill the meter while anonymous view timelines reveal each card.
Sources and further reading
References
Authoritative documentation used to verify and expand this cheat sheet.
- CSS Working GroupScroll-driven Animations Module Level 1 Editor's Draftdrafts.csswg.org
- CSS Working GroupCSS Animations Module Level 2 Editor's Draftdrafts.csswg.org
- World Wide Web ConsortiumWeb Animations Level 2w3.org
- World Wide Web ConsortiumMedia Queries Level 5w3.org
- World Wide Web ConsortiumCSS Scroll Snap Module Level 1w3.org
- World Wide Web ConsortiumCSS View Transitions Module Level 2w3.org
Help us improve
Found a typo or missing example?
Tell us what would make this cheat sheet clearer, more complete, or more useful.



