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

scout/forge:scout/3548-the-shared-components-stylesheet-stops-e → main 2 commits fork 2 threads · 2 unresolved

  • Merged. scout/3548-the-shared-components-stylesheet-stops-e was merged into main as 95f64cd.

Review

Sign in to review this change.
3 files changed +34 −1
docs/design/baseline.json +1 −1
@@ -57,7 +57,7 @@
5757 "src/web/style/components.ts": {
5858 "colour": 3,
5959 "size": 2,
60 "motion": 20,
60 "motion": 0,
6161 "forced": 1,
6262 "focus": 2,
6363 "input": 1,
src/web/style/components.ts +14
@@ -833,4 +833,18 @@ a.row:hover .row-title { color: var(--accent); }
833833 input, textarea, select, .input, .input[data-size="sm"], .topbar-search input,
834834 .palette .palette-input { font-size: 16px; }
835835 }
836
837 /* Asked for less motion, the page keeps every state and loses the movement.
838 \`!important\` because a bare \`*\` has no specificity: every class rule above
839 that sets an animation or transition would win. Durations collapse rather
840 than \`animation: none\`, so \`animationend\` still fires (src/web/live.ts
outdated

Zero specificity: any .class { animation: ... } rule beats this. Use animation: none !important; transition: none !important;, or keep base.ts:208 as the single kill switch. Add a test that would fail without it.

841 removes its flash class on it). A busy loader stops turning and dims. */
842 @media (prefers-reduced-motion: reduce) {
843 *, *::before, *::after {
844 animation-duration: .01ms !important;
845 animation-iteration-count: 1 !important;

This repeats base.ts:208 line for line. Keep the .ico-loader dim here. Fold the rest into one switch when the lint can see across files.

846 transition-duration: .01ms !important;
847 }
848 .btn[aria-busy="true"] .ico-loader { opacity: .6; }
849 }
836850 `;
test/components-reduced-motion.test.ts added +19
@@ -0,0 +1,19 @@
1 import { expect, test } from "bun:test";
2 import { components } from "../src/web/style/components";
3
4 const block = components.match(/@media \(prefers-reduced-motion: reduce\) \{([\s\S]*?)\n\}\n/)?.[1] ?? "";
5
6 test("reduced motion collapses every animation and transition, and wins over class rules", () => {
7 expect(block).toContain("*, *::before, *::after");
8 for (const d of ["animation-duration: .01ms", "animation-iteration-count: 1", "transition-duration: .01ms"]) {
9 expect(block).toContain(`${d} !important;`);
10 }
11 });
12
13 test("reduced motion leaves animationend firing (live.ts clears its flash class on it)", () => {
14 expect(block).not.toMatch(/animation\s*:\s*none/);
15 });
16
17 test("a busy loader has a static look under reduced motion", () => {
18 expect(block).toContain('.btn[aria-busy="true"] .ico-loader { opacity: .6; }');
19 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs