WCAG Animation Conformance

Part of Accessible Motion Architecture.

WCAG animation conformance is the practice of mapping each animation you ship to the specific Web Content Accessibility Guidelines success criteria it can violate, and proving you have addressed each one. Three criteria carry almost all the weight for CSS motion: 2.2.2 Pause, Stop, Hide (Level A) for anything that plays automatically, 2.3.1 Three Flashes or Below Threshold (Level A) for flashing, and 2.3.3 Animation from Interactions (Level AAA) for motion set off by user interaction. Underpinning all three is prefers-reduced-motion, which is not itself a criterion but the mechanism most sites use to deliver the reduced experience the criteria require. This guide takes each criterion in turn, states exactly what it demands, and gives copy-ready CSS that satisfies it.


Concept: criteria define the “what”, the media query delivers the “how”

The most common conformance mistake is treating prefers-reduced-motion as if honouring it were the whole obligation. It is not. Each success criterion defines a condition that must be true of your interface; the media query is one delivery route. Some criteria — notably the flash limit — must hold for every user and cannot be gated behind the query at all. Keeping the two ideas separate is the key to conformance: first ask which criterion applies, then ask how do I deliver the remedy, and only sometimes is the answer to the second question “the reduced-motion query”.


Execution model: where each criterion bites in the pipeline

These criteria are about observable behaviour, not the rendering thread, but knowing where each concern originates helps you locate and fix it. 2.2.2 is about time and control: a looping or long autoplay animation keeps re-composing frames indefinitely, and the criterion demands a user-facing control that stops that timeline — a state change your JavaScript or a :hover/checkbox toggle drives, independent of the compositor. 2.3.1 is about luminance over time: a rapidly pulsing opacity or alternating background-color produces large brightness swings; the paint phase re-rasterises the colour each cycle, and the hazard is the rate, which you cap in the animation’s timing, not its thread. 2.3.3 is about provenance: motion that a user action triggered, which the criterion says must be defeatable unless it is essential — a cascade decision resolved at style-resolution time by the prefers-reduced-motion query.

The practical upshot is that conformance is enforced at authoring time in your CSS and markup, and verified by observing behaviour, not by reading a Performance trace. A compositor-perfect animation can still fail all three criteria, exactly as a vestibular-unsafe animation can be perfectly composited.

WCAG success criteria mapped to animation concern and remedy A three-column matrix. Rows: 2.2.2 Pause Stop Hide concerns autoplay motion over five seconds with no stop control, remedied by a pause control plus reduced-motion gate. 2.3.1 Three Flashes concerns flashing more than three per second, remedied by capping the flash rate. 2.3.3 Animation from Interactions concerns non-essential interaction-triggered motion, remedied by a mechanism to disable it. The mechanism row shows prefers-reduced-motion delivering the remedies by gating non-essential motion. Success criterion Animation concern Remedy 2.2.2 Pause/Stop/Hide Autoplay motion > 5 s, no stop control Pause control + gate off 2.3.1 Three Flashes Flashing more than 3 per second Cap flash rate (always) 2.3.3 Animation (AAA) Non-essential interaction motion Mechanism to disable it Mechanism prefers-reduced-motion delivers remedies Gate non-essential motion

Success criteria reference

The table restates each criterion precisely, with its conformance level and the CSS surface it touches.

Criterion Level What it requires CSS / markup surface
2.2.2 Pause, Stop, Hide A For moving content that starts automatically, lasts > 5 s, and is presented alongside other content, provide a way to pause, stop, or hide it Autoplay carousels, marquees, looping @keyframes
2.3.1 Three Flashes or Below Threshold A Nothing flashes more than three times in any one-second period Pulsing opacity, alternating background-color, flicker
2.3.3 Animation from Interactions AAA Motion triggered by interaction can be disabled unless essential to function Hover/scroll/transition motion gated by prefers-reduced-motion
prefers-reduced-motion (mechanism) n/a Not a criterion; the media feature used to deliver reduced experiences @media (prefers-reduced-motion: reduce)

2.2.2 Pause, Stop, Hide — control over autoplay

