Product design skills

motion-and-zoom.md

A supporting file of the better-accessibility skill.

Motion and zoom

prefers-reduced-motion, zoom and reflow, and unit choices that respect user settings.

prefers-reduced-motion

Make motion opt-in. Wrap animations in @media (prefers-reduced-motion: no-preference) so users who asked for reduced motion get the static version by default, rather than you chasing every animation with an override.

/* Good: motion is opt-in */
.card {
  /* static styles */
}
@media (prefers-reduced-motion: no-preference) {
  .card {
    transition: transform 200ms ease-out;
  }
}
// Tailwind: motion-safe / motion-reduce variants
<div className="motion-safe:transition-transform motion-safe:hover:-translate-y-1" />

For an existing codebase where opt-in isn't feasible, the global kill switch is the fallback:

@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;
  }
}

0.01ms rather than none, so animationend and transitionend still fire and JS waiting on them doesn't hang.

What to disable vs reduce

Reduced motion means reduced, not eliminated. It targets vestibular triggers, not feedback.

Disable entirelyReplaceKeep
Parallax scrollingSlide/scale/zoom transitions → opacity crossfadeLoading spinners and progress
Autoplaying video, GIFs, looping decorationSmooth scrolling → instant jumpInstant state changes (hover color, focus ring)
Spinning, large-scale movement across the screenAuto-rotating carousels → start pausedBrief functional feedback (button press)

Animations are interruptible and driven by user input. Nothing autoplays or refuses to stop, and under reduced motion carousels start paused.

Autoplay and timed UI

Motion the user didn't ask for and UI acting on its own schedule:

  • No autoplaying media without visible controls (WCAG 2.2.2). Anything moving, blinking, or updating on its own for more than 5 seconds needs a visible pause or stop control, muted looping hero videos included.
  • Prefer explicit dismissal over timers. Auto-dismissal suits low-stakes confirmations and nothing else. A toast carrying an action, an error, or information the user may need stays until dismissed. Where one must time out, 5 seconds is the floor, and hovering or focusing it pauses the timer.
  • Never put critical information only in a timed element. A vanished toast with the only link to an undo action is data loss on a schedule.

Zoom and reflow

  • 200% zoom (WCAG 1.4.4). All content and functionality survives text scaled to 200%, and the viewport leaves the reader able to zoom.
  • Reflow at 320px (WCAG 1.4.10). At 400% zoom on a 1280px viewport, equivalent to a 320px one, the page must work with vertical scrolling alone. Genuinely 2D content is the exception: tables, maps and code blocks scroll inside their own container.

Fixed heights are what break under zoom. Use min-height on anything containing text and let containers grow.

rem vs px

Respect how the codebase is set up. Where the project sizes in px, or on an established Tailwind scale, stay consistent and never introduce mixed units into someone else's system. Where you do have the choice, in new code or a codebase already on rem, rem respects the user's base font size and px ignores it:

Use remUse px
font-sizeBorders and hairlines
max-width of text containersFocus outline width and offset
Media-query breakpoints (@media (min-width: 48rem))box-shadow details
Spacing that should scale with textFixed-size decorations

Breakpoints are where the choice matters most. At a larger base font size an em or rem query switches to the mobile layout when the text needs it, and a px query does not.

On this page