Cover art for Scroll Driven Animation Is an Accessibility Regression by Default

Published: November 2nd

Scroll Driven Animation Is an Accessibility Regression by Default

The feature is fine. Its defaults are not.

Scroll driven animation = a CSS animation whose timeline is the scroll position instead of the clock.

It used to need a library and a scroll listener. Now it is a few lines of CSS. animation-timeline: scroll() ties the motion to the scrollbar. view() ties it to an element crossing the screen. Chrome shipped it in version 115, Safari in 26 and Firefox in 159, so all three engines have it.

I like the feature. I do not like its defaults, and the defaults are what most teams will ship.

The rule that would stop the bad version of this sits at Level AAA. Almost nobody has agreed to meet it.

The W3C is blunt about the risk. If scrolling moves things beyond the scroll itself, it can set off vestibular disorders, and the reactions it lists are dizziness, nausea, migraine headaches and needing to lie down. One review puts the lifetime rate of vestibular vertigo in adults at 7.4%. Your scroll effect is not for everyone.

Five Defaults To Change Before You Ship

Three designers laughing around a monitor in a bright studio, one of them leaning back from the screen with a hand raised.
The reaction that never shows up in an analytics dashboard.

1. There Is No Reduced Motion Guard

What teams ship: the animation, bare.

Why it fails: CSS gives you nothing here. If the reader asked their operating system for less motion, your scroll animation still runs.

Ship instead: wrap it in @media not (prefers-reduced-motion). The W3C lists that query as a sufficient technique, and WebKit's own guide uses it.

2. The Range Covers The Whole Screen

What teams ship: view() with no range set.

Why it fails: the default starts when the element first appears and ends only when its last pixel leaves. The thing moves the entire time it can be seen.

Ship instead: set animation-range: 0% 50%. It arrives, then holds still and lets itself be read.

3. Parallax By Reflex

What teams ship: a background moving at a different speed to the foreground.

Why it fails: the W3C names parallax as the common case of motion that is not essential. Wide movement across the field of vision reads as real movement in space, and that is the trigger.

Ship instead: if the page still works without it, it is decoration, and decoration gets a switch.

4. Big Motion Judged Like Small Motion

What teams ship: one blanket decision for every animation on the page.

Why it fails: a slow progress bar in a corner is not the same risk as a full width image flying in. WebKit draws that line itself, naming zoom, parallax and focal blur across a wide area as the risky ones.

Ship instead: judge each animation by how much of the screen it crosses and how fast.

5. The Silent Failure

What teams ship: animation-timeline written above the animation shorthand.

Why it fails: the shorthand resets it. Your animation quietly falls back to the clock and plays on load, which is the one thing you did not want.

Ship instead: declare animation-timeline after animation, every time.

The Default And The Fix

The defaultWhat to write
Motion runs for everyone@media not (prefers-reduced-motion)
Animates across the whole viewportanimation-range: 0% 50%
Parallax because it looked niceCut it, or give it an off switch
One rule for every animationJudge by size and speed
Shorthand resets the timelinePut animation-timeline last

The Four Question Audit

Run this before the page goes out.

  1. Turn on reduce motion in your operating system, then reload. Does anything still move?
  2. Does any element keep animating for its whole time on screen?
  3. Would the page still make sense with every scroll animation switched off?
  4. Is animation-timeline declared after the animation shorthand?

If the answer to question three is yes, the motion is decoration. That is fine. Decoration just has to be optional.

Get the PDF