Skip to content

spec(design): under reduced motion, touch cross-fades and other pointers change at once - #8272

Merged
cixzhang merged 6 commits into
mainfrom
spec/gr-reduced-motion
Oct 24, 2026
Merged

cixzhang merged 6 commits into
mainfrom
spec/gr-reduced-motion

Conversation

@cixzhang

@cixzhang cixzhang commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

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

Decision

New current record design:reduced-motion, matching iOS Reduce Motion with Prefer Cross-Fade:

  • DR1: under prefers-reduced-motion: reduce, a core component's entrances, exits, presentation switches, and state changes never move, scale, resize, or spring.
  • DR2: on a coarse pointer, they cross-fade: opacity only, 200 ms or less.
  • DR3: on any other pointer, they complete at once.
  • DR4: no core component, prop, or theme value keeps motion under reduced motion.

spec:AST-057 FR5 names that a swiped row's slide out, spring back, and hold-out follow DR2 under reduced motion. The draft design:motion keeps its other rules unapproved; its DR5 points to the new record. A separate record keeps the promotion to this one rule, as docs/design/README.md advises for a different approval lifecycle.

Rejected alternatives

  • A per-component setting that keeps full motion under reduced motion.

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:motion DEC-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

  • R1 narration: none.
  • R2 research in the record: none.
  • R3 evidence naming a change: none; verification names browser states.
  • R4 rejected alternatives over one line or outside a decision: one, inside DEC-1, one line.
  • R5 pull request or issue references remaining: none.
  • R6 ruled open questions still listing options: none.

Scope

  • This PR contains specification records only.
  • It records observable behavior and preserves equivalent internal implementations.
  • Any named internal mechanism is intentionally public, names its dependent caller/system, and explains why an equivalent implementation would not satisfy the contract.
  • Verification states the evidence layer and failure signal without prescribing CI job/workflow topology.
  • It records one intentional durable decision, not a rescue for a separable tagalong.
  • The canonical knowledge owner, affected current claims, and overlapping open work are named; any ownership collision is resolved or called out for human decision.
  • Adjacent API, visual, accessibility, composition, and implementation facts remain outside scope unless this exact decision depends on them.
  • Public text and artifacts contain no internal Meta context.

Validation

  • pnpm check:knowledge (CI lint)
  • Prettier
  • git diff --check

… 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.
@vercel

vercel Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
astryx Ready Ready Preview Oct 24, 2026 8:47pm UTC

Request Review

@astracat-bot

astracat-bot Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

Review status Updated
🟢 Reviewed (for maintainers only) Oct 24, 2026, 9:20 PM UTC

@github-actions github-actions Bot added the needs:spec-owner-review Current knowledge records await owner approval label Oct 24, 2026
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Oct 24, 2026
@github-actions

github-actions Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

📚 Storybook Preview

View Storybook for this PR

🧪 Sandbox Preview

View Sandbox for this PR

No new or modified components detected.

Bundle Size Summary

Package Size (ESM) Size (CJS) Gzipped
@astryxdesign/core N/A 4.9KB 1.2KB

Accessibility Audit

Status: No accessibility violations detected.

Visual Regression

46 of 504 shot(s) changed.

A change here is a question, not a failure: check whether the after is the
picture you intended. Record that review on the PR. Baseline maintenance is an
explicit dispatch of CI; this report never rewrites the baseline or adds a release gate.

component story theme mode pixels
Selector Indicator Space Bottom Sheet Narrow End RTL neutral light 753,933
Selector Indicator Space Bottom Sheet Wide Start neutral light 740,072
Chat Full AI Chat probe light 230,823
Chat Full AI Chat probe dark 229,805
RichTextEditor Markdown parity: side by side probe dark 94,866
RichTextEditor Markdown parity: side by side probe light 89,762
Collapsible Dividers probe dark 66,445
Collapsible Dividers probe light 66,445
Chat Default neutral dark 36,304
Chat Default neutral light 35,767
Chat Empty State probe light 23,531
Chat Empty State probe dark 22,622
ChatAutoScroll Scroll Behavior Comparison neutral dark 15,378
ChatAutoScroll Scroll Behavior Comparison neutral light 14,107
Chat With Attachments probe light 13,406
Chat With Attachments probe dark 11,232
ProgressBar With Target Mark probe dark 7,895
ProgressBar With Target Mark probe light 7,895
Markdown Default neutral dark 7,573
Markdown Default neutral light 7,566

