UI/UX Atlas
Accessibility Intermediate

Motion, Animation & Sensory Accessibility

Purposeful motion elevates interfaces — but for millions of users with vestibular disorders, epilepsy, or sensory sensitivities, uncontrolled animation is a health hazard, not a delight.

9 min read

The full lesson

Animation is now a core part of design. Spring transitions, scroll-driven effects, and rich micro-interactions are standard in modern products. But the same motion that communicates state and feels delightful can trigger migraines, nausea, seizures, and full vestibular episodes for a significant portion of your users.

WCAG 2.2 defines when motion becomes a conformance failure. The prefers-reduced-motion media query gives you a clean, CSS-native way to respect user preferences. The goal is not to remove animation — it is to design motion responsibly from the start.

Who Is Affected and Why It Matters

The affected population is larger than most teams expect:

  • Vestibular disorders affect roughly 35% of adults over 40 and up to 69 million Americans. Conditions like benign paroxysmal positional vertigo (BPPV), labyrinthitis, and Ménière’s disease cause the visual system and inner ear to send conflicting signals. Parallax scrolling, large translating elements, and looping background animations are the most common triggers.
  • Photosensitive epilepsy is less common (about 3 in 100,000 people) but carries severe consequences. Flashing content between 3–50 Hz in a large enough area of the visual field can trigger tonic-clonic seizures.
  • Attention and cognitive load — users with ADHD, anxiety disorders, or cognitive disabilities often cannot focus on what matters when decorative motion is playing nearby.
  • Situational factors — a user on a moving vehicle, someone recovering from a concussion, or a person who is simply tired may experience motion sensitivity they would not describe as a disability.

WCAG Criteria That Govern Motion

Knowing which criteria apply — and at what conformance level — helps you communicate obligations clearly.

CriterionLevelRequirement
2.2.2 Pause, Stop, HideAMoving, blinking, or scrolling content that starts automatically, lasts more than 5 seconds, and appears alongside other content must have a pause/stop/hide mechanism.
2.3.1 Three Flashes or Below ThresholdAContent must not flash more than three times per second, unless the flash is below the general flash and red flash thresholds.
2.3.2 Three FlashesAAANo flashing above three times per second, regardless of threshold.
2.3.3 Animation from InteractionsAAAMotion animation triggered by interaction can be disabled, unless the animation is essential to the function or the information.

Two important notes on 2.3.3: although it is AAA, it directly names animation triggered by interaction — scroll-linked effects, parallax, entrance animations. These are the motion patterns teams ship every day. And while AAA conformance is rarely required by contract, violating 2.3.3 is a common source of user complaints and ADA/EAA accessibility filings even when organizations only target AA.

The prefers-reduced-motion: reduce query is the recommended way to implement 2.3.3. It satisfies the spirit of the criterion at both the AA and AAA tiers.

The prefers-reduced-motion Media Query

The query has two states:

  • no-preference — the user has not enabled reduced motion. Default motion is fine.
  • reduce — the user has asked for reduced motion. Remove or substantially reduce all non-essential motion.

The modern best practice is to invert the default: start with no animation, then add motion only when the user has no preference. This is safer than the alternative (motion-first, then override) because reduced-motion users are never accidentally served an un-overridden transition.

/* Reduced-motion-first: the default has no animation */
.card {
  transition: none;
}

/* Add motion only when the user hasn't requested reduction */
@media (prefers-reduced-motion: no-preference) {
  .card {
    transition: transform 300ms cubic-bezier(0.34, 1.56, 0.64, 1),
                opacity 200ms ease-out;
  }
}

For JavaScript-driven animation libraries (GSAP, Framer Motion, Web Animations API), check the preference at runtime:

const prefersReduced = window.matchMedia(
  '(prefers-reduced-motion: reduce)'
).matches;

// Framer Motion: pass reduced variants conditionally
const motionConfig = prefersReduced
  ? { duration: 0, ease: 'linear' }
  : { duration: 0.4, type: 'spring', stiffness: 300 };

Framer Motion also provides a useReducedMotion() hook that reactively tracks the media query. That is the idiomatic React approach.

