Repository navigation
spec(design): under reduced motion, touch cross-fades and other pointers change at once - #8272
Conversation
… otherwise New current record design:reduced-motion. Under prefers-reduced-motion, core component transitions never move, scale, or spring; on a coarse pointer they cross-fade (opacity only, 200 ms or less), otherwise they change at once; no core component, prop, or theme value opts out. The draft design:motion DR5 points to it.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
AI review status for this pull request.
|
PR Analysis Report📚 Storybook Preview🧪 Sandbox PreviewNo new or modified components detected. Bundle Size Summary
Accessibility AuditStatus: No accessibility violations detected. Visual Regression46 of 504 shot(s) changed. A change here is a question, not a failure: check whether the after is the
and 26 more. Generated by PR Enrichment workflow | Storybook | Sandbox | View full report |
|
/approve-spec b3c21d9 |
There was a problem hiding this comment.
The reduced-motion rule is owner-approved on this head, but the record needs three fixes before it can land, and since a new commit resets that approval, the fixed head needs a fresh /approve-spec <full head SHA> from an engineering owner.
pnpm check:knowledgefails:verified_byis empty and nothing links the record to a repository path (INV25); naming the tests or stories that verify DR1–DR3 inverified_byfixes both errors.- The post-release clause sends an
Itemswipe row's settle to DR2 or DR3, whilespec:AST-057FR5 still requires the row to slide out and spring back with no reduced-motion form; one of the two records should say which governs. - The coarse-pointer State row also allows an immediate change, so nothing requires the cross-fade that DR2, DEC-1 and the phone examples describe; align the row and the DR1/DR2 failure signal with the intended rule.
[Automated review]
| approved_at: 2026-10-24 | ||
| owners: [cixzhang] | ||
| review_triggers: [motion, interaction, accessibility] | ||
| verified_by: [] |
There was a problem hiding this comment.
pnpm check:knowledge fails on this record: a current design record needs a non-empty verified_by, and with no linked path it is also unreachable (INV25). Listing the tests or stories that verify DR1–DR3 here fixes both errors.
|
|
||
| | State | Required representation | Allowed variation | | ||
| | --------------------------------- | ----------------------------------------------- | -------------------------------------------- | | ||
| | reduced motion, coarse pointer | Opacity-only cross-fade of 200 ms or less (DR2) | A shorter cross-fade, or an immediate change | |
There was a problem hiding this comment.
This row also allows “an immediate change” on a coarse pointer, so the cross-fade that DR2, DEC-1 and the phone examples describe is never required, and the DR1/DR2 failure signal cannot catch a touch change with no fade. If the fade is required, drop “or an immediate change” and add a no-fade failure signal; otherwise say “may cross-fade” in DR2 and DEC-1.
| Content that moves with the person's own pointer or finger during a drag | ||
| follows it as direct manipulation and is not a DR1 transition; a transition a | ||
| component starts after release follows DR2 or DR3. |
There was a problem hiding this comment.
After release this sends an Item swipe row's settle to DR2 or DR3, but spec:AST-057 FR5 still says the row MUST slide out and spring back, with no reduced-motion form. One of the two records should state which governs under reduced motion; the shipped settle already lands at once.
…ch cross-fade, and governs a swiped row's settle
|
/approve-spec b1d9172 |
There was a problem hiding this comment.
The reduced-motion rule and the spec:AST-057 FR5 amendment are owner-approved on this head, but three fixes are needed before it can land, and because a new commit resets that approval, the fixed head needs a fresh /approve-spec <full head SHA> from an engineering owner (the author can record it as one).
pnpm check:knowledgefails becausedocs/design/reduced-motion.mdis current with an emptyverified_by; list the tests or stories that check DR1–DR3, or land the record with the implementation that adds them.BottomSheetSwitcher.spec.mdFR3 and ORD1 still require a step handoff to transform and align the sheets with no reduced-motion form, which contradicts DR1 and DR2; give them a reduced-motion form as FR5 now has, or say which record governs.- FR5 sends a swiped row's settle to DR2 while the new record's drag paragraph says DR2 or DR3, so the two disagree for a touch swipe on a device whose primary pointer is fine; make them say the same thing.
[Automated review]
| approved_at: 2026-10-24 | ||
| owners: [cixzhang] | ||
| review_triggers: [motion, interaction, accessibility] | ||
| verified_by: [] |
There was a problem hiding this comment.
pnpm check:knowledge fails here: a current design record needs a non-empty verified_by (the design kind lists it under currentNonEmptyFields in docs/schemas/knowledge/v1.json). CI lint and test-ui (1/4) stop on the same message. Listing the tests or stories that check DR1–DR3, or landing the record with the implementation that adds them, clears it.
| - **DR2 — Touch presentations cross-fade.** Under reduced motion, when the | ||
| primary pointer is coarse (`pointer: coarse`), each DR1 transition MUST | ||
| change opacity only and MUST finish in 200 ms or less. An entering element | ||
| appears in its final place and fades in; a leaving element fades out where it | ||
| is. |
There was a problem hiding this comment.
BottomSheetSwitcher.spec.md FR3 (current) still says a taller retained sheet “aligns down to a shorter entering sheet” and fades only after the transforms complete, and its ORD1 orders “start entering transform and any required retained alignment → wait for both transforms → fade retained child”, with no reduced-motion form. That contradicts DR1 and this DR2 (“a leaving element fades out where it is”) for a multi-step bottom sheet on a phone. Giving FR3 and ORD1 a reduced-motion form, as FR5 now has, or naming the precedence here would reconcile them.
| | FR2 | `Item` MUST render each side's panel from its entries into an out-of-flow position on its existing root and MUST add no element to the row for it. The gesture MUST drive the row's travel by writing a custom property on the root; the root's transform MUST read it and each panel's transform its negation. | DEC-5; `useToastGesture` | Proposed; real-Chromium evidence required | | ||
| | FR3 | `swipeBehavior` MUST be one enum with the closed values `reveal` and `commit`, default `reveal`. `commit` with more than one entry on a side MUST warn in development, because the other entries have no touch path. | DEC-2; `spec:AST-002` FR15 | Proposed | | ||
| | FR4 | A sideways drag on a coarse pointer MUST move the row and uncover the side's panel, showing each entry's `label` and `icon` as a filled surface in its `variant`'s colour; the panel MUST NOT be visible at rest, by any pixel. | Real-browser finding: a zero-width panel still paints its padding | Proposed; real-Chromium evidence required | | ||
| | FR5 | Under `reveal`, a release past the panel's width MUST leave the row resting open with every entry tappable, and a drag past the commit point or a fling MUST fire the **outermost** entry and no other. Under `commit`, a release past the commit point or a fling MUST slide the row out and fire the outermost entry; nothing MUST rest. After any entry fires, under either model, the row MUST spring back unless the entry has `hasRemoval`, in which case it MUST hold out at the edge it travelled toward. That bit is the only thing an entry declares about the row; the component MUST NOT wait on, observe, or act on what the caller does next, and MUST NOT warn if a held-out row is never removed. A release short of the threshold MUST spring back under both models. Under reduced motion the slide out, the spring back, and the hold-out follow `design:reduced-motion` DR2: the row reaches its resting place with an opacity-only cross-fade and no travel or spring. `onActivate` MUST fire at activation and may return a Promise. | DEC-1, DEC-2; UIKit `performsFirstActionWithFullSwipe` | Proposed; real-device evidence required | |
There was a problem hiding this comment.
This sends the slide out, spring back and hold-out to DR2 in every case, but the swipe starts from a touch pointer event whatever the primary pointer, and design:reduced-motion applies DR2 only when the primary pointer is coarse; its drag paragraph says such a settle “follows DR2 or DR3”. On a touchscreen laptop the two records ask for different settles. “Follow design:reduced-motion DR2 or DR3” here, or a touch-gesture rule in the design record, would make them agree.
…e pointer rule for a swiped row; verified_by
|
/approve-spec 469f6d7 |
There was a problem hiding this comment.
The reduced-motion rule and the spec:AST-057 FR5 and BottomSheetSwitcher FR3/ORD1 amendments are owner-approved on this head, and the earlier conflicts and the knowledge-check failure are fixed, but two record corrections are still needed. Because a new commit resets that approval, the corrected head needs a fresh /approve-spec <full head SHA> from an engineering owner (the author can record it as one).
BottomSheetSwitcher.spec.mdFR3 now requires a reduced-motion handoff the shipped switcher does not perform (it still runs the entering and alignment transforms in0.01sand never fades the entering sheet in on touch), so under the record's own Intent the row needs a present-tense gap note, a Basis that citesdesign:reduced-motionDR2 and DR3, and a reduced-motion state in its verification row.docs/design/reduced-motion.mdsays no component contract links are introduced, yetBottomSheetSwitcherFR3 and ORD1 andspec:AST-057FR5 now implement DR2 and DR3, so that section should list them.
[Automated review]
| | FR1 | Zero or one child sheet is interactive for each controlled `activeSheet` value; all other children are hidden or retained inert and `aria-hidden` during a transition. | Current source, docs, and tests | | ||
| | FR2 | Each child sheet owns one native dialog. Opening a sheet presents its dialog once; a handoff presents the entering sheet's dialog above the retained one and closes the retained dialog only after its panel has faded. Returning to a sheet whose dialog is still open beneath a newer one re-presents it so the interactive sheet is the topmost layer. | DEC-1 | | ||
| | FR3 | During a handoff, the entering sheet is above the retained sheet. A taller retained sheet aligns down to a shorter entering sheet, and the retained sheet fades only after required transform motion completes. | Current source and transition tests | | ||
| | FR3 | During a handoff, the entering sheet is above the retained sheet. A taller retained sheet aligns down to a shorter entering sheet, and the retained sheet fades only after required transform motion completes. Under reduced motion the handoff follows `design:reduced-motion`: no transform motion runs, and the sheets cross-fade (DR2) or change at once (DR3) in the same order. | Current source and transition tests | |
There was a problem hiding this comment.
The shipped handoff does not do this yet: under reduced motion the entering panel still rises from translateY(100%) at full opacity and a taller retained panel still aligns down before fading, each over 0.01s (BottomSheetPanel.tsx:157-168, :673-674; BottomSheetSwitcher.tsx:610-627). The Intent (:39-40) says an unmet requirement's row carries a present-tense gap note, so this row needs one, its Basis should cite design:reduced-motion DR2 and DR3 rather than current source, and the FR1–FR4 verification row (:200) needs a reduced-motion handoff state.
|
|
||
| ## Component contract links | ||
|
|
||
| No component-specific contract links are introduced. |
There was a problem hiding this comment.
component:BottomSheetSwitcher FR3 and ORD1 now implement DR2 and DR3, as the after-release paragraph (:79-81) says, and spec:AST-057 FR5 does too. The design template puts such links in this section (component:<Name>/FR1 implements design:<surface>/DR1), so this line should name them instead of saying none are introduced.
…and verification; reduced-motion lists its component links
|
/approve-spec 5ff8849 |
There was a problem hiding this comment.
The reduced-motion rule and the spec:AST-057 FR5 and BottomSheetSwitcher FR3/ORD1 amendments are owner-approved on this head, and the switcher's gap note and the new record's contract links are in place, but each amended record still carries unchanged lines that the amendment now contradicts. Because a new commit resets the approval, the corrected head needs a fresh /approve-spec <full head SHA> from an engineering owner (the author can record it as one).
spec:AST-057FR5 keys the reduced-motion settle on a coarse primary pointer, while the same record says pointer type is read from the pointer event and rejects a second adaptive media query (:220,:265,:54); amend those lines, or key FR5 on the swipe's own pointer and align the design record's after-release paragraph.BottomSheetSwitcher.spec.mdstill says transition behavior stays unchanged (:46-47), which FR3's reduced-motion handoff no longer holds; qualify that line.
[Automated review]
| | FR2 | `Item` MUST render each side's panel from its entries into an out-of-flow position on its existing root and MUST add no element to the row for it. The gesture MUST drive the row's travel by writing a custom property on the root; the root's transform MUST read it and each panel's transform its negation. | DEC-5; `useToastGesture` | Proposed; real-Chromium evidence required | | ||
| | FR3 | `swipeBehavior` MUST be one enum with the closed values `reveal` and `commit`, default `reveal`. `commit` with more than one entry on a side MUST warn in development, because the other entries have no touch path. | DEC-2; `spec:AST-002` FR15 | Proposed | | ||
| | FR4 | A sideways drag on a coarse pointer MUST move the row and uncover the side's panel, showing each entry's `label` and `icon` as a filled surface in its `variant`'s colour; the panel MUST NOT be visible at rest, by any pixel. | Real-browser finding: a zero-width panel still paints its padding | Proposed; real-Chromium evidence required | | ||
| | FR5 | Under `reveal`, a release past the panel's width MUST leave the row resting open with every entry tappable, and a drag past the commit point or a fling MUST fire the **outermost** entry and no other. Under `commit`, a release past the commit point or a fling MUST slide the row out and fire the outermost entry; nothing MUST rest. After any entry fires, under either model, the row MUST spring back unless the entry has `hasRemoval`, in which case it MUST hold out at the edge it travelled toward. That bit is the only thing an entry declares about the row; the component MUST NOT wait on, observe, or act on what the caller does next, and MUST NOT warn if a held-out row is never removed. A release short of the threshold MUST spring back under both models. Under reduced motion the slide out, the spring back, and the hold-out follow `design:reduced-motion`: the row reaches its resting place with no travel or spring, cross-fading on a coarse primary pointer (DR2) and changing at once otherwise (DR3). `onActivate` MUST fire at activation and may return a Promise. | DEC-1, DEC-2; UIKit `performsFirstActionWithFullSwipe` | Proposed; real-device evidence required | |
There was a problem hiding this comment.
This keys the reduced-motion settle on “a coarse primary pointer”, which design:reduced-motion DR2 defines as pointer: coarse, but the same record still says “Pointer type is read from the pointer event” (:265), that the split is “Read from the pointer event, not a prop or a media query” (:220), and its review checks reject “a second adaptive media query” (:54); the swipe already reads (prefers-reduced-motion: reduce). On a touchscreen laptop the two readings give different settles. Amending those three lines to admit the primary-pointer read for this form, or keying the form on the swipe's own pointer, would make them agree.
| | --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- | | ||
| | FR1 | Zero or one child sheet is interactive for each controlled `activeSheet` value; all other children are hidden or retained inert and `aria-hidden` during a transition. | Current source, docs, and tests | | ||
| | FR2 | Each child sheet owns one native dialog. Opening a sheet presents its dialog once; a handoff presents the entering sheet's dialog above the retained one and closes the retained dialog only after its panel has faded. Returning to a sheet whose dialog is still open beneath a newer one re-presents it so the interactive sheet is the topmost layer. | DEC-1 | | ||
| | FR3 | During a handoff, the entering sheet is above the retained sheet. A taller retained sheet aligns down to a shorter entering sheet, and the retained sheet fades only after required transform motion completes. Under reduced motion the handoff follows `design:reduced-motion`: no transform motion runs, and the sheets cross-fade (DR2) or change at once (DR3) in the same order. Implementation gap: under reduced motion the shipped switcher still runs the entering and alignment transforms, shortened, and does not fade the entering sheet in on a coarse pointer. | Current source and transition tests; `design:reduced-motion` DR2, DR3 | |
There was a problem hiding this comment.
With this reduced-motion handoff and its gap note, the Compatibility line (:46-47) no longer holds: it still says “public props, defaults, exports, visual output, and transition behavior stay unchanged”. Qualifying it, for example “except under reduced motion, where the handoff follows design:reduced-motion (FR3)”, keeps the record consistent.
… compatibility names the reduced-motion handoff
|
/approve-spec f60ceae |
There was a problem hiding this comment.
The reduced-motion rule and the spec:AST-057 FR5 and BottomSheetSwitcher FR3/ORD1 amendments are owner-approved on this head, and both corrections requested on the previous head are in place. One contradiction remains inside design:reduced-motion: DR3 and the “any other pointer” State representation row still require a touch swipe's settle on a device whose primary pointer is fine to complete at once, and the contract links still say FR5 implements DR2 and DR3, while the after-release paragraph and FR5 now cross-fade every touch-released settle. Please let DR2 also cover a transition that a touch drag's release starts (and DR3 everything else) and change the link to “FR5 implements DR2”; because a new commit resets the approval, the corrected head then needs a fresh /approve-spec <full head SHA> from an engineering owner, which the author can record as one.
[Automated review]
| - **DR3 — Fine-pointer presentations change at once.** Under reduced motion, | ||
| when the primary pointer is not coarse, each DR1 transition MUST complete | ||
| immediately, with no animated property. |
There was a problem hiding this comment.
DR3 still covers every DR1 transition when the primary pointer is not coarse, so on a touchscreen laptop it requires a touch swipe's settle to complete at once with no animated property, while the after-release paragraph (:78-81) and spec:AST-057 FR5 cross-fade that settle because a finger started it. Letting DR2 also cover a transition that a touch drag's release starts (and DR3 everything else), and mirroring that in the State representation rows (:58-59), would give the settle one outcome.
| - `spec:AST-057` FR5 implements DR2 and DR3 for a swiped row's slide out, | ||
| spring back, and hold-out. |
There was a problem hiding this comment.
FR5 now cross-fades every settle because the swipe's own touch pointer started it, so it no longer implements DR3; this link should read “implements DR2”.
|
/approve-spec c7d28a2 |
There was a problem hiding this comment.
Nothing blocks this from landing: an engineering owner approved design:reduced-motion and the spec:AST-057 FR5 and BottomSheetSwitcher FR3/ORD1 amendments for this exact head, and the contradiction left on the previous revision is fixed, since DR2 now covers a transition that a touch drag's release starts, DR3 covers everything else, and the contract link reads "FR5 implements DR2". The records pass the knowledge check and conflict with no other current record; the one failing CI job, pr-a11y, trips a DateInput wheel-scroll assertion in code this change does not touch. Two behaviours stay undefined and could be settled in a later record edit: whether a scroll a component starts itself, such as a carousel's Scroll right, counts as a DR1 transition, and whether a hover-intent dwell meets DR3's "complete immediately". Any further commit needs a fresh /approve-spec <full head SHA> from an engineering owner, which the author can record as one.
[Automated review]
…se or a tap cross-fades design:reduced-motion (current, meta#8272): under prefers-reduced-motion a core component's changes of state never move, scale or spring (DR1); a touch release may only cross-fade, opacity only, 200 ms or less (DR2); nothing keeps motion (DR4). The touch presentation kept the lift's scale and the release coast on their springs under Reduce Motion (a guess, M-SL12). Now the lift, feedback with no fade of its own, plays only when the animator reports motion on (the press shows in the pressed paint alone); the coast is played as a touch release and the tap's spring as a plain change, so the animator's Reduced Motion policy turns both into its 200 ms opacity cross-fade to the same value. The slider keeps no Reduce Motion check or mode of its own, and hands back the opacity a cross-fade wrote with the translate. The Chromium iOS-emulated case is flipped from "keeps the lift and coast" to the record's rule (no scale while held; a flick's release and a track tap dip the opacity and land within 200 ms, opacity only, the release at the coast's value past the lift's); it fails on the previous head (the held thumb at scale 1.55).
…ad of appearing at once (#8322) Supersedes #8214, which #8272's merge closed through its closing keyword; same branch, plus the 200 ms cap that `design:reduced-motion` asks for. ## User impact With Reduce Motion on, a Tooltip, HoverCard, menu or other layer that opens under a finger appeared in a single frame: the shared layer entrance dropped its whole animation, fade included. On a touch screen Reduce Motion replaces motion with a cross-fade, never a jump. ## Evidence iOS 26.2 Safari with Reduce Motion on, a tap on the Tooltip's term (a grey dot drawn by the test page marks the finger); before (main), then after:   - Before: the tooltip appears in a single frame of the 60 fps recording. - After: it fades in where it lands, opacity only, over the theme's fast duration (165 ms with the default motion scale; most of the change in the first 115 ms on the standard ease). Under a mouse with Reduce Motion it still appears at once; under a finger without it the entrance still animates opacity and transform. - Regression proof: the Chromium contract's fade cases fail on main's build, and its slow-theme case (`--duration-fast-max: 400ms`) fails without the cap (0.4 s) and passes with it (0.2 s). ## Problem and solution fit `layerAnimations` (the entrance every layer applies through `layer.render`) set `animation-name: none` under `prefers-reduced-motion: reduce`. That suits a mouse. Under a coarse pointer it is a jump where iOS fades. Under a coarse pointer with Reduce Motion the entrance now keeps its opacity ramp, drops the slide and the scale, and lasts `min(--duration-fast-max, 200ms)`. Under a mouse it still appears at once; without Reduce Motion nothing changes. Components with their own touch presentation (DropdownMenu, Popover in the open stacks) override this entrance inline, so this reaches layers that have none: Tooltip, HoverCard, Selector, ContextMenu and the like. ## Expected behavior and authority - `design:reduced-motion` (current): under reduced motion nothing moves, scales or springs (DR1); on a coarse pointer it cross-fades, opacity only, in 200 ms or less (DR2); on other pointers it changes at once (DR3); no theme value keeps motion (DR4). ## Smallest restoration One added media condition per placement in `layerAnimations.stylex.ts` (a fade keyframe, and the capped duration), and a real-engine contract with its own pr-a11y step. A UIKit-timed touch entrance for tooltips is an open owner question (D7), not built here. ## Scope - [x] One defect is restored. - [x] Separable contradictory or unsettled tagalongs were removed or split. - [x] No new public API, visual representation, default, or interaction model is hidden under the bug-fix label. - [x] Public text and artifacts contain no internal Meta context. ## Testing - `pnpm exec playwright test packages/core/src/Layer/__tests__/LayerReducedMotion.a11y.chromium.spec.ts --project chromium` after `pnpm storybook:build`: 8/8 (Tooltip and HoverCard: an opacity-only fade of 200 ms or less under a finger with Reduce Motion, also with a slow theme; unchanged without it; still instant under a mouse). The fade cases fail against main; the slow-theme case fails against #8214's head. - `pnpm vitest run .github/scripts/ci-test-routing.test.mjs .github/scripts/release-check-workflow.test.mjs`: 101 pass. - `pnpm vitest run packages/core/src/Layer packages/core/src/Tooltip packages/core/src/HoverCard packages/core/src/Popover packages/core/src/DropdownMenu packages/core/src/Selector packages/core/src/ContextMenu`: 983 pass. - iOS 26.2 simulator in Safari with Reduce Motion on, before and after, 60 fps recordings with a page log; the cap changes nothing at the default theme's 165 ms. - Core typecheck, eslint, prettier.
…se or a tap cross-fades design:reduced-motion (current, meta#8272): under prefers-reduced-motion a core component's changes of state never move, scale or spring (DR1); a touch release may only cross-fade, opacity only, 200 ms or less (DR2); nothing keeps motion (DR4). The touch presentation kept the lift's scale and the release coast on their springs under Reduce Motion (a guess, M-SL12). Now the lift, feedback with no fade of its own, plays only when the animator reports motion on (the press shows in the pressed paint alone); the coast is played as a touch release and the tap's spring as a plain change, so the animator's Reduced Motion policy turns both into its 200 ms opacity cross-fade to the same value. The slider keeps no Reduce Motion check or mode of its own, and hands back the opacity a cross-fade wrote with the translate. The Chromium iOS-emulated case is flipped from "keeps the lift and coast" to the record's rule (no scale while held; a flick's release and a track tap dip the opacity and land within 200 ms, opacity only, the release at the coast's value past the lift's); it fails on the previous head (the held thumb at scale 1.55).
…se or a tap cross-fades design:reduced-motion (current, meta#8272): under prefers-reduced-motion a core component's changes of state never move, scale or spring (DR1); a touch release may only cross-fade, opacity only, 200 ms or less (DR2); nothing keeps motion (DR4). The touch presentation kept the lift's scale and the release coast on their springs under Reduce Motion (a guess, M-SL12). Now the lift, feedback with no fade of its own, plays only when the animator reports motion on (the press shows in the pressed paint alone); the coast is played as a touch release and the tap's spring as a plain change, so the animator's Reduced Motion policy turns both into its 200 ms opacity cross-fade to the same value. The slider keeps no Reduce Motion check or mode of its own, and hands back the opacity a cross-fade wrote with the translate. The Chromium iOS-emulated case is flipped from "keeps the lift and coast" to the record's rule (no scale while held; a flick's release and a track tap dip the opacity and land within 200 ms, opacity only, the release at the coast's value past the lift's); it fails on the previous head (the held thumb at scale 1.55).
Exact decision needed
What a person who prefers reduced motion sees when a core component animates a change, on touch and on other pointers, and whether a component may keep its motion.
Existing authority searched
design:motionDR5 (draft): every animated transition has an immediate or minimally moving form. Draft, so not authority.spec:AST-013FR13: reduced-motion behavior is unchanged by server-rendered entrances; it defines none.spec:AST-012FR2:motion: reduceandpointer: coarse/fineare the adaptation conditions.design:motionDEC-2, hover entrances; no reduced-motion rule), fix(Layer): under a finger with Reduce Motion, a layer fades in instead of appearing at once #8214 (layer fade under a finger with Reduce Motion; conforms once its fade is 200 ms or less), spec(AST-094): touch presentations follow UIKit #8052 (AST-094 FR8 carries the matching touch rule), docs(Spinner): an iOS-style activity indicator, composed from existing APIs #8211, feat(ProgressBar): iOS Safari's indeterminate bar under a finger #8239, feat(Skeleton): rest still under a finger, as iOS placeholders do #8253 (touch indicator and placeholder work; continuous indicators are outside this record).Decision
New current record
design:reduced-motion, matching iOS Reduce Motion with Prefer Cross-Fade:prefers-reduced-motion: reduce, a core component's entrances, exits, presentation switches, and state changes never move, scale, resize, or spring.spec:AST-057FR5 names that a swiped row's slide out, spring back, and hold-out follow DR2 under reduced motion. The draftdesign:motionkeeps its other rules unapproved; its DR5 points to the new record. A separate record keeps the promotion to this one rule, asdocs/design/README.mdadvises for a different approval lifecycle.Rejected alternatives
Claim scope and non-goals
Only transitions under reduced motion. Motion without the preference, durations and easing, tokens, continuous indicators such as a spinner, and content the person drags with their own finger are outside this record.
Evidence and implementation relationship
Layers that open under a finger with Reduce Motion appear in one frame today, where iOS cross-fades. The open fix #8214 makes them fade under a coarse pointer and stay immediate under a mouse, which conforms to DR2 and DR3 once its fade is 200 ms or less. The open draft #7367 (
design:motionDEC-2, hover-summoned layers) does not touch reduced motion. The AST-094 revision in #8052 carries the matching touch rule. No implementation is included.Writing rubric results
Scope
Validation
pnpm check:knowledge(CI lint)git diff --check