Skip to content

fix(Layer): the touch cross-fade uses the motion foundation's Reduce Motion values - #8355

Merged
cixzhang merged 5 commits into
meta:mainfrom
vjeux:uikit/c26-layer-rm-values
Nov 3, 2026
Merged

cixzhang merged 5 commits into
meta:mainfrom
vjeux:uikit/c26-layer-rm-values

Conversation

@vjeux

@vjeux vjeux commented Oct 25, 2026

Copy link
Copy Markdown
Contributor

Follows #8322 (merged), as its review agreed: the layer's touch cross-fade takes the motion foundation's own Reduce Motion values.

User impact

With Reduce Motion on, a layer that opens under a finger fades in over the theme's fast duration and curve (#8322), capped at 200 ms. The motion foundation's policy, which every other touch presentation follows, fades over exactly 200 ms on easeInOut. A layer and a touch presentation opened side by side fade on different clocks.

Evidence

iOS 26.2 Safari with Reduce Motion on, a tap on the Tooltip's term (a grey dot marks the finger); after this change:

After

  • It fades in where it lands, opacity only, over 200 ms on easeInOut, with no travel or scale; under a mouse it still appears at once.
  • Regression proof: the Chromium contract's four cross-fade cases (Tooltip and HoverCard, default and slow theme) check the curve and the 200 ms; they fail on main's build and pass on this one.

Problem and solution fit

layerAnimations.stylex.ts timed the cross-fade with the theme's fast duration and easing. The animator's Reduce Motion policy (design:reduced-motion) uses a fixed 200 ms easeInOut under a coarse pointer and no animation under any other pointer. The layer now uses the same values and spells its media queries as that policy does: (prefers-reduced-motion: reduce) and (pointer: coarse) and (prefers-reduced-motion: reduce) and (not (pointer: coarse)). The keys stay literal, since the StyleX build orders an imported at-rule key differently; the values become the foundation's shared constants once those are on main.

Expected behavior and authority

  • design:reduced-motion (current): on a coarse pointer an opacity-only cross-fade in 200 ms or less that no theme value lengthens (DR2, DR4); on other pointers an instant change (DR3).

Smallest restoration

Two media conditions and their values in layerAnimations.stylex.ts; the contract checks the easing, and its mouse case asserts a fine pointer in its context first.

Scope

  • One defect is restored.
  • Separable contradictory or unsettled tagalongs were removed or split.
  • No new public API, visual representation, default, or interaction model is hidden under the bug-fix label.
  • 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. The four cross-fade cases fail on main's build (theme-timed curve).
  • pnpm vitest run packages/core/src/Layer: 210 pass.
  • iOS 26.2 simulator in Safari, 60 fps recording with a page log, Reduce Motion set through the simulator's accessibility settings.
  • Core typecheck, eslint, prettier.

…Motion values

Under Reduce Motion the layer entrance now takes the values and media queries
the motion foundation's policy uses: under a coarse pointer an opacity-only
fade of 200 ms on easeInOut that no theme value lengthens
(design:reduced-motion DR2, DR4), under any other pointer no animation (DR3).
The queries are spelled as that policy spells them (literal keys, which the
StyleX build orders as written), and the values become its shared constants
once those are on main.

The Chromium contract checks the curve too: ease-in-out, which fails on the
theme-timed fade on main. Its mouse case asserts that its context is a fine
pointer before it checks anything.
@vercel

vercel Bot commented Oct 25, 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 Nov 3, 2026 9:44am UTC

Request Review

@github-actions github-actions Bot added the needs:design-review Affects visuals — Design should review label Oct 25, 2026
@astracat-bot

astracat-bot Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

Review status Updated
🟢 Reviewed (for maintainers only) Nov 3, 2026, 9:54 AM UTC

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Oct 25, 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.

Thanks: the layer's Reduce Motion touch fade works as described, an opacity-only 200 ms fade under a finger, with mouse and no-preference entrances unchanged. Approval waits on one owner decision: design:reduced-motion DR2 only bounds this fade at 200 ms and names no curve, so a fixed 200 ms on easeInOut whatever the theme (the policy proposed in #8056) needs an engineering owner to provide or identify the governing spec. An engineering owner records that decision by commenting /approve-spec f23d41c0cf2f88ef38b81e60f5bd0992d191fa62 on this pull request (the author may do so if they are an engineering owner), or by stating the curve and duration in DR2.

  • If the curve stays, let the contract's ease-in-out check also accept cubic-bezier(0.42, 0, 0.58, 1): Chromium reports the curve as written, and only the production Storybook build turns it into ease-in-out.

