Meeting WCAG 2.3.3 Animation from Interactions

Part of WCAG Animation Conformance in Accessible Motion Architecture.

The problem: interaction motion with no off switch

Success Criterion 2.3.3 (Level AAA) states that “motion animation triggered by interaction can be disabled, unless the animation is essential to the functionality or the information being conveyed.” In plain terms: if a user action — hover, focus, click, scroll — sets off movement, and that movement is not essential, the user must be able to turn it off. Most sites fail this not through malice but through omission: the scroll-reveal, the hover flip, the page-transition slide all fire unconditionally, with no path to a still version. This page shows how to close that gap cheaply.

Root cause: unconditional motion and a missing gate

The failure is structural. Interaction motion is typically authored as a plain rule — .card:hover { transform: rotateY(180deg); } — with no surrounding condition, so it runs for everyone with no way out. Meanwhile the operating system already exposes the user’s answer: the “reduce motion” accessibility setting, surfaced to CSS as prefers-reduced-motion: reduce. The criterion is satisfied the moment that expressed preference actually disables the non-essential motion, because WCAG accepts honouring the OS setting as a valid mechanism. The work, then, is twofold: decide which motion is non-essential (almost all of it), and route that motion through the gate so the OS switch controls it. The gate itself is built on the prefers-reduced-motion architecture; this page is about applying it correctly to the 2.3.3 case.

Decision steps: is this motion essential?

Work through these in order for each interaction animation. The bar for “essential” is deliberately high — WCAG reserves it for cases where the motion carries irreplaceable meaning.

  1. Does the motion itself convey information? If the animation is the message — a progress spinner that is the only indication work is happening, or a diagram that animates to teach a physical process — it may be essential. If it merely decorates a state change that is also shown another way, it is non-essential.
  2. Would an instant state change lose meaning? Swap the animation for its end state applied immediately. If the interface still communicates the same thing, the motion is non-essential and must be disableable.
  3. Is the motion required to operate the control? A drag interaction’s follow-the-cursor movement can be essential to the operation; a hover flourish never is.
  4. Default to non-essential. If steps 1–3 are ambiguous, treat the motion as non-essential and gate it. Erring toward disableable is always conformant; erring the other way is a failure.

The overwhelming majority of interaction motion — reveals, hovers, parallax, page transitions — resolves to non-essential at step 2, because the underlying state change (content present, card selected, page navigated) is always available without the movement.

Production code: gating non-essential interaction motion

The pattern authors the reduced experience as the fallback and enables motion only under no-preference, so a user with the OS setting on receives an instant, fully functional interface with no movement.

/* Non-essential interaction motion is OPT-IN: only served to users who accept it. */
@media (prefers-reduced-motion: no-preference) {
  /* Hover flip — decorative emphasis on a card */
  .card {
    transition: transform 300ms cubic-bezier(0.22, 1, 0.36, 1);
    transform-style: preserve-3d;
  }
  .card:hover,
  .card:focus-within {
    transform: rotateY(180deg);
  }

  /* Scroll reveal — motion carries no information the static state lacks */
  .reveal { opacity: 0; transform: translateY(12px); }
  .reveal.is-visible {
    transition: opacity 300ms ease, transform 300ms ease;
    opacity: 1;
    transform: translateY(0);
  }
}

/* Reduced-motion: the OS switch disables the motion; FUNCTION is preserved.
   The card still flips state via a class, the reveal content is simply present. */
@media (prefers-reduced-motion: reduce) {
  .card,
  .card:hover,
  .card:focus-within {
    transition: none;
    transform: none;                 /* no rotation; use a data-flipped class if needed */
  }
  .reveal,
  .reveal.is-visible {
    transition: none;
    opacity: 1;                      /* content shown immediately — nothing hidden */
    transform: none;
  }
}

Rendering Impact: transform + opacity — composite only; the reduced-motion branch removes all interaction motion while leaving the state change intact, satisfying 2.3.3 without hiding any content.

The load-bearing detail is that the reduced branch never leaves content hidden. A common bug is gating only the animation while the reveal’s opacity: 0 base state survives, so reduced-motion users see nothing at all. Always reset the base state — opacity: 1, transform: none — inside the reduce block. For motion that must instead be driven from JavaScript, detecting prefers-reduced-motion in JavaScript covers reading the same signal so the two paths stay consistent.

Verification checklist

Constraints and trade-offs

  • 2.3.3 is Level AAA, so it is not required for AA conformance — but because a reduced-motion gate is usually already present, meeting it is nearly free and worth doing.
  • Honouring the OS preference is a valid mechanism, but only if the gated state preserves function; disabling motion in a way that hides content converts a AAA nicety into a Level A failure.
  • The “essential” exemption is narrow; using it to keep a decorative animation running is a conformance risk and should be challenged in review.
  • This criterion covers interaction-triggered motion specifically; autoplay is governed by 2.2.2 Pause, Stop, Hide and flashing by 2.3.1, so a full audit checks all three.
  • A single global reduced-motion reset can over-suppress essential motion; prefer per-component gating so the rare essential animation can be exempted deliberately.