Motion Tokens: Designing the System, Not One-Offs

Hardcoding transition: 0.3s ease in individual components creates inconsistent motion. It also makes system-wide prefers-reduced-motion overrides fragile and forces you to search every file when you want to change a duration or easing. The modern approach is motion tokens — a small set of named duration, easing, and delay values defined once and used everywhere.

A minimal motion token set in W3C DTCG format:

{
  "motion": {
    "duration": {
      "instant":  { "$value": "0ms",    "$type": "duration" },
      "fast":     { "$value": "100ms",  "$type": "duration" },
      "moderate": { "$value": "250ms",  "$type": "duration" },
      "slow":     { "$value": "400ms",  "$type": "duration" }
    },
    "easing": {
      "standard":  { "$value": "cubic-bezier(0.4, 0, 0.2, 1)",     "$type": "cubicBezier" },
      "decelerate":{ "$value": "cubic-bezier(0, 0, 0.2, 1)",        "$type": "cubicBezier" },
      "spring":    { "$value": "cubic-bezier(0.34, 1.56, 0.64, 1)", "$type": "cubicBezier" }
    }
  }
}

When prefers-reduced-motion: reduce is active, a single override swaps all duration tokens to instant (0ms). One rule, entire system, zero exceptions missed.

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

Note: use 0.01ms rather than 0ms. Setting transition-duration: 0 triggers a known bug in some screen reader and browser combinations where transitionend events do not fire. That breaks JavaScript that listens for transition completion.

Flashing Content and Seizure Risk

WCAG 2.3.1 is the sharpest motion criterion because the failure mode is a medical emergency. The general flash threshold requires one of two conditions to be true:

  • The flash covers less than 25% of any 10-degree visual field (roughly a 341×256 px area at typical viewing distance), or
  • The contrast between the peak bright and dark states is less than 10%.

The red flash threshold is stricter. A pair of opposing transitions involving a saturated red is dangerous at a lower area threshold.

Practical guidance:

  • Hero videos and backgrounds — never use footage with rapid strobing, flickering lights, or fast-cut sequences without testing against the Photosensitive Epilepsy Analysis Tool (PEAT) or the Harding Test.
  • Skeleton loaders and loading pulses — a shimmer animation that pulses faster than 3 Hz can violate 2.3.1. Keep skeleton pulse animations at or below 1 Hz (one cycle per second).
  • Error states and attention captures — flashing a border or icon red to grab attention is a common pattern. It can fail the threshold if the element is large and the flash rate is above 3 Hz. Use a single flash or a slow fade instead.
  • Video content — YouTube, Vimeo, and user-generated video embeds cannot be pre-analyzed. Showing a warning before content known to contain strobing is a reasonable safeguard.

Scroll-Driven Animation and Parallax

Scroll-linked effects — elements that move, scale, fade, or rotate as you scroll — are a significant vestibular trigger. Parallax specifically creates a disconnect between vestibular input (your body is not moving) and visual input (things on screen are moving). The inner ear interprets this as motion sickness.

The View Transitions API (supported in Chrome, Safari 18+, with polyfill paths for Firefox) introduces a new category of motion: full-page or element-level morphing transitions during navigation. These are powerful, but they need the same prefers-reduced-motion guard:

/* View Transitions: disable the cross-fade morphing when motion is reduced */
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-image-pair(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}

For scroll-driven animations using the CSS animation-timeline: scroll() API, wrap the effect in a @media (prefers-reduced-motion: no-preference) block. That way, reduced-motion users see the static final state rather than a tracked parallax.

Do

  • Default to no animation and add motion progressively inside @media (prefers-reduced-motion: no-preference) blocks.
  • Animate only transform and opacity — these run on the compositor and never cause layout thrashing.
  • Use motion tokens so a single reduced-motion override disables transitions across the entire design system.
  • Test scroll-linked effects and parallax sections by enabling Reduce Motion in your OS system settings before shipping.
  • Replace looping decorative animations with a static image or a single-play animation when motion is reduced.
  • Keep skeleton shimmer pulse rates at or below 1 Hz to stay comfortably under the 3 Hz seizure threshold.
  • Guard View Transitions API and CSS scroll-driven animation-timeline rules with prefers-reduced-motion: no-preference.