[Automated review]

Comment on lines +103 to +106
animationTimingFunction: {
default: easeVars['--ease-standard'],
'@media (prefers-reduced-motion: reduce) and (pointer: coarse)':
'cubic-bezier(0.42, 0, 0.58, 1)',

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 curve is the open question for an owner: design:reduced-motion DR2 bounds the touch cross-fade at 200 ms, opacity only, and names no curve, while main's layer fade uses the theme's --ease-standard. Whether core's reduced-motion touch cross-fade is a fixed 200 ms on easeInOut whatever the theme (the policy proposed in #8056) needs an engineering owner's decision.

Comment on lines +127 to +129
// The motion foundation's cross-fade: Core Animation's easeInOut,
// cubic-bezier(0.42, 0, 0.58, 1), which the engine names ease-in-out.
expect(entrance.easing).toBe('ease-in-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.

Chromium reports this value as cubic-bezier(0.42, 0, 0.58, 1) (computed on a Storybook dev server, and the core package's dist/astryx.css ships it that way); it reads ease-in-out here only because the production Storybook build rewrites it. If the curve stays, accept both spellings and say the build, not the engine, renames it. The same applies at line 153.

@github-actions

github-actions Bot commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

🔎 Preview · b749028

Storybook · Sandbox · built from this PR's head

Modified components

Component Size LoC Visual · A11y · RTL
DateInput 103.3 kB (+41 B) · 15.3 kB CSS (+46 B) — — · — · —
TableHeaderCell 75.9 kB (+46 B) · 15.6 kB CSS (+39 B) — — · — · —
TableCell 76.4 kB (+45 B) · 15.8 kB CSS (+39 B) — — · — · —
110 more components
Component Size LoC Visual · A11y · RTL
CheckboxInput 48.8 kB (+49 B) · 9.7 kB CSS (+33 B) — — · — · —
DateTimeInput 158.8 kB (+39 B) · 17.1 kB CSS (+40 B) — — · — · —
Table 84.5 kB (+39 B) · 16.0 kB CSS (+40 B) — — · — · —
CheckboxList 43.4 kB (+44 B) · 8.6 kB CSS (+33 B) — — · — · —
Markdown 145.2 kB (+45 B) · 18.3 kB CSS (+32 B) — — · — · —
ContextMenu 75.2 kB (+40 B) · 15.3 kB CSS (+36 B) — — · — · —
TextArea 47.3 kB (+45 B) · 9.9 kB CSS (+30 B) — — · — · —
MoreMenu 73.5 kB (+36 B) · 15.5 kB CSS (+38 B) — — · — · —
Slider 88.9 kB (+43 B) · 9.4 kB CSS (+31 B) — — · — · —
PowerSearch 184.6 kB (+35 B) · 20.6 kB CSS (+38 B) — — · — · —
Selector 77.2 kB (+36 B) · 15.7 kB CSS (+36 B) — — · — · —
TimeInput 90.1 kB (+35 B) · 14.3 kB CSS (+37 B) — — · — · —
RadioList 43.0 kB (+38 B) · 8.4 kB CSS (+33 B) — — · — · —
Tokenizer 70.3 kB (+27 B) · 17.1 kB CSS (+44 B) — — · — · —
ComplexSelector 55.6 kB (+40 B) · 11.1 kB CSS (+30 B) — — · — · —
DropdownMenu 73.2 kB (+32 B) · 15.5 kB CSS (+38 B) — — · — · —
AppShell 68.6 kB (+39 B) · 12.2 kB CSS (+30 B) — — · — · —
IconButton 42.4 kB (+41 B) · 9.5 kB CSS (+28 B) — — · — · —
ListItem 52.8 kB (+37 B) · 11.0 kB CSS (+32 B) — — · — · —
Pagination 86.2 kB (+33 B) · 16.0 kB CSS (+36 B) — — · — · —
ToggleButton 43.3 kB (+40 B) · 9.6 kB CSS (+29 B) — — · — · —
BaseTypeahead 55.2 kB (+37 B) · 10.3 kB CSS (+31 B) — — · — · —
CheckboxListItem 53.5 kB (+36 B) · 11.0 kB CSS (+32 B) — — · — · —
DialogHeader 48.5 kB (+38 B) · 10.2 kB CSS (+30 B) — — · — · —
DrawerHeader 48.5 kB (+36 B) · 11.6 kB CSS (+32 B) — — · — · —
FileInput 53.7 kB (+37 B) · 10.7 kB CSS (+31 B) — — · — · —
Lightbox 50.5 kB (+39 B) · 10.2 kB CSS (+29 B) — — · — · —
MobileNav 50.3 kB (+37 B) · 10.4 kB CSS (+31 B) — — · — · —
MultiSelector 72.8 kB (+29 B) · 15.2 kB CSS (+39 B) — — · — · —
NumberInput 55.0 kB (+38 B) · 11.2 kB CSS (+30 B) — — · — · —
ChatComposer 61.1 kB (+38 B) · 11.2 kB CSS (+29 B) — — · — · —
SideNav 66.1 kB (+36 B) · 10.9 kB CSS (+31 B) — — · — · —
Thumbnail 49.6 kB (+35 B) · 11.3 kB CSS (+32 B) — — · — · —
Button 42.4 kB (+38 B) · 9.5 kB CSS (+28 B) — — · — · —
ChatDictationButton 46.0 kB (+36 B) · 9.8 kB CSS (+30 B) — — · — · —
CodeBlock 59.1 kB (+36 B) · 11.1 kB CSS (+30 B) — — · — · —
MobileNavToggle 45.8 kB (+36 B) · 9.8 kB CSS (+30 B) — — · — · —
SideNavItem 54.8 kB (+37 B) · 10.5 kB CSS (+29 B) — — · — · —
Switch 87.1 kB (+38 B) · 10.6 kB CSS (+28 B) — — · — · —
TextInput 52.9 kB (+35 B) · 11.0 kB CSS (+31 B) — — · — · —
ChatLayout 48.5 kB (+36 B) · 10.3 kB CSS (+29 B) — — · — · —
TabMenu 55.5 kB (+36 B) · 10.4 kB CSS (+29 B) — — · — · —
TopNavMegaMenu 54.4 kB (+35 B) · 11.2 kB CSS (+30 B) — — · — · —
TopNavMenu 58.4 kB (+35 B) · 10.5 kB CSS (+30 B) — — · — · —
Typeahead 62.9 kB (+34 B) · 11.8 kB CSS (+31 B) — — · — · —
SideNavHeading 58.9 kB (+35 B) · 10.6 kB CSS (+29 B) — — · — · —
TopNav 51.9 kB (+34 B) · 10.7 kB CSS (+30 B) — — · — · —
TopNavHeading 58.3 kB (+33 B) · 10.4 kB CSS (+31 B) — — · — · —
Banner 47.3 kB (+31 B) · 10.3 kB CSS (+32 B) — — · — · —
Carousel 50.8 kB (+33 B) · 10.6 kB CSS (+30 B) — — · — · —
ChatComposerInput 56.2 kB (+36 B) · 10.4 kB CSS (+27 B) — — · — · —
ChatComposerTokenElement 56.2 kB (+36 B) · 10.4 kB CSS (+27 B) — — · — · —
RadioListItem 51.3 kB (+30 B) · 10.1 kB CSS (+33 B) — — · — · —
AvatarGroupOverflow 39.0 kB (+39 B) · 6.6 kB CSS (+23 B) — — · — · —
DateRangeInput 69.9 kB (+31 B) · 11.9 kB CSS (+31 B) — — · — · —
Field 41.5 kB (+31 B) · 8.3 kB CSS (+31 B) — — · — · —
Popover 50.1 kB (+33 B) · 12.4 kB CSS (+29 B) — — · — · —
SideNavCollapseButton 46.2 kB (+32 B) · 9.8 kB CSS (+30 B) — — · — · —
Stepper 47.0 kB (+31 B) · 9.9 kB CSS (+30 B) — — · — · —
TableSelectionToolbar 52.3 kB (+31 B) · 11.5 kB CSS (+30 B) — — · — · —
BreadcrumbItem 67.3 kB (+32 B) · 11.5 kB CSS (+28 B) — — · — · —
FieldLabel 36.1 kB (+39 B) · 5.9 kB CSS (+21 B) — — · — · —
AlertDialog 56.5 kB (+23 B) · 12.8 kB CSS (+36 B) — — · — · —
InputGroup 42.1 kB (+27 B) · 8.4 kB CSS (+32 B) — — · — · —
Toast 59.5 kB (+30 B) · 10.1 kB CSS (+29 B) — — · — · —
AvatarGroup 37.7 kB (+36 B) · 6.6 kB CSS (+22 B) — — · — · —
HoverCard 18.6 kB (+39 B) · 5.0 kB CSS (+19 B) — — · — · —
Calendar 54.3 kB (+28 B) · 10.4 kB CSS (+29 B) — — · — · —
ChatLayoutScrollButton 45.9 kB (+27 B) · 9.9 kB CSS (+30 B) — — · — · —
Drawer 36.9 kB (+35 B) · 8.2 kB CSS (+22 B) — — · — · —
LayerProvider 62.8 kB (+29 B) · 10.5 kB CSS (+28 B) — — · — · —
StatusDot 23.2 kB (+38 B) · 6.3 kB CSS (+19 B) — — · — · —
InfoTip 37.8 kB (+35 B) · 6.2 kB CSS (+21 B) — — · — · —
Link 42.9 kB (+34 B) · 8.0 kB CSS (+22 B) — — · — · —
Avatar 35.1 kB (+30 B) · 6.5 kB CSS (+23 B) — — · — · —
Tooltip 21.0 kB (+32 B) · 5.0 kB CSS (+19 B) — — · — · —
ChatSendButton 45.2 kB (+19 B) · 9.5 kB CSS (+29 B) — — · — · —
lab/ChatEmojiPicker 65.2 kB (+45 B) · 773 B CSS (0) — — · — · —
lab/ChatReactionBar 65.7 kB (+42 B) · 1.1 kB CSS (0) — — · — · —
DropdownMenuSubMenu 56.0 kB (+22 B) · 9.9 kB CSS (+19 B) — — · — · —
lab/CommentPin 47.2 kB (+41 B) · 1.2 kB CSS (0) — — · — · —
lab/MobileTokenizer 101.2 kB (+41 B) · 880 B CSS (0) — — · — · —
CommandPalette 48.9 kB (0) · 12.2 kB CSS (+40 B) — — · — · —
lab/TransferList 74.4 kB (+40 B) · 1.7 kB CSS (0) — — · — · —
lab/DiffViewer 70.8 kB (+38 B) · 2.1 kB CSS (0) — — · — · —
lab/TransferListSelector 80.9 kB (+38 B) · 1.9 kB CSS (0) — — · — · —
richtext/RichTextEditorToolbar 222.3 kB (+37 B) · 454 B CSS (0) — — · — · —
lab/ListInput 81.6 kB (+36 B) · 1.6 kB CSS (0) — — · — · —
lab/MobilePowerSearch 214.0 kB (+36 B) · 1.2 kB CSS (0) — — · — · —
charts/ChartLegend 52.4 kB (+34 B) · 20 B CSS (0) — — · — · —
lab/EmojiPicker 70.3 kB (+34 B) · 722 B CSS (0) — — · — · —
richtext/RichTextEditor 277.8 kB (+34 B) · 2.6 kB CSS (0) — — · — · —
SegmentedControl 81.9 kB (+11 B) · 7.8 kB CSS (+22 B) — — · — · —
charts/Chart 62.9 kB (+32 B) · 20 B CSS (0) — — · — · —
charts/ChartTooltip 52.4 kB (+32 B) · 20 B CSS (0) — — · — · —
lab/TourStep 55.5 kB (+31 B) · 590 B CSS (0) — — · — · —
Timestamp 21.3 kB (0) · 9.9 kB CSS (+30 B) — — · — · —
lab/Schedule 61.4 kB (+30 B) · 3.9 kB CSS (0) — — · — · —
lab/CommentThread 227.3 kB (+26 B) · 2.4 kB CSS (0) — — · — · —
richtext/RichTextView 258.3 kB (+26 B) · 2.3 kB CSS (0) — — · — · —
lab/CommentComposer 102.5 kB (+24 B) · 1.9 kB CSS (0) — — · — · —
Timer 9.6 kB (+1 B) · 7.0 kB CSS (+22 B) — — · — · —
Spinner 18.5 kB (0) · 6.8 kB CSS (+21 B) — — · — · —
CommandPaletteInput 26.8 kB (+1 B) · 8.5 kB CSS (+19 B) — — · — · —
Heading 7.9 kB (−1 B) · 6.0 kB CSS (+21 B) — — · — · —
Text 7.4 kB (−1 B) · 6.0 kB CSS (+21 B) — — · — · —
ChatToolCalls 25.8 kB (0) · 9.3 kB CSS (+19 B) — — · — · —
lab/Comments 232.6 kB (+19 B) · 2.5 kB CSS (0) — — · — · —
ChatMessageList 22.8 kB (−2 B) · 7.9 kB CSS (+19 B) — — · — · —
ProgressBar 10.8 kB (+1 B) · 7.3 kB CSS (+1 B) — — · — · —

Package size: @astryxdesign/core 6.09 MB packed (+206 B); no new dependencies.

Wide change (shared styles: packages/core/src/Layer/layerAnimations.stylex.ts): add ci:run-visual, ci:run-a11y or ci:run-rtl to run them.


Generated by PR Enrichment workflow | View full report

@vjeux
vjeux marked this pull request as draft October 29, 2026 22:35
…accepts either easeInOut spelling

reducedMotionFadeConsts is on main now, so the layer's Reduce Motion fade
takes its duration and curve from it rather than repeating the literals.
CodeBunny on this PR: Chromium reports the curve as written, so the
contract accepts cubic-bezier(0.42, 0, 0.58, 1) as well as ease-in-out.
@cixzhang

cixzhang commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

/approve-spec 2f89fc0

@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 Reduce Motion fade works as described: under a finger, layers now fade in over 200 ms on easeInOut, opacity only, and under a mouse they still appear at once. Approval waits on one engineering-owner decision.

  • Owner decision needed: the published layerAnimations type changes, since each placement's animationTimingFunction widens from StyleXClassNameFor<"animationTimingFunction", "cubic-bezier(0.24, 1, 0.4, 1)"> to StyleXClassNameFor<"animationTimingFunction", string>, and no current record decides that API change, so an engineering owner confirms or rejects it with a comment on this pull request.
  • Optional: the comment above animationBase (layerAnimations.stylex.ts, lines 94-95) repeats what the styles and the exported JSDoc already say; keeping only the reason and the StyleX quirk is enough.

[Automated review]

Comment on lines +94 to +95
// Under Reduce Motion a coarse pointer gets the shared opacity-only fade,
// which no theme value lengthens, and any other pointer gets no animation.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Non-blocking: this sentence repeats what the styles below and the exported JSDoc already say. Keeping only the reason and the quirk is enough:

// No theme value lengthens the Reduce Motion fade, so its values are constants.
// The media keys stay literal: StyleX gives an imported at-rule key a later
// priority layer (see reducedMotionKeys).

…er fade comment

The fade's easing now comes from a StyleX constant, so each placement's
animationTimingFunction class type is StyleXClassNameFor<..., string>. The
core API report records that. The comment above animationBase keeps only
the reason and the StyleX quirk.
…ions keeps its published type

Specs: a widened published type needs the owner, and a patch must not
widen it. The Reduce Motion fade keeps main's animationTimingFunction (the
theme ease) on the class and sets reducedMotionFadeConsts.easing in the
fade keyframe's first frame, which overrides it for that interval. The
core API report is back to main's. The contract reads a curve set in the
first keyframe and leaves it out of the animated properties.
@cixzhang

cixzhang commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

/approve-spec b749028

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

No changes needed: with Reduce Motion under a finger, layers now fade in over a fixed 200 ms on easeInOut, opacity only, and under a mouse they still appear at once.

  • The Chromium contract passes on this head and fails on the base build only on the curve, so it pins the decided fade.
  • The published layerAnimations type is unchanged now that the curve sits in the fade's first keyframe.
  • Optional: an inline suggestion offers plainer wording for the new comment above fadeIn.

[Automated review]

Comment on lines +89 to +90
// The fade's curve rides in its first keyframe, so the published class type
// of `animationTimingFunction` stays the theme ease's.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Optional (non-blocking): this keeps a real reason, but "rides in" is an idiom and "the theme ease's" is hard to parse. A plainer form:

// The fade sets its curve in its first keyframe. This keeps the published
// class type of `animationTimingFunction` unchanged.

@cixzhang cixzhang added the ci:run A maintainer lets this outside contributor's pull request run CI (AST-030 FR17) label Nov 3, 2026
@cixzhang
cixzhang marked this pull request as ready for review November 3, 2026 10:15
@cixzhang
cixzhang merged commit e629e7d into meta:main Nov 3, 2026
149 checks passed

This branch was successfully deployed

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

Labels

ci:run A maintainer lets this outside contributor's pull request run CI (AST-030 FR17) CLA Signed This label is managed by the Meta Open Source bot. needs:design-review Affects visuals — Design should review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants