| name | motion-accessibility |
| description | This skill should be used when the user needs to make animations safe and compliant for users with motion sensitivities, or wants to audit existing animations for accessibility. Trigger when the user mentions "prefers-reduced-motion", "vestibular disorder", "motion sensitivity", "WCAG animation", "WCAG 2.3.1", "seizure threshold", "flashing content", "parallax accessibility", "dizzy from animation", "animation causes nausea", "motion a11y", "reduce motion", "animation accessibility audit", or asks how to make their animations WCAG compliant. Also trigger when the user is working on a medical, government, or high-compliance product and mentions any animation, or when accessibility is mentioned alongside motion design. |
Motion Accessibility Checker
Motion accessibility is not a nice-to-have. For users with vestibular disorders, epilepsy, ADHD, or certain neurological conditions, ignored motion preferences cause genuine physical harm: nausea, vertigo, headaches, and in extreme cases, seizures. This skill provides a systematic audit and remediation workflow.
The key insight: prefers-reduced-motion gets mentioned in every animation article and ignored in every production codebase. This skill treats it as a functional requirement, not a footnote.
Who Is Affected
| Condition | Problematic animation types |
|---|
| Vestibular disorders | Parallax, spatial translations, zoom transforms, camera-like movement |
| Epilepsy / photosensitivity | Rapid flashing, high-frequency strobing, fast blinking |
| ADHD / attention conditions | Background animations, infinite loops, peripheral motion |
| Motion sickness | Scroll-linked transforms, rotation, 3D perspective |
| Low vision | Fast animations that leave no time to read transitional states |
Affected users: approximately 35% of adults experience some form of motion sensitivity. Vestibular disorders affect 1 in 3 people over 40.
The Minimum Implementation
Every site with animations must implement this baseline, with no exceptions:
@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;
}
}
This is a safe catch-all. The !important ensures library-injected inline styles cannot override it.
In Framer Motion:
import { useReducedMotion } from "framer-motion";
function AnimatedComponent() {
const prefersReducedMotion = useReducedMotion();
return (
<motion.div
initial={prefersReducedMotion ? false : { opacity: 0, y: 16 }}
animate={{ opacity: 1, y: 0 }}
transition={prefersReducedMotion
? { duration: 0 } // Instant — no animation
: { duration: 0.28, ease: [0, 0, 0.58, 1] }
}
/>
);
}
Reduced motion ≠ no motion. Fading is safe. Instant state changes are safe. What to eliminate:
| Eliminate | Acceptable alternative |
|---|
| Large translateX/Y (spatial movement) | Opacity fade only |
| Scale pulsing (0.95 → 1.05 → ...) | Opacity pulse (0.6 → 1 → ...) |
| Parallax depth layers | Static layers, no movement |
| Spinning loaders | Non-spinning progress bar |
| Background decorative animations | Static background |
| Scroll-linked transforms | Remove motion, keep fade |
WCAG Compliance: The Three Rules
WCAG 2.3.1 — Three Flashes or Below Threshold
Rule: No content should flash more than 3 times per second.
Three flashes per second is the threshold at which strobing content can trigger photosensitive epilepsy in susceptible individuals. This applies to ANY on-screen content: animations, video, scrolling text, loading pulses.
In practice:
- Any animation with more than 3 cycles per second of visible light/dark contrast change must be redesigned
- Skeleton pulse at 1 cycle/1.5s = 0.67Hz — safe
- Rapidly looping icons at 200ms = 5Hz — fails
Test it: PEAT (Photosensitive Epilepsy Analysis Tool) or Harding Test online.
WCAG 2.2.2 — Pause, Stop, Hide
Rule: Any automatically-playing content that lasts more than 5 seconds must have a pause/stop mechanism.
function AutoplayAnimation() {
const [playing, setPlaying] = useState(true);
return (
<>
<motion.div animate={playing ? { rotate: 360 } : {}} />
<button
onClick={() => setPlaying(!playing)}
aria-label={playing ? "Pause animation" : "Resume animation"}
>
{playing ? "Pause" : "Play"}
</button>
</>
);
}
Infinite decorative loops (background hero animations, looping particles, ambient motion) all require this control.
WCAG 1.4.3 / 2.1 — Non-Motion Alternatives
Rule: Motion must never be the only way to communicate state or status.
<motion.button animate={{ scale: [1, 1.1, 1] }} />
<motion.button
animate={{ scale: [1, 1.1, 1] }}
aria-label="Submitted successfully"
>
<span aria-live="polite">{submitted ? "Done!" : "Submit"}</span>
</motion.button>
Loading spinners must have accompanying text or ARIA:
<div role="status" aria-label="Loading, please wait">
<Spinner aria-hidden="true" />
<span className="sr-only">Loading...</span>
</div>
Audit Workflow
Use this checklist on any animated page or component:
Identify animations
Check for WCAG 2.3.1 violations
Check prefers-reduced-motion
Check for looping animations
Check non-motion alternatives
Check focus management
Focus Management During Transitions
function Modal({ isOpen, onClose }) {
const firstFocusableRef = useRef(null);
const triggerRef = useRef(null);
useEffect(() => {
if (isOpen) {
setTimeout(() => firstFocusableRef.current?.focus(), 50);
} else {
triggerRef.current?.focus();
}
}, [isOpen]);
return (
<AnimatePresence>
{isOpen && (
<motion.dialog
aria-modal="true"
initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 8 }}
transition={{ duration: 0.2 }}
>
<button ref={firstFocusableRef} onClick={onClose}>Close</button>
{/* modal content */}
</motion.dialog>
)}
</AnimatePresence>
);
}
Trap focus inside open modals — keyboard users should not be able to tab outside the modal while it is open. Use a focus trap library (focus-trap-react) or implement manually with a keydown listener.
Additional Resources
references/wcag-motion-audit.md — WCAG 2.3.1 full text and examples, PEAT test tool walkthrough, jurisdiction-specific requirements (ADA, EN 301 549), legal case precedents, complete ARIA role and live region reference for animation-driven state changes