Interface Lab

Motion as opt-in

The same five components are implemented using opt-out and opt-in patterns; animations that remain under reduced motion are counted.
006 · MotionConditionally supportedStarted 2026-10-03 · updated 2026-10-03
Demo width

Loading demo…

Principles

Motion should be opt-in

Define animation only inside prefers-reduced-motion: no-preference, so newly added components are static by default.

  • StandardW3C (2024)Interaction-triggered motion must be switchable off; opt-in motion provides that behavior by default.

Hypothesis

If the same five components use (A) motion in base styles with reduce overrides for four components but an accidentally omitted fifth override, versus (B) motion only in no-preference blocks, then under reduced motion A should still animate its fifth component but B should animate none; without reduced motion both should animate the same set of components.

Measured by
For each variant, open the accordion, toggle the switch, start loading, and show the toast; the skeleton shimmer is continuously active. Sample running document.getAnimations() over 50 frames, attributing animations to components using data-comp. Record peak simultaneous animations and unattributed animations as instrument checks.
Falsified if
Falsified if B has one or more running animations under reduced motion, A has none, or the non-reduced-motion animated component sets differ between A and B.

Implementation

A · A · Opt-out
Motion is defined in base styles; reduce overrides are added component by component, with one intentionally omitted.
B · B · Opt-in
Base styles are static; all transitions and animations live inside no-preference blocks.

Five components: accordion (450 ms grid-template-rows transition), switch (250 ms transform transition), loading spinner (900 ms infinite rotation while loading), toast (350 ms opacity/transform transition), and shimmering skeleton (1.6 s infinite animation). The fifth acts as a newly added component.

The forgotten override is simulated, not observed in real codebases: in A the four original components have correct reduce overrides but the fifth does not; in B all motion is written in no-preference blocks. The experiment compares the resulting implementations, not actual developer error rates.

The instrument activates all four interactive components, then samples document.getAnimations() every frame for 50 frames (about 750 ms at 60 Hz, covering the longest 450 ms transition). It counts animations only when playState is running, attributing each one via data-comp on its target.

After each variant all components are reset and the instrument waits 600 ms so reverse transitions do not leak into the next measurement.

The first version made the switch track an inline span, so its width and height had no effect and it was invisible. A screenshot revealed the issue; display:block fixed it. Measurements were identical before and after.

Observations

Equivalence control without reduced motion

Use Measure: trigger the four interactive components of each variant, then sample document.getAnimations() for 50 frames. Four runs at 1280 px and one at 375 px.

Chromium 153 (Playwright, headless-shell), 2026-10-03, 1280×900 and 375×812

VariantAnimated componentsPeak running animationsUnattributed
A · Opt-out5 of 560
B · Opt-in5 of 560

Without reduced motion, both variants matched: all five components (accordion, switch, loading, toast, new component) moved in every run and at both widths. Six Animation objects were counted rather than five because the toast uses separate opacity and transform transitions. No animations lacked attribution, so data-comp mapping passed its check.

Reduced motion: opt-out leaks, opt-in does not

Repeat the control under emulated prefers-reduced-motion: reduce. Five runs at 1280 px and one at 375 px.

Chromium 153 (Playwright, headless-shell), 2026-10-03, 1280×900 and 375×812, reduced motion

VariantAnimated componentsPeak running animationsUnattributed
A · Opt-out1 of 5 (new component)10
B · Opt-in0 of 500

Under reduced motion, A ran exactly one animation: the skeleton shimmer with its deliberately omitted override. The other four components remained static. B ran no animations; opening the accordion, toggling, loading, and showing the toast produced no Animation objects.

Cross-browser replication: equivalence control

Run the same instrument via Playwright, opening the demo, clicking Measure, and reading the table. Three runs at 1280 px and one at 375 px per engine; results were identical across runs.

Firefox 155 and WebKit 26.6 (Playwright, headless), 60 Hz, 2026-10-03, 1280×900 and 375×812

VariantAnimated componentsPeak running animationsUnattributed
A · Opt-out5 of 560
B · Opt-in5 of 560

Without reduced motion both variants behaved identically in Firefox and WebKit, matching Chromium: five components moved and six simultaneous Animation objects were counted (two from the toast), with no unattributed animations. The equivalence control passed.

Cross-browser replication: reduced motion

Repeat under emulated prefers-reduced-motion: reduce and verify matchMedia at every run; one run per engine at 1280 px.

Firefox 155 and WebKit 26.6 (Playwright, headless), 60 Hz, 2026-10-03, 1280×900, reduced motion

VariantAnimated componentsPeak running animationsUnattributed
A · Opt-out1 of 5 (new component)10
B · Opt-in0 of 500

Under reduced motion, A leaked exactly one skeleton-shimmer animation and B leaked none in both engines, exactly reproducing Chromium, including a verified reduced-motion emulation in WebKit.

Findings

Conditionally supported

With reduced motion emulated and all components triggered, the opt-out variant ran exactly one animation (a skeleton shimmer with an intentionally omitted override), while opt-in ran none at every repetition and tested width; without reduced motion both behaved identically. This holds only when new components follow the opt-in convention; the pattern prevents accidental omissions, not deliberate violations.

Limitations

  • The missing-override case was simulated rather than measured in real codebases; its actual prevalence is unknown.
  • Five small CSS-only components were tested. JavaScript animation, canvas, and video were not.
  • Chromium 153 headless-shell, Firefox 155, and WebKit 26.6 headless; real Safari/Firefox and physical devices were not tested.
  • The instrument counts all running Animation objects, including deliberate safer substitutes such as opacity crossfades. WebKit guidance (2017) recommends removing known vestibular triggers rather than all animation; this principle defaults to no motion without prohibiting explicitly chosen alternatives.
  • Effects on users were not measured; the instrument counted animations rather than symptoms or comprehension.

Evaluation

Usability
Five components can be tried without instruction. One Measure button evaluates both variants and writes a one-sentence summary. Toasts auto-dismiss or close with a button; loading finishes automatically.
Accessibility
Accordion uses aria-expanded and a region; switch uses role=switch with Space-key support; loading uses aria-busy and status; toast uses role=status; skeleton shimmer is aria-hidden with a visible label. Focus was visibly outlined. axe-core 4.11 found zero violations under both motion settings for both variants (WCAG 2.2 AA + best practices).
Responsive
scrollWidth equaled viewport width at 320, 375, 768, and 1440 px; cards stacked into one column at 375 px. Measurements matched at 375 and 1280 px.
Performance
About 153 KB of transferred JavaScript. Sampling 50 frames only reads DOM state; animations use only the properties under test.
Craft
The fifth component is labeled 'added later' to make the forgotten-override scenario clear. A reduced-motion status row remains visible; summaries change according to motion settings (leak versus control).

Area: Motion