Don't

  • Animate width, height, top, left, or margin — these trigger layout, cause jank, and can still produce vestibular symptoms even at reduced speeds.
  • Ship a looping background animation with no pause/stop mechanism — this fails WCAG 2.2.2 (Level A) if it runs alongside page content for more than 5 seconds.
  • Rely on translateZ(0) or will-change: transform hacks for GPU promotion — modern browsers handle compositing automatically and these hacks waste memory.
  • Assume prefers-reduced-motion: reduce means “slow down” — it means “remove non-essential motion entirely.” Easing a 400ms animation to 800ms is not an acceptable reduced-motion response.
  • Use strobing or rapid-flicker effects in hero videos, loaders, or attention captures without checking against the 3 Hz threshold and Harding Test.
  • Treat AAA criterion 2.3.3 (Animation from Interactions) as irrelevant because your contract targets AA — vestibular users will still file complaints and the harm is real.

Autoplay Video and the Pause Mechanism Requirement

WCAG 2.2.2 (Pause, Stop, Hide — Level A) is frequently violated by marketing sites that autoplay hero videos. The requirements are:

  1. Moving or scrolling content that starts automatically, lasts more than 5 seconds, and appears alongside other content must have a user-accessible pause, stop, or hide control.
  2. The pause mechanism must be keyboard-accessible. A hidden play/pause button that only mouse users can reach does not satisfy the criterion.
  3. If the auto-playing content is the only content on the page (for example, a splash screen), there is no concurrent content and no 2.2.2 obligation — though a pause control is still good practice.

For autoplay video backgrounds, a visible play/pause button with an accessible name (aria-label="Pause background video") is the minimum. Many teams also honor prefers-reduced-motion: reduce by defaulting the video to paused:

<video
  id="hero-video"
  autoplay
  muted
  loop
  playsinline
  aria-hidden="true"
  data-reducible="true"
>
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
  document.querySelectorAll('[data-reducible]').forEach(v => v.pause());
}

Sensory Modality Beyond Motion

Sensory accessibility covers more than motion.

Sound: WCAG 1.4.2 (Audio Control, AA) — if audio plays automatically for more than 3 seconds, users need a mechanism to pause, stop, or control the volume. Notification sounds in dashboards and chat applications should be controlled by user preference, not forced on every visitor.

Color and luminance: animated gradients and luminance-cycling backgrounds can trigger photosensitivity even when they do not technically flash above 3 Hz. High-luminance-contrast cycling — for example, an animated gradient from near-white to near-black — sits in a grey zone. Exercise caution.

Touch and haptics: on mobile, vibration feedback triggered by animation events (navigator.vibrate()) should respect reduced-motion intent. Vestibular users may have heightened sensitivity to unexpected physical stimulation. This is not codified in WCAG 2.2, but it is consistent with the intent of 2.3.3.

Auditing Motion Accessibility in Practice

A structured motion audit covers five categories:

  1. Autoplay inventory — list every auto-playing animation, video, or GIF. Verify each has a pause/stop mechanism and does not exceed the flash threshold.
  2. prefers-reduced-motion coverage — enable Reduce Motion OS-wide and walk through the product. Every visible animation still running is a gap. Document each one.
  3. Flash analysis — run hero videos and any animated SVGs or canvas elements through PEAT or submit to a Harding Test service. Flag anything above threshold.
  4. Scroll-linked effect review — identify parallax sections, sticky headers with animation, and scroll-triggered entrance effects. Each needs a static fallback.
  5. Motion token audit — if your design system uses motion tokens, confirm the reduced-motion override rule is in the global stylesheet and that no component hardcodes durations outside the token system.

Automated tools like axe-core can flag some 2.3.1 violations (known flash patterns in SVG/CSS), but most motion accessibility issues require manual testing. Lighthouse has no motion-specific checks. The most reliable audit method available today: walk through the product with Reduce Motion enabled, then review a screen recording at 1/4 speed.