From harness-claude
Implements animations that respect user motion preferences, avoid seizure triggers, and provide pause controls. Covers CSS media queries, JavaScript checks, and React hooks.
How this skill is triggered — by the user, by Claude, or both
Slash command
/harness-claude:a11y-motion-animationThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
> Implement animations that respect user motion preferences, avoid seizure triggers, and provide pause controls
Implement animations that respect user motion preferences, avoid seizure triggers, and provide pause controls
prefers-reduced-motion. Users who enable "Reduce motion" in their OS settings have vestibular disorders or motion sensitivity. Query this preference and eliminate or simplify animations./* Default: full animations */
.card {
transition:
transform 0.3s ease,
opacity 0.3s ease;
}
.card:hover {
transform: scale(1.05);
}
/* Reduced motion: remove or simplify */
@media (prefers-reduced-motion: reduce) {
.card {
transition: none;
}
.card:hover {
transform: none;
}
}
/* Approach A: motion-first (add animations, remove for reduced-motion) */
.element {
animation: slide-in 0.5s ease;
}
@media (prefers-reduced-motion: reduce) {
.element {
animation: none;
}
}
/* Approach B: reduce-first (no animations by default, add for no-preference) */
.element {
animation: none;
}
@media (prefers-reduced-motion: no-preference) {
.element {
animation: slide-in 0.5s ease;
}
}
Approach B is safer — users get no animation by default.
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
function animateElement(element: HTMLElement) {
if (prefersReducedMotion) {
// Instant state change, no animation
element.style.opacity = '1';
return;
}
element.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 300, easing: 'ease-in' });
}
function usePrefersReducedMotion(): boolean {
const [prefersReduced, setPrefersReduced] = useState(
() => window.matchMedia('(prefers-reduced-motion: reduce)').matches
);
useEffect(() => {
const mql = window.matchMedia('(prefers-reduced-motion: reduce)');
const handler = (e: MediaQueryListEvent) => setPrefersReduced(e.matches);
mql.addEventListener('change', handler);
return () => mql.removeEventListener('change', handler);
}, []);
return prefersReduced;
}
// Usage
function AnimatedCard() {
const reduceMotion = usePrefersReducedMotion();
return (
<motion.div
initial={{ opacity: 0, y: reduceMotion ? 0 : 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: reduceMotion ? 0 : 0.3 }}
/>
);
}
function AutoCarousel({ slides }: { slides: Slide[] }) {
const [isPaused, setIsPaused] = useState(false);
return (
<div>
<button
onClick={() => setIsPaused(!isPaused)}
aria-label={isPaused ? 'Play slideshow' : 'Pause slideshow'}
>
{isPaused ? <PlayIcon /> : <PauseIcon />}
</button>
<Carousel autoPlay={!isPaused} slides={slides} />
</div>
);
}
Avoid flashing content. Content that flashes more than 3 times per second can trigger seizures (WCAG 2.3.1 — Three Flashes). This is a hard constraint with no workaround.
Avoid large-scale motion. Parallax effects, zooming transitions, and full-page scroll animations are common vestibular triggers. When using these, always check prefers-reduced-motion and provide a static alternative.
Keep essential animations short. Transitions that communicate state changes (loading, success, error) should be under 500ms. Long, elaborate animations add cognitive load and frustrate users who interact frequently.
Use will-change and GPU-composited properties for performance. Janky animations that drop frames are worse than no animation. Stick to transform and opacity for smooth 60fps animations.
.animated {
will-change: transform, opacity;
transition:
transform 0.2s ease,
opacity 0.2s ease;
}
WCAG requirements:
What counts as "reduced motion":
Framer Motion integration:
<motion.div
layout
transition={{
layout: { duration: prefersReduced ? 0 : 0.3 },
}}
/>
Common mistakes:
prefers-reduced-motion once at load (user can change it at runtime — use addEventListener)https://www.w3.org/WAI/WCAG21/Understanding/animation-from-interactions
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeRespects prefers-reduced-motion in CSS/animations and prevents content that flashes more than 3 times per second to avoid triggering vestibular episodes or seizures.
Designs accessible motion and animations for users with vestibular disorders, motion sensitivity, or seizure conditions. Covers safe animation principles, prefers-reduced-motion, and avoiding dangerous patterns like flashing or parallax.
Defines motion tokens, spring presets, and accessibility-compliant animation defaults for React apps using motion/react. Provides SSR-safe initial states and reduced-motion support.