CSS animation-trigger Property: Scroll-Triggered Animations

Scroll-triggered animations without IntersectionObserver

Playing an animation once when an element scrolls into view is the classic scrollytelling pattern. It used to require IntersectionObserver plus the Web Animations API. The animation-trigger property does the same job in pure CSS.

Old way 11 lines
const io = new IntersectionObserver(   (entries) =>  {  entries.forEach(   (entry) =>  {    if (entry.isIntersecting)        {      entry.target.animate(      [        { opacity: 0,             translate: '0 32px' },        { opacity: 1,             translate: '0 0' }      ], { duration: 600,      easing: 'ease-out',           fill: 'both' });      io.unobserve(    entry.target);    }  });});document.querySelectorAll(    '.reveal').forEach(  (el) => io.observe(el));
Modern
5 lines
.reveal   {  animation: fade-up .6s ease-out both;  animation-trigger: --t play-forwards;   timeline-trigger-name: --t;   timeline-trigger-source: view(); }@keyframes fade-up   {  from   { opacity: 0; translate: 0 32px; }  to     { opacity: 1; translate: 0 0; }}
Limited availability 64% global usage

This feature is not Baseline because it does not work in some of the most widely-used browsers.

Not ready for production without a fallback.

146+
146+
scroll inside to trigger reveal animations on each card
scroll down
Reveal card 1
Reveal card 2
Reveal card 3
Reveal card 4
Reveal card 5
animation-trigger: --t play-forwards

No JavaScript observer

The browser tracks when the element crosses into the trigger range. No IntersectionObserver setup, no observer cleanup, no animate() calls.

Distinct from scroll-driven

Scroll-driven animations scrub progress to scroll position. Scroll-triggered animations play once with their own duration when a range is entered. Use the right tool for the job.

Two-way actions

play-forwards on activation, play-backwards on deactivation. Or pair with reset, pause, and step to build reveal-and-hide patterns.

Lines Saved
11 → 5
No JS required
Old Approach
IntersectionObserver
JS observer + animate()
Modern Approach
animation-trigger
CSS property

How it works

Reveal-on-scroll animations have been a JavaScript job for years. The standard recipe: spin up an IntersectionObserver, watch a set of elements, call element.animate() (or toggle a class) when one crosses the threshold, then unobserve to prevent re-triggering. It works but it lives outside CSS and runs whether or not the animation actually needs to play.

The animation-trigger property tells a regular CSS animation when to start. Set up a trigger with timeline-trigger-name and timeline-trigger-source: view(), then bind it to the animation with animation-trigger: --t play-forwards. When the element scrolls into its view range, the animation plays. The browser handles the tracking, the threshold, and the play state.

Accessibility note: Scroll-triggered animations still respond to prefers-reduced-motion when you wrap them in a media query, just like any other CSS animation. Disable or shorten the motion for users who request it.
ESC