The criterion targets motion that begins without the user asking and continues past five seconds while other content is present — the classic auto-advancing carousel or looping background animation. Conformance requires a mechanism to pause, stop, or hide it. The cleanest CSS-first approach pairs a visible control with a reduced-motion gate so the animation never even starts for users who have opted out.

Intent: an auto-looping banner that stops on a user toggle and does not autoplay at all under reduced motion.

/* Full-motion: the banner loops only while the page is in the "playing" state */
@media (prefers-reduced-motion: no-preference) {
  .banner[data-state="playing"] .banner__track {
    animation: banner-scroll 20s linear infinite;
  }
  @keyframes banner-scroll {
    from { transform: translateX(0); }
    to   { transform: translateX(-50%); }
  }
}

/* A pause toggle sets data-state="paused"; the animation halts immediately */
.banner[data-state="paused"] .banner__track {
  animation-play-state: paused;
}

/* Reduced-motion: never autoplay; the track is static and the control is unnecessary */
@media (prefers-reduced-motion: reduce) {
  .banner .banner__track {
    animation: none;
    transform: none;
  }
}

Rendering Impact: transform: translateX — composite only; animation-play-state: paused stops the compositor timeline without a repaint, and the reduced-motion branch never allocates the animation at all.

Providing animation-play-state: paused behind a real, keyboard-operable control is what satisfies 2.2.2; the reduced-motion branch is a stronger courtesy on top. Note that a control is required for autoplay over five seconds even for users who have expressed no motion preference.


2.3.1 Three Flashes — the hard safety limit

This criterion is unconditional. Content must not flash more than three times per second, because faster flashing can trigger photosensitive seizures. Unlike the vestibular concerns, there is no version of this you may serve to some users: it applies to everyone, always, and cannot be gated behind prefers-reduced-motion.

Intent: a “recording” indicator that pulses slowly, staying far below the three-flash ceiling.

/* Full-motion: a slow pulse — one cycle every 1.6 s, well under 3 flashes/second */
@media (prefers-reduced-motion: no-preference) {
  .rec-dot {
    animation: rec-pulse 1600ms ease-in-out infinite;
  }
  @keyframes rec-pulse {
    0%, 100% { opacity: 0.45; }
    50%      { opacity: 1; }
  }
}

/* Reduced-motion: hold a steady, legible state — no pulsing at all */
@media (prefers-reduced-motion: reduce) {
  .rec-dot {
    animation: none;
    opacity: 1;
  }
}

Rendering Impact: opacity — composite only; the 1.6 s cycle keeps the flash rate an order of magnitude under the 2.3.1 threshold for all users.

The detailed flash-rate and threshold guidance expands on how to keep pulsing, flickering, and rapid colour changes safe. The rule for CSS is simple: no opacity or colour animation should complete more than three cycles per second, and a comfortable design stays well under that.


2.3.3 Animation from Interactions — a switch for interaction motion

At Level AAA, this criterion says that motion set off by user interaction — hover reveals, scroll-driven parallax, page-transition effects — must be disableable unless the motion is essential to the content or operation. Because most teams already ship a prefers-reduced-motion gate, they are most of the way to conformance; the dedicated guide to meeting 2.3.3 works through the essential-versus-non-essential decision and the exact gating.

Intent: a scroll-reveal that runs for users who accept motion and is fully disabled — content shown immediately — for those who do not.

/* Full-motion: content reveals with a small, brief rise on scroll into view */
@media (prefers-reduced-motion: no-preference) {
  .reveal { opacity: 0; transform: translateY(12px); }
  .reveal.is-visible {
    animation: reveal-in 320ms cubic-bezier(0.22, 1, 0.36, 1) forwards;
  }
  @keyframes reveal-in {
    to { opacity: 1; transform: translateY(0); }
  }
}

/* Reduced-motion: the interaction motion is disabled; content is simply present */
@media (prefers-reduced-motion: reduce) {
  .reveal,
  .reveal.is-visible {
    opacity: 1;
    transform: none;
    animation: none;
  }
}

Rendering Impact: opacity + small transform — composite only; the reduced-motion branch removes the interaction motion entirely, satisfying 2.3.3 for the non-essential reveal.


DevTools workflow: auditing conformance