and 26 more.


Generated by PR Enrichment workflow | Storybook | Sandbox | View full report

@cixzhang

Copy link
Copy Markdown
Contributor Author

/approve-spec b3c21d9

@github-actions github-actions Bot removed the needs:spec-owner-review Current knowledge records await owner approval label Oct 24, 2026

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:knowledge fails: verified_by is empty and nothing links the record to a repository path (INV25); naming the tests or stories that verify DR1–DR3 in verified_by fixes both errors.
  • The post-release clause sends an Item swipe row's settle to DR2 or DR3, while spec:AST-057 FR5 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]

Comment thread docs/design/reduced-motion.md Outdated
approved_at: 2026-10-24
owners: [cixzhang]
review_triggers: [motion, interaction, accessibility]
verified_by: []

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread docs/design/reduced-motion.md Outdated

| 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 |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread docs/design/reduced-motion.md Outdated
Comment on lines +69 to +71
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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
@cixzhang

Copy link
Copy Markdown
Contributor Author

/approve-spec b1d9172

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:knowledge fails because docs/design/reduced-motion.md is current with an empty verified_by; list the tests or stories that check DR1–DR3, or land the record with the implementation that adds them.
  • BottomSheetSwitcher.spec.md FR3 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]

Comment thread docs/design/reduced-motion.md Outdated
approved_at: 2026-10-24
owners: [cixzhang]
review_triggers: [motion, interaction, accessibility]
verified_by: []

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +64 to +68
- **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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread docs/specs/AST-057-row-actions/spec.md Outdated
| 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 |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
@cixzhang

Copy link
Copy Markdown
Contributor Author

/approve-spec 469f6d7

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.md FR3 now requires a reduced-motion handoff the shipped switcher does not perform (it still runs the entering and alignment transforms in 0.01s and 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 cites design:reduced-motion DR2 and DR3, and a reduced-motion state in its verification row.
  • docs/design/reduced-motion.md says no component contract links are introduced, yet BottomSheetSwitcher FR3 and ORD1 and spec:AST-057 FR5 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 |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread docs/design/reduced-motion.md Outdated

## Component contract links

No component-specific contract links are introduced.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
@cixzhang

Copy link
Copy Markdown
Contributor Author

/approve-spec 5ff8849

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-057 FR5 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.md still says transition behavior stays unchanged (:46-47), which FR3's reduced-motion handoff no longer holds; qualify that line.

[Automated review]

Comment thread docs/specs/AST-057-row-actions/spec.md Outdated
| 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 |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
@cixzhang

Copy link
Copy Markdown
Contributor Author

/approve-spec f60ceae

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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]

Comment on lines +69 to +71
- **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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread docs/design/reduced-motion.md Outdated
Comment on lines +106 to +107
- `spec:AST-057` FR5 implements DR2 and DR3 for a swiped row's slide out,
spring back, and hold-out.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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”.

@cixzhang

Copy link
Copy Markdown
Contributor Author

/approve-spec c7d28a2

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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]

@cixzhang
cixzhang merged commit c743ae0 into main Oct 24, 2026
70 of 71 checks passed
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 25, 2026
…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).
cixzhang pushed a commit that referenced this pull request Oct 25, 2026
…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](https://raw.githubusercontent.com/vjeux/astryx/5d92dd599ea0966754547e01fdb59e1d8e86acb5/ios-reduce-motion-tooltip-before.gif) ![After](https://raw.githubusercontent.com/vjeux/astryx/5d92dd599ea0966754547e01fdb59e1d8e86acb5/ios-reduce-motion-tooltip-after.gif)

- 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.
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 25, 2026
…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).
@github-actions
github-actions Bot deleted the spec/gr-reduced-motion branch October 25, 2026 06:20
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 25, 2026
…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).

This branch was successfully deployed

1 active deployment
Preview — c7d28a22 Deployed Oct 24, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant