WCAG 2.3.3: Animation from Interactions
WCAG success criterion 2.3.3 Animation from Interactions is the rule that governs motion a user sets off by scrolling, clicking, hovering, or dragging. It is Level AAA, which means it sits above the legal floor most policies cite — but it is one of the cheapest AAA criteria to meet and one of the highest-impact for people with vestibular disorders, so many teams adopt it anyway. This is a developer's reference: what the criterion actually requires, what counts as "motion animation from interaction," the narrow "essential" exception, how to satisfy it in code, and how it differs from the criterion it is most often confused with, 2.2.2.
What WCAG 2.3.3 requires
In plain terms: when a user interaction triggers motion animation, the user must be able to turn that motion off — unless the motion is essential. The official Understanding 2.3.3 document describes the recognized way to offer that switch, and it is the one built into the platform: honor the operating-system "reduce motion" setting, exposed to the web as the prefers-reduced-motion media feature. You do not have to build a custom on-page toggle; respecting the preference the person has already set satisfies the criterion.
The intent is specific and physical. Scroll-driven parallax, a hero that zooms as you scroll, cards that fly in from the edges, a background that drifts on mouse-move — these can cause dizziness, nausea, and migraines for people with vestibular conditions. 2.3.3 says: if the person has signalled that movement makes them unwell, do not force it on them just because they scrolled.
What counts as "motion animation from interaction"
Two conditions have to both be true for 2.3.3 to apply. The animation is motion — movement across the screen: translation, scaling, rotation, parallax, a spin. And it is triggered by a user interaction rather than playing on its own. Typical in-scope examples:
- Scroll-triggered reveals that slide, zoom, or fly elements in as they enter the viewport.
- Parallax layers that move at different speeds while you scroll.
- A hover that scales or rotates a card, or a drag that spins an element.
- A page transition that slides the whole view when you click a link.
What is generally out of scope: a plain color change, and — in most readings — a simple opacity fade with no movement, since neither creates the illusion of motion that triggers a vestibular response. Motion that plays automatically without any interaction is also not 2.3.3's job; that belongs to 2.2.2 (below). When a fade is borderline or you are unsure, the low-cost move is to guard it anyway.
The "essential" exception
2.3.3 carves out motion that is essential — where the movement is the information and removing it would defeat the purpose. The exception is deliberately narrow. A genuine example is an animation that demonstrates a physical process the content is explaining, where a static frame would lose the meaning. It does not cover decorative reveals, parallax, or "it looks nicer moving" — those are exactly what the criterion targets. If you can convey the same thing with a cross-fade or an instant state change, the motion was not essential, and it must yield to the preference.
How to satisfy 2.3.3 in code
Build interaction motion as an opt-in: default to no movement, and add it only when the person has not asked for less. In CSS that is one media query:
/* Default state: present, no motion. */
.reveal { opacity: 1; transform: none; }
/* Motion added only when it is welcome. */
@media (prefers-reduced-motion: no-preference) {
.reveal { animation: slide-in 0.5s ease-out both; }
}
Motion driven by JavaScript — a scroll library, a GSAP timeline, an Intersection Observer that animates on entry — has to check the preference itself, because it never reads your CSS media query. Gate it with matchMedia and react to live changes:
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)');
function initReveal() {
if (reduce.matches) return; // preference respected: no interaction motion
// …attach the scroll/hover animation here…
}
initReveal();
reduce.addEventListener('change', initReveal);
That is the whole mechanism. The deeper patterns — the blanket opt-out for retrofits, per-library gotchas, and the four ways to confirm the guard actually fires — are in prefers-reduced-motion, explained (and how to test it).
2.3.3 vs 2.2.2 — don't confuse the two motion criteria
WCAG has two success criteria about moving content, and they cover different situations. Mixing them up is the most common mistake in a motion audit:
- 2.2.2 Pause, Stop, Hide (Level A) — for motion that starts automatically, runs longer than five seconds, and plays alongside other content (carousels, tickers, animated backgrounds). The fix is a control to pause, stop, or hide it.
- 2.3.3 Animation from Interactions (Level AAA) — for motion triggered by the user's own action (scroll, hover, click). The fix is to let it be disabled, via
prefers-reduced-motion.
The quick test: did the motion start because the user did something? If yes, it is 2.3.3 territory. If it started on its own and just keeps going, it is 2.2.2. A page can easily trip both — an auto-playing carousel (2.2.2) on a site whose sections also fly in on scroll (2.3.3) — so check for each independently. For the full walkthrough across every motion criterion, see Accessible animation: a WCAG guide.
How to test WCAG 2.3.3
Testing 2.3.3 is concrete: turn the preference on and confirm the interaction motion stops.
- Toggle the OS setting — macOS: System Settings → Accessibility → Display → Reduce motion; Windows: Settings → Accessibility → Visual effects → Animation effects off. Then scroll, hover, and click through the page. Anything non-essential that still moves is a failure.
- Emulate it in DevTools — in Chrome or Edge, open the Command Menu (
Cmd/Ctrl + Shift + P) and run "Emulate CSS prefers-reduced-motion". Fast for iterating, but pair it with the real OS toggle before you ship. - Scan the CSS — paste the URL into the free motion accessibility check. It statically reads the page's linked CSS and
<style>blocks against WCAG 2.2.2 and 2.3.3, resolves whether each animation is actually inside aprefers-reduced-motionguard, and returns every finding with the fix — nothing stored. It is honest about its limits: runtime JS/GSAP motion is reported as not audited rather than passed, so the OS-toggle test remains the ground truth for scripted animation.
Should you adopt a AAA criterion?
WCAG marks 2.3.3 as Level AAA, and the W3C notes that AAA conformance is not required as a blanket policy for whole sites. That framing scares some teams off — but 2.3.3 is a special case: the cost is a single media query, and the benefit falls on a group for whom the failure mode is physical illness, not inconvenience. Treating it as a baseline you meet everywhere is a small, defensible decision. The durable way to hold that baseline is to stop relying on every developer and every generated snippet to remember the guard, and make it structural instead — the layer MotionSpec builds: animation assembled from a reviewed catalog of primitives that each ship a prefers-reduced-motion fallback by construction, so interaction motion that ignores the preference cannot appear in the first place. For how unguarded motion gets into a codebase, see What is motion slop? and How to detect and fix motion slop.
Paste any URL into the free motion accessibility check and see which animations honor prefers-reduced-motion — every finding with the fix.
Run the free motion check