WCAG Motion Success Criteria: What Each One Actually Requires
Accessibility reviews of animated interfaces tend to produce vague findings — "too much motion", "add reduced motion support" — because the reviewer and the CSS author are not working from the same list. There is a specific, short list. Four WCAG 2.2 success criteria govern motion and moving content, they sit at three different conformance levels, and only some of them can be satisfied in CSS at all. This page enumerates them precisely, states what each one requires, and names the corresponding obligation for the person writing the stylesheet. It belongs to Accessibility in CSS Animations, which covers the techniques; this page covers the rules those techniques exist to meet.
The four criteria, at a glance:
- 2.2.2 Pause, Stop, Hide — Level A — anything moving or auto-updating for more than five seconds
- 2.3.1 Three Flashes or Below Threshold — Level A — flashing content and seizure risk
- 1.4.13 Content on Hover or Focus — Level AA — tooltips, popovers, dropdowns
- 2.3.3 Animation from Interactions — Level AAA — motion triggered by user action
Why the level matters before the technique
If you are targeting Level AA — the near-universal contractual and regulatory baseline — then three of these four are binding on you: both Level A criteria are inherited by AA conformance, plus 1.4.13 itself. Only 2.3.3 is AAA, which means a strictly AA-conformant product can legally ship an interface that animates heavily on interaction with no way to turn it off. That is a bad product, and honouring prefers-reduced-motion remains the right default, but it is important not to tell a team that a AAA criterion is a failure against their AA target. Overstating requirements erodes trust in the ones that genuinely bind.
The second thing to establish early: CSS cannot satisfy 2.2.2 by itself in the general case. The criterion demands a mechanism the user can operate. A CSS-only mechanism is possible for a narrow class of components — a checkbox or a :hover region can toggle animation-play-state — but a mechanism that survives page navigation, applies to a video element, or persists as a preference needs script. The other three criteria are much closer to pure styling concerns. The diagram maps each criterion to its level and to what CSS is actually on the hook for.
2.2.2 Pause, Stop, Hide — Level A
The criterion covers two things. For moving, blinking or scrolling information that starts automatically, lasts more than five seconds, and is presented in parallel with other content, the user must have a way to pause, stop, or hide it. For auto-updating information that starts automatically and is presented in parallel with other content, the user must have a way to pause, stop, or hide it or to control its update frequency.
Three qualifiers do real work. "Starts automatically" excludes anything the user initiated. "More than five seconds" excludes a two-second entrance animation or a short spinner that resolves. "In parallel with other content" excludes a full-screen loading state where the animation is the only thing on the page — there is nothing for it to distract from. An exception also covers movement that is essential to the activity, such as a progress indicator whose whole purpose is to report ongoing work.
The CSS obligation. Anything with animation-iteration-count: infinite that shares the viewport with readable content is a candidate failure. Your obligation is to make sure a mechanism exists and that your stylesheet respects it, typically by exposing animation-play-state: paused through a state hook rather than hard-coding running. Write the animation so it can be paused; the control itself is a component-level responsibility, and for video, GIFs and script-driven counters it will not be CSS.
2.3.1 Three Flashes or Below Threshold — Level A
Web pages must not contain anything that flashes more than three times in any one second period, unless the flashing is below the general flash and red flash thresholds. This is a seizure-safety criterion, not a comfort one, and it is the only motion criterion where the failure mode is medical harm.
A flash is a pair of opposing changes in relative luminance, where the changes occupy more than 25 percent of a 10-degree field of view. Saturated red transitions are treated separately and more strictly. Note the Level AAA sibling, 2.3.2 Three Flashes, which removes the threshold escape hatch entirely: at AAA, nothing may flash more than three times per second regardless of area or luminance.
The CSS obligation. Audit any keyframe set that alternates high-contrast values — an attention-getting background-color blink, a strobing opacity, a rapid filter: invert(). The practical rule is to keep any full-cycle alternation at or above roughly 333ms per cycle, and to prefer small, low-contrast, or spatially limited effects over large full-bleed ones. A pulsing dot 12 pixels wide cannot cross the 25 percent field-of-view threshold; a page-wide background flash easily can. The material in Vestibular-Safe Animation Patterns covers the adjacent category of motion that triggers nausea rather than seizures — different mechanism, different guidance.
1.4.13 Content on Hover or Focus — Level AA
When additional content becomes visible on pointer hover or keyboard focus and then hidden again, three conditions must hold unless the content is a user-agent-rendered feature such as a native title tooltip:
- Dismissable — the user can dismiss the additional content without moving pointer hover or keyboard focus, and without the content itself disappearing on its own. The standard mechanism is the Escape key.
- Hoverable — if pointer hover can trigger the content, the pointer can be moved over the additional content without it disappearing.
- Persistent — the content stays visible until hover or focus is removed, the user dismisses it, or its information is no longer valid.
The CSS obligation. Hoverable and Persistent are almost entirely CSS problems, and both are commonly failed by the same mistake: positioning the panel with a visual gap between the trigger and the panel, so the pointer crosses dead space and the panel vanishes mid-journey. Keep the panel's box adjacent to or overlapping the trigger, use transparent padding to bridge any visual offset, and include the panel itself in the reveal condition. Dismissable requires a key handler, so it is the one condition CSS cannot own. The tooltip patterns in Accessible CSS-Only Tooltips show the markup side of this.
Hover the term below, then move the pointer down onto the panel — it stays put, which is the Hoverable condition demonstrated.
2.3.3 Animation from Interactions — Level AAA
Motion animation triggered by interaction can be disabled, unless the animation is essential to the functionality or the information being conveyed. "Motion animation" here means movement — translation, scaling, rotation, parallax — not a colour change, an opacity fade, or a change in an element's own appearance without displacement. A cross-fade between two views is explicitly not motion animation for this purpose.
The CSS obligation. Provide a path where movement-bearing effects resolve to no displacement. Honouring prefers-reduced-motion: reduce is the accepted technique, and the syntax and cascade behaviour are covered in Reducing Motion Preferences in CSS. Be careful with the word "disabled": the criterion is satisfied by a mechanism the user controls, and the operating-system preference counts as one. It does not require an in-page toggle, though for a heavily animated product one is worth building anyway.
A stylesheet shaped by all four
The following is one component — an auto-scrolling announcement strip with a hover panel — written so that each rule traces back to a criterion. It is CSS-only, and the comments mark exactly where the criteria stop being satisfiable in a stylesheet.
<div class="strip">
<input class="strip__toggle" type="checkbox" id="pause-strip">
<label class="strip__label" for="pause-strip">Pause announcements</label>
<div class="strip__viewport">
<p class="strip__track">Maintenance window Saturday 02:00 UTC — status page updated hourly</p>
</div>
<span class="alert" tabindex="0">Why am I seeing this?
<span class="alert__panel">This strip appears while a maintenance window is scheduled.</span>
</span>
</div>
/* --- 2.2.2 -----------------------------------------------------------
The marquee runs longer than five seconds beside readable content, so a
mechanism is mandatory. The checkbox IS that mechanism; the stylesheet's
job is to make the animation pausable rather than to hard-code it. */
.strip__viewport { overflow: hidden; }
.strip__track {
margin: 0;
white-space: nowrap;
animation: strip-scroll 22s linear infinite;
}
.strip__toggle:checked ~ .strip__viewport .strip__track {
animation-play-state: paused; /* pause, not remove: position is kept */
}
@keyframes strip-scroll {
from { translate: 100% 0; }
to { translate: -100% 0; }
}
/* --- 2.3.1 -----------------------------------------------------------
An attention pulse on the label. One full cycle takes 2s, so the pair of
opposing luminance changes occurs well under three times per second, and
the change is confined to a small element rather than a full-bleed area. */
.strip__label {
animation: strip-pulse 2s ease-in-out infinite;
}
@keyframes strip-pulse {
0%, 100% { background-color: #ffe9a8; }
50% { background-color: #fff6d6; }
}
/* --- 1.4.13 ----------------------------------------------------------
Hoverable: the panel is flush against the trigger (top: 100%, no gap), so
the pointer never crosses dead space. Persistent: the panel's own :hover
is part of the reveal condition, so it survives the pointer landing on it.
Dismissable still needs an Escape-key handler in script. */
.alert { position: relative; }
.alert__panel {
position: absolute;
top: 100%;
left: 0;
width: 16rem;
padding: 0.6rem;
background: #ffffff;
border: 1px solid #94a3b8;
visibility: hidden;
}
.alert:hover .alert__panel,
.alert:focus-visible .alert__panel,
.alert__panel:hover {
visibility: visible;
}
/* --- 2.3.3 -----------------------------------------------------------
The scroll is displacement, so it goes. The pulse is a colour change with
no displacement and is not motion animation under this criterion, but the
marquee stops moving and the text is shown statically instead. */
@media (prefers-reduced-motion: reduce) {
.strip__track {
animation: none;
white-space: normal; /* let the full text wrap and be read at rest */
}
}
The one rule doing the heavy lifting
animation-play-state: paused is the property that makes 2.2.2 tractable in CSS. Setting animation: none under a paused state would satisfy nothing useful: the element would snap back to its unanimated position and the user would lose their place in the content. paused freezes the animation at its current computed values, so the announcement stays exactly where it was when the user asked it to stop. That is the difference between a pause control and a delete control, and the criterion asks for the former. Everything else in the sheet is arithmetic — cycle durations kept above 333ms, gaps kept at zero — but this one property changes what is achievable without script.
Variation: pausing on hover as well
Some auto-advancing components pause when the pointer rests on them, which is a courtesy rather than a conformance mechanism — it does not satisfy 2.2.2 on its own, because a keyboard-only user cannot invoke it and the effect is not discoverable. Layered on top of a real control it is still worth having:
.strip:hover .strip__track,
.strip:focus-within .strip__track {
animation-play-state: paused;
}
Pairing :hover with :focus-within at least keeps keyboard users at parity while they are inside the component. Note that the pointer-capability caveats matter here too: on a touch device :hover behaviour is unreliable, which is why the visible checkbox remains the mechanism of record.
Browser support
Nothing in this page depends on a recent feature. animation-play-state has been supported in every engine for many years, and prefers-reduced-motion since Chrome 74, Edge 79, Firefox 63 and Safari 10.1. :focus-visible is the newest thing used, at Chrome 86, Edge 86, Firefox 85 and Safari 15.4. The conformance obligations themselves are independent of browser support: an engine that ignores your media query does not excuse a missing pause control.
FAQ
Which WCAG success criteria apply to CSS animation? Four apply directly: 2.2.2 Pause, Stop, Hide (Level A), 2.3.1 Three Flashes or Below Threshold (Level A), 1.4.13 Content on Hover or Focus (Level AA), and 2.3.3 Animation from Interactions (Level AAA). Only 2.3.3 is AAA, so the other three are in scope for most conformance targets.
Does the five-second rule in 2.2.2 apply to a hover transition? No. 2.2.2 covers movement that starts automatically, so a transition you trigger by hovering is outside its scope. It applies to carousels, tickers, looping background video and auto-refreshing counters that run for more than five seconds alongside other content.
Is prefers-reduced-motion enough to satisfy WCAG?
It satisfies the common failure technique for 2.3.3 at Level AAA, but it is not sufficient for 2.2.2. That criterion requires a mechanism the user can operate on the page itself, and a person who has not set an operating-system preference still needs a visible pause control.
What counts as a flash under 2.3.1? A pair of opposing changes in relative luminance covering more than 25 percent of a 10-degree field of view, or a transition to or from saturated red. Three or fewer such flashes per second passes; above that you must stay below the general and red flash thresholds.
Related
- Accessibility in CSS Animations — the parent guide these criteria sit behind.
- Target Size and Pointer Accessibility — the pointer-side criteria, 2.5.8 and 2.5.5.
- prefers-reduced-motion Recipes — component-level blocks for the 2.3.3 obligation.
- Creating Accessible Focus Indicators — the focus criteria that sit alongside these.
- Fluid Type Accessibility and Zoom — the reflow and text-resize criteria on the layout side.
Related articles
More pages in the same section.