The essentials

Quick reference

One focused task per row. Jump to the related section for complete, working examples.

UseSyntaxExamples
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 Transitiondocument.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

01

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.

Attach keyframes without losing the timeline
@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);
}
Back to quick reference ↑
02

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.

Drive page and carousel indicators from different sources
.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); }
}
Back to quick reference ↑
03

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.

Reveal every card on its own timeline
@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%;
}
Back to quick reference ↑
04

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.

Keep repeated reader widgets independent
.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;
}
Back to quick reference ↑
05

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.

Drive a chapter marker from its section
.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%;
}
Back to quick reference ↑
06

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.

Finish an entrance shortly after the subject becomes visible
.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;
}
Back to quick reference ↑
07

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.

Enhance a readable card without hiding the fallback
.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; }
}
Back to quick reference ↑
08

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.

Animate visual properties without changing document geometry
@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%;
}
Back to quick reference ↑

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.

Runs in your browser
Preview

Sources and further reading

References

Authoritative documentation used to verify and expand this cheat sheet.

  1. CSS Working GroupScroll-driven Animations Module Level 1 Editor's Draftdrafts.csswg.org
  2. CSS Working GroupCSS Animations Module Level 2 Editor's Draftdrafts.csswg.org
  3. World Wide Web ConsortiumWeb Animations Level 2w3.org
  4. World Wide Web ConsortiumMedia Queries Level 5w3.org
  5. World Wide Web ConsortiumCSS Scroll Snap Module Level 1w3.org
  6. 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.

Share feedback