Chrome DevTools — Rendering tab

  1. Set Emulate CSS media feature prefers-reduced-motion to reduce. Walk the whole interface: every non-essential interaction animation (2.3.3) and every autoplay (2.2.2) should be gone or static.
  2. Toggle back to no-preference and confirm the richer experience still functions.

Chrome DevTools — Animations panel

  1. Capture animation groups and read each one’s iteration count and duration. Any infinite animation that starts on load is a 2.2.2 candidate — confirm it has a pause control or is under five seconds.
  2. For opacity and background-color animations, compute cycles per second from the duration. Anything at or above three cycles per second is a 2.3.1 failure to fix immediately.

Manual keyboard pass

  1. Tab to every pause/stop control you added for 2.2.2 and confirm it is reachable and operable without a mouse — an inaccessible control does not satisfy the criterion.

Failure modes and fixes

Problem: An auto-advancing hero carousel has no pause button and loops indefinitely. Root cause: 2.2.2 requires a pause, stop, or hide mechanism for autoplay over five seconds; there is none. Fix: Add a keyboard-operable pause toggle driving animation-play-state: paused, and gate the autoplay behind prefers-reduced-motion: no-preference so it never starts for opted-out users.


Problem: A notification badge flashes rapidly to draw attention. Root cause: The pulse exceeds three flashes per second, breaching 2.3.1 — a seizure risk for every user. Fix: Slow the cycle to roughly one per second or less, and reduce the luminance swing. This limit is unconditional and cannot be gated behind the reduced-motion query.


Problem: Hover cards perform a large flip animation with no way to turn it off. Root cause: Non-essential interaction motion with no disable mechanism fails 2.3.3. Fix: Gate the flip behind prefers-reduced-motion: no-preference and provide the static state under reduce, so the honoured OS preference disables it.


Problem: The team assumed a global reduced-motion stylesheet made them conformant, but the flashing badge still failed audit. Root cause: 2.3.1 applies regardless of the query; honouring prefers-reduced-motion does not exempt content that flashes for users who never set the preference. Fix: Fix the flash rate at source for everyone, then keep the reduced-motion gate for the vestibular concerns it legitimately covers.


Accessibility and reduced-motion notes

The mental model to carry away: prefers-reduced-motion is the how, the success criteria are the what. For 2.2.2 the mechanism can be a pause control (required regardless of preference) plus the query as a courtesy. For 2.3.3 the query usually is the mechanism. For 2.3.1 the query is irrelevant — the flash limit is absolute. Building on top of a robust prefers-reduced-motion architecture gives you the delivery route for two of the three criteria almost for free, which is why that architecture and this conformance mapping are best implemented together.


Frequently asked questions

Which WCAG success criteria apply specifically to CSS animation?

Three are central. 2.2.2 Pause, Stop, Hide (Level A) governs automatically starting motion that lasts more than five seconds. 2.3.1 Three Flashes or Below Threshold (Level A) limits flashing to three times per second. 2.3.3 Animation from Interactions (Level AAA) requires a way to disable non-essential motion triggered by user interaction. prefers-reduced-motion is the usual mechanism that satisfies the reduced experience these criteria call for.

Does honouring prefers-reduced-motion make me WCAG conformant on its own?

Not by itself. The media query is a delivery mechanism, not a criterion. You still have to satisfy each success criterion: provide a pause control for long autoplay under 2.2.2, keep flashing under the 2.3.1 threshold for everyone regardless of the query, and offer a disable mechanism for interaction motion under 2.3.3. prefers-reduced-motion is how you commonly deliver those reductions, but the criteria define what must be true.

Is the three-flash limit waived for users who have not set a motion preference?

No. 2.3.1 is a Level A safety criterion that applies to every user unconditionally, because photosensitive seizures are a physical hazard that a person may not know to guard against. Unlike vestibular preferences, the flash limit cannot be gated behind prefers-reduced-motion; it must always hold.

2.3.3 is Level AAA — is it worth implementing?

Yes, and it is inexpensive. Because most sites already implement a prefers-reduced-motion gate for vestibular safety, they are most of the way to satisfying 2.3.3 for free. Providing a way to disable non-essential interaction motion is low-effort, materially helps vestibular users, and is a reasonable target even for teams aiming at AA overall.