Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.encoreos.io/llms.txt

Use this file to discover all available pages before exploring further.

Any new JS-driven animation MUST respect the user’s prefers-reduced-motion: reduce setting. CSS animations and transitions are already neutralized globally by the @media (prefers-reduced-motion: reduce) rule in src/index.css, but JavaScript-driven motion bypasses that rule and must opt in explicitly.

When this applies

  • Chart/graph libraries with internal requestAnimationFrame interpolation (Recharts, D3, Chart.js, etc.)
  • element.scrollIntoView / scrollTo / scrollBy calls with behavior: 'smooth'
  • Carousels and sliders (Embla, Swiper, keen-slider)
  • Web Animations API (element.animate(...)) and manual requestAnimationFrame loops
  • Spring/physics libraries (Framer Motion, react-spring) — gate animation props on the hook
  • Auto-scrolling tickers, marquees, parallax, and confetti effects

How to comply

Use the existing helpers — do not re-implement the matchMedia check inline.
NeedUse
Read the preference in a componentusePrefersReducedMotion() from @/platform/a11y
Read the preference outside ReactprefersReducedMotion() from @/shared/lib/a11y/scroll
scrollIntoView / scrollTo / scrollBysafeScrollIntoView / safeScrollTo / safeScrollBy from @/shared/lib/a11y/scroll
Recharts seriesAlready patched globally by installRechartsReducedMotion() in src/main.tsx — no per-chart change needed
Embla carouselPass duration: reducedMotion ? 0 : <normal> to useEmblaCarousel
Framer Motion / react-springConditionally set transition={{ duration: 0 }} (Framer) or immediate (react-spring) when reduced motion is on
Web Animations APISet duration: 0 (or skip .animate() entirely) when reduced motion is on

Rules

  1. Never hardcode a non-zero animation duration without checking the preference first when the motion is JS-driven.
  2. Never write behavior: 'smooth' directly — use the safeScroll* helpers. The only allowed exception is when the call site has already early-returned for reduced-motion users (see ScrollableTabsList.tsx for the pattern).
  3. The reduced-motion path must be functionally equivalent — instant, but the same end state. Do not skip the action, only its animation.
  4. Both branches must be reactive: if you read usePrefersReducedMotion(), the component will re-render when the OS toggle changes — make sure your animation re-initializes (e.g., Embla reInit, Framer key change) so the new duration takes effect without a reload.
  5. Tests for new motion code should cover both branches (matchMedia mock returns matches: true and matches: false).

Reference

  • Helpers: src/shared/lib/a11y/scroll.ts, src/platform/a11y/usePrefersReducedMotion.ts
  • Recharts patch: src/shared/lib/a11y/recharts-motion.ts (installed in src/main.tsx)
  • Global CSS rule: src/index.css (@media (prefers-reduced-motion: reduce))
  • WCAG: 2.3.3 Animation from Interactions (AAA)