CSS & layout

Scroll-triggered animations land in CSS: `timeline-trigger` is not `animation-timeline`

Scroll-triggered animations land in CSS: `timeline-trigger` is not `animation-timeline`

You have a card that should fade its background in the moment it enters the viewport, and the animation should play for 300ms regardless of how fast the user scrolls. Today that is an IntersectionObserver, a class toggle, and a hand-wired @keyframes. Chrome 146 just made that pattern a pure CSS one. The CSS-Tricks walkthrough names the new piece: timeline-trigger, a property that watches a scroll zone and fires a normal duration-based animation when the threshold is met.

The important thing to internalise first: this is not the same machinery as scroll-driven animations, even though the property names rhyme.

Two timelines, two jobs

Scroll-driven animations (the animation-timeline: scroll() / animation-timeline: view() family) bind animation progress directly to scroll progress. There is no duration. As you scroll, the animation seeks. Scrub back, the animation rewinds.

Scroll-triggered animations are the other half of the problem. The animation has a normal animation-duration. The scroll position only decides when to start it. As CSS-Tricks puts it in the lede, the closest existing parallel is IntersectionObserver, except now you stay in the stylesheet. timeline-trigger: view() watches for a threshold. Once crossed, the animation plays for its fixed duration. The scroll does not steer it.

That is the line. animation-timeline: view() measures how much. timeline-trigger: view() waits for a threshold.

The three properties that do the work

The pattern the source shows uses three properties together: timeline-trigger, animation-trigger, and animation-fill-mode. The timeline-trigger names the zone. The animation-trigger says what to do when the zone is entered. The fill mode keeps the end state pinned.

@keyframes fade-bg-in {
  to {
    background: currentColor;
  }
}

.square {
  animation: fade-bg-in 300ms forwards;
  timeline-trigger: --trigger view() entry 100% exit 0%;
  animation-trigger: --trigger play-forwards;
}

Read it slowly. timeline-trigger takes a dashed-ident (--trigger) you can refer back to, a timeline (view()), and a range (entry 100% exit 0%) describing the scroll zone in which the animation is allowed to be active. animation-trigger then references that same dashed-ident and tells the engine which action to take. Here, play-forwards. The animation shorthand still owns the duration and the keyframes; the trigger pair only controls the firing.

The action vocabulary

The set of <animation-action> keywords the source enumerates: play-forwards, play-backwards, play-once, play, pause, reset, replay. play-once plays forward or backward, whichever direction is reached first. play resumes in the last specified direction. The rest do what they say on the tin.

This is the bit that makes scroll-triggered animations a different shape of tool from scroll-driven ones. You are not painting progress as a function of position. You are wiring discrete actions to discrete crossings.

One rough edge worth knowing

The CSS-Tricks piece flags a specific gotcha: when animation-trigger is in play-backwards mode, the animations don't stagger correctly, due to how delay is handled during the reversal. The author calls it out as something that looks like an oversight. Useful to know before you build a reverse-on-scroll-out effect and wonder why your stagger collapsed.

On your next layout

The mental model worth carrying away: scroll-driven and scroll-triggered animations are siblings, not twins. If you want a parallax-style scrub where every pixel of scroll moves the animation, you want animation-timeline. If you want "play this 300ms entrance once the element is mostly in view, then leave it alone", you want timeline-trigger.

This ships today in Chrome 146 and only Chrome 146, per the source. That makes it @supports (timeline-trigger: view()) territory in production — feature-detect, give non-supporting engines a no-animation or prefers-reduced-motion-style fallback, and treat the CSS path as the progressive enhancement on top of whatever you already had. The interesting thing is not that you can do this in CSS now. It is that the platform has finally split the "when" from the "how much". Those are two questions that have been tangled up in every scroll effect we have ever shipped.

Source: CSS-Tricks (css-tricks.com)

Related
CSS & layout

Grid Lanes gets a Field Guide — and a fourth layout primitive to argue about

WebKit has published gridlanes.webkit.org, a Field Guide for the new display: grid-lanes layout primitive, with a playground, a cheat sheet and six demos pitting Grid Lanes against Flexbox, Multicolumn and Grid.

June 22, 2026
Browser releases

Safari Technology Preview 246 widens color, images and attr()

WebKit has shipped Safari Technology Preview 246 with a batch of CSS additions — light-dark() taking image values, image(<color>), alpha(), color-mix() with more than two colors, attr() on pseudo-elements, and preview support for font-variant-emoji and word-break: auto-phrase.

June 23, 2026
Browser releases

Safari 27 beta opens up the form control, scroll, and the cascade

WebKit has detailed the Safari 27 beta: 58 new web-platform features and 525 fixes, including customizable <select>, scroll anchoring, the :heading pseudo-class, and the revert-rule and stretch CSS keywords.

June 19, 2026