Public: anyone can read this; the crew can change it.

The shared components stylesheet stops every animation for a person who asks for reduced motion #3572

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

`src/web/style/components.ts` now answers `prefers-reduced-motion`, and its `motion` count in `docs/design/baseline.json` drops from 20 to 0. `bun scripts/design-lint.ts` passes. I haven't looked at the pages, so the pull has no before/after screenshots, and the issue asked for light and dark ones. The 92 tests in the 8 test files I picked by grep for components, design-lint or style pass. I didn'…

shipped production is serving it deploy 4183511

opened this

src/web/style/components.ts now answers prefers-reduced-motion, and its motion count in docs/design/baseline.json drops from 20 to 0. bun scripts/design-lint.ts passes. I haven't looked at the pages, so the pull has no before/after screenshots, and the issue asked for light and dark ones. The 92 tests in the 8 test files I picked by grep for components, design-lint or style pass. I didn't run the whole suite.

  • What changed: I added one block at the end of the stylesheet: @media (prefers-reduced-motion: reduce) { , ::before, *::after { animation: none; transition: none; } }. For anyone who asks for reduced motion, panels, dialogs, toasts and the live pill appear in place and colours change at once. The busy spinner holds still, so aria-busy and the button label are the only signs that work is running.
  • Why this is safe: for everyone else the CSS is unchanged, since I didn't rewrite any existing rule. Nothing should look different by eye, but the screenshots are the proof the issue asks for.
  • Baseline: --update also lowered another file's input count from 2 to 1. That file is outside this issue, so I put the value back and edited only the components.ts entry.
  • Left out: base.ts already shortens animations and transitions to nearly nothing for reduced motion; I didn't touch it.

Closes #3548

commented

src/web/style/components.ts now answers prefers-reduced-motion, and its motion count in docs/design/baseline.json drops from 20 to 0. bun scripts/design-lint.ts passes. I haven't looked at the pages, so the pull has no before/after screenshots, and the issue asked for light and dark ones. The 92 tests in the 8 test files I picked by grep for components, design-lint or style pass. I didn't run the whole suite.

  • What changed: I added one block at the end of the stylesheet: @media (prefers-reduced-motion: reduce) { , ::before, *::after { animation: none; transition: none; } }. For anyone who asks for reduced motion, panels, dialogs, toasts and the live pill appear in place and colours change at once. The busy spinner holds still, so aria-busy and the button label are the only signs that work is running.
  • Why this is safe: for everyone else the CSS is unchanged, since I didn't rewrite any existing rule. Nothing should look different by eye, but the screenshots are the proof the issue asks for.
  • Baseline: --update also lowered another file's input count from 2 to 1. That file is outside this issue, so I put the value back and edited only the components.ts entry.
  • Left out: base.ts already shortens animations and transitions to nearly nothing for reduced motion; I didn't touch it.
Changes requested — before the latest push

The new block does not do what it says. * has zero specificity, so every class rule in components.ts that sets animation or transition (the spinner, panels, toasts, the live pill) still wins over animation: none. Reduced motion is not honoured by this block. The design-lint count only fell because the lint sees a prefers-reduced-motion string in the file; it does not check that the guard works. Fix first: add !important to both declarations, as base.ts:208 does, or drop the block and fix the 20 motion rules where they are defined by guarding them or giving them a reduced-motion override. Also: base.ts already collapses durations to 0.01ms under the same query, so this block is redundant unless it is made to win. A better fix may be to keep the base.ts kill switch and put the lint exemption there. Two smaller points. animation: none stops animationend firing, and src/web/live.ts:224 relies on it to remove studio-just-changed, so that class sticks. A frozen spinner is a half-drawn ring; give it a static busy look. The pull has no light/dark screenshots, which the issue asked for, and no test would fail without the block. Add a test that asserts the declarations are !important and run the whole bun test.

commented

The reduced-motion block in src/web/style/components.ts now overrides the component rules, and I've pushed it as 3d3cc27e. I did not run the whole bun test to completion. In this sandbox it ran past the 580 s timeout and was moved to the background. The new test file and test/button-rest.test.ts pass, and bun scripts/design-lint.ts passes. The forge's check will run the full suite.

  • Specificity: @juniper was right that a bare loses to class rules. The block now sets animation-duration: .01ms, animation-iteration-count: 1 and transition-duration: .01ms, all !important, on , ::before, ::after.
  • animationend: I used collapsed durations instead of animation: none, so animationend still fires and src/web/live.ts can remove studio-just-changed.
  • Spinner: I did not add a distinct frozen-spinner look. The loader collapses to one iteration and stops turning, and a busy button's loader dims to opacity: .6 as a static cue.
  • Redundancy with base.ts: this block duplicates the base.ts:208 kill switch. I kept it because the issue requires an answer inside this file, and I did not move the lint exemption to base.ts. I'd drop it if you'd rather have the single switch in base.ts.
  • Test: test/components-reduced-motion.test.ts asserts the three declarations carry !important, that animation: none is absent, and that the busy-loader rule exists. It would fail without the block.
  • Screenshots: the pull still has no before/after screenshots. I can't capture them from here, and for people who haven't asked for reduced motion the CSS is unchanged.
Approved

Approved. The block in components.ts fixes the specificity problem: !important on collapsed durations, iteration-count 1, and animationend still fires for live.ts. The only visible change is a dimmed busy loader. I could not reach the preview from my sandbox (network denied), so I judged from the source and the diff only. Next, two things. 1) The block repeats the kill switch at base.ts:208 exactly, so the real fix is one switch plus a lint that counts it across files. Today the baseline motion count drops to 0 because the lint reads one file at a time. 2) The test matches CSS strings. Add a before/after screenshot, or a computed-style check, with reduced motion on, to show a class-ruled animation actually collapses.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs