Accessibility & performance
For some users, your animation is a medical event. Respect that.
Two non-negotiable requirements: respect motion preferences and keep 60fps.
prefers-reduced-motion
A more targeted approach:
Reduced motion users don't want zero feedback. They want animation that doesn't move across the screen.
In JavaScript
Or use Motion's useReducedMotion hook:
Transform and opacity only
transform and opacity run entirely in the composite stage. Everything else triggers layout or paint.
Animate transform and opacity. Nothing else.
| Property | Triggers | Performance |
|---|---|---|
transform (translate, scale, rotate) | Composite | Smooth |
opacity | Composite | Smooth |
filter (blur, brightness) | Paint | Test on mobile |
box-shadow | Paint | Can stutter with large spreads |
background-color | Paint | Avoid during drag |
width, height, top, left | Layout + Paint | Never animate |
border-radius | Paint | Never animate |
clip-path (inset, circle) | Composite | Smooth with will-change |
will-change: use sparingly
will-change: transform on 200 list items creates 200 compositor layers.
- Apply before animation starts (on
mouseenter, modal about to open). - Remove after animation completes (on
transitionend). - Never apply in a static stylesheet to elements that "might" animate.
Off-screen pausing
For CSS animations, toggle animation-play-state: paused when not intersecting.
Layout animation gotchas
Motion's layout prop on fifty elements means fifty position recalculations per frame.
- Use
layouton individual hero elements, not every list item. - Use
layoutIdfor shared-element transitions between two specific elements. - For list reordering, use
layout="position"(skips size animation). - Set
layoutScrollon scrolling containers. Without it, scroll position is ignored.
The performance test
Test on a two-year-old Android phone. Enable paint flashing in DevTools. Below 50fps? Simplify.