Motion accessibility and WCAG compliance for web animations. CRITICAL: This skill should be co-loaded with elite-gsap or elite-css-animations whenever implementing animations. Covers prefers-reduced-motion (CSS and GSAP patterns), WCAG contrast requirements, APCA, focus management, keyboard navigation, touch interaction patterns, and screen reader considerations. Use when asked about: reduced motion, motion sensitivity, vestibular disorders, accessibility, a11y, WCAG, contrast ratios, focus styles, touch targets, safe areas, gesture accessibility, or making animations accessible. Also covers touch interaction patterns for mobile.
Making motion-rich websites accessible to everyone.
| Topic | Reference File |
|---|---|
| Reduced motion | reduced-motion.md |
| WCAG contrast | wcag-contrast.md |
| Focus & keyboard | focus-keyboard.md |
| Inclusive aesthetics | inclusive-aesthetics.md |
| Touch interaction |
| touch-interaction.md |
Motion can cause real harm:
Accessibility isn't optional - it's a design requirement.
Every animation MUST have a prefers-reduced-motion alternative.
This is non-negotiable for elite web design.
/* Global reduced motion */
@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;
}
}
// ALWAYS use gsap.matchMedia() for motion preferences
const mm = gsap.matchMedia();
mm.add('(prefers-reduced-motion: no-preference)', () => {
// Full animations for users who want them
gsap.from('.hero-content', {
opacity: 0,
y: 50,
duration: 1,
stagger: 0.2
});
});
mm.add('(prefers-reduced-motion: reduce)', () => {
// Instant state for users who prefer reduced motion
gsap.set('.hero-content', { opacity: 1, y: 0 });
});
/* Only enable scroll animations if motion is OK */
@media (prefers-reduced-motion: no-preference) {
.scroll-animated {
animation: reveal linear;
animation-timeline: scroll();
}
}
/* Fallback: just show the content */
@media (prefers-reduced-motion: reduce) {
.scroll-animated {
opacity: 1;
transform: none;
}
}
Don't just disable animations - provide alternatives:
| Full Animation | Reduced Motion Alternative |
|---|---|
| Slide in from side | Instant appear or fade only |
| Parallax scrolling | Static positioning |
| Scroll-triggered reveals | Content visible by default |
| Bouncing/pulsing | Static or subtle opacity change |
| Page transitions | Instant navigation |
| Auto-playing carousels | Static slide, manual controls |
const mm = gsap.matchMedia();
// Full experience
mm.add('(prefers-reduced-motion: no-preference)', () => {
gsap.from('.card', {
opacity: 0,
y: 30,
duration: 0.6,
stagger: 0.1,
scrollTrigger: {
trigger: '.cards-section',
start: 'top 80%'
}
});
});
// Reduced motion: simple fade, no movement
mm.add('(prefers-reduced-motion: reduce)', () => {
gsap.from('.card', {
opacity: 0,
duration: 0.3, // Shorter
stagger: 0.05, // Faster stagger
scrollTrigger: {
trigger: '.cards-section',
start: 'top 80%'
}
});
});
System Preferences → Accessibility → Display → Reduce motion
Settings → Ease of Access → Display → Show animations in Windows (off)
Settings → Accessibility → Motion → Reduce Motion
| Content | AA (Minimum) | AAA (Enhanced) |
|---|---|---|
| Normal text | 4.5:1 | 7:1 |
| Large text (18px+) | 3:1 | 4.5:1 |
| UI components | 3:1 | 3:1 |
| Focus indicators | 3:1 | 3:1 |
Use browser extensions:
See wcag-contrast.md for detailed guidance.
Every interactive element needs visible focus:
/* Base focus style */
:focus-visible {
outline: 2px solid var(--color-accent);
outline-offset: 2px;
}
/* Remove default, add custom */