diff --git a/.changeset/list-row-press-compositor.md b/.changeset/list-row-press-compositor.md index 75ad1a8430311..3d4b3c619ddcb 100644 --- a/.changeset/list-row-press-compositor.md +++ b/.changeset/list-row-press-compositor.md @@ -2,6 +2,6 @@ '@astryxdesign/core': patch --- -[fix] A list row's touch press release keeps fading while the page runs script, once the release has started. `ListItem`, `Item` and `TreeList` rows used to freeze mid-fade until the main thread was free, and every frame of the fade repainted the row; the release now runs on the compositor, with the same color, clock and curve as before. Script that runs in the tap's own handlers, before the next frame, still holds the row pressed until it ends. Under a finger, while a row is pressed and through its release, the press paints on the row's `::before` and the row is its own stacking context; at rest nothing of it applies, so a row's own `::before` decoration and its children's stacking are as they were. During a press the layer is the row's `::before`, and any `::before` property a caller sets, from `xstyle` (which `Item` composes after the layer) or from a stylesheet, wins over the layer's: a decoration with its own size and background jumps to the row's corner, and the row shows no pressed fill until the release ends. Decoration that should stay through a press belongs on `::after` or a child element. With a mouse or trackpad the row is unchanged: no layer and no new stacking context. +[fix] A list row's touch press release keeps fading while the page runs script, once the release has started. `ListItem`, `Item` and `TreeList` rows used to freeze mid-fade until the main thread was free, and every frame of the fade repainted the row; the release now runs on the compositor, with the same color as before. Script that runs in the tap's own handlers, before the next frame, still holds the row pressed until it ends. Under a finger, while a row is pressed and through its release, the press paints on the row's `::before` and the row is its own stacking context; at rest nothing of it applies, so a row's own `::before` decoration and its children's stacking are as they were. During a press the layer is the row's `::before`, and any `::before` property a caller sets, from `xstyle` (which `Item` composes after the layer) or from a stylesheet, wins over the layer's: a decoration with its own size and background jumps to the row's corner, and the row shows no pressed fill until the release ends. Decoration that should stay through a press belongs on `::after` or a child element. With a mouse or trackpad the row is unchanged: no layer and no new stacking context. @vjeux diff --git a/.changeset/press-release-exit-tokens.md b/.changeset/press-release-exit-tokens.md new file mode 100644 index 0000000000000..475fa26a0ea6b --- /dev/null +++ b/.changeset/press-release-exit-tokens.md @@ -0,0 +1,7 @@ +--- +'@astryxdesign/core': patch +--- + +[fix] A press's release now fades on the theme's exit motion tokens, `--duration-exit` and `--ease-exit`, instead of 200 ms, scaled by the theme's `tempo`, on a curve of its own. This covers every release that fades: a finger, mouse, pen or key press on a phone or tablet, and a pen press on a desktop, on buttons, cards, switches, checkboxes, list and tree rows, tabs, menu items and action groups. With the default tokens the release is 175 ms and holds its colour a little longer before it goes. A theme that changes `--duration-exit` or `--ease-exit` now changes the release too; a theme's `tempo` still does, through `--duration-exit`. Under Reduce Motion a finger's release still fades for no more than 200 ms. A mouse press on a desktop is unchanged. + +@cixzhang diff --git a/.changeset/reduced-motion-button-cards.md b/.changeset/reduced-motion-button-cards.md new file mode 100644 index 0000000000000..f37dd9ea46698 --- /dev/null +++ b/.changeset/reduced-motion-button-cards.md @@ -0,0 +1,7 @@ +--- +'@astryxdesign/core': patch +--- + +[fix] Under reduced motion, a pressed Button no longer shrinks, and ClickableCard, SelectableCard and Thumbnail change their hover, border and selection colors at once on every pointer, whatever the theme's duration. A finger's press highlight still fades out on release, within 200 ms. + +@vjeux diff --git a/docs/design/reduced-motion.md b/docs/design/reduced-motion.md index 31995ac89528e..449334d4dbbb8 100644 --- a/docs/design/reduced-motion.md +++ b/docs/design/reduced-motion.md @@ -10,7 +10,11 @@ approved_by: cixzhang approved_at: 2026-10-24 owners: [cixzhang] review_triggers: [motion, interaction, accessibility] -verified_by: [scripts/check-knowledge.mjs] +verified_by: + [ + scripts/check-knowledge.mjs, + packages/core/src/__tests__/ReducedMotionPress.a11y.chromium.spec.ts, + ] architecture: [architecture:interaction-modality] components: [] families: [] @@ -32,8 +36,8 @@ deciding_specs: [] A person who asks their device to reduce motion still sees every change Astryx shows, with no travel, scaling, or springing. On a touch screen a change cross-fades, as iOS does with Reduce Motion and Prefer Cross-Fade, except an -in-place value change, which applies at once; with a mouse, -trackpad, or keyboard the change is immediate. +in-place value change and a hover or selection color, which apply at once; +with a mouse, trackpad, or keyboard the change is immediate. ## Design principles @@ -56,7 +60,7 @@ trackpad, or keyboard the change is immediate. | State | Required representation | Allowed variation | | ------------------------------------------------- | ----------------------------------------------------------------------------------------------- | -------------------------- | -| reduced motion, coarse pointer | Opacity-only cross-fade of 200 ms or less (DR2); an in-place value change applies at once (DR2) | A shorter cross-fade | +| reduced motion, coarse pointer | Opacity-only cross-fade of 200 ms or less (DR2); in-place values and colors apply at once (DR2) | A shorter cross-fade | | reduced motion, any other pointer | Immediate change (DR3), except a touch drag's release (DR2) | None | | no reduced-motion preference, theme on `'system'` | Outside this record | Component and theme motion | | a theme on `'always'` (DR2a) | As reduced motion, whatever the person's setting | DR1–DR3 | @@ -75,7 +79,13 @@ trackpad, or keyboard the change is immediate. coast Astryx animates itself after a throw behaves like native momentum under Reduce Motion. An in-place value change, such as a progress fill's length or another continuous value, does not cross-fade: under reduced motion it - applies at once on any pointer, with no added element (DEC-3). + applies at once on any pointer, with no added element (DEC-3). A control's + hover or selection color, such as SelectableCard's selection border and + ring, also applies at once on any pointer, as on iOS (DEC-4). So does the + onset of its press color. A color that changes with a part that cross-fades + follows that cross-fade, such as Switch's track with its thumb + (`component:Switch` FR15). A touch press's release keeps its fade of 200 ms + or less. - **DR2a — A theme may reduce motion for everyone.** A theme's `reducedMotion: 'always'` applies DR1–DR3 for every person, whatever their setting; `'system'` follows each person's setting (`spec:AST-108`). No theme @@ -131,6 +141,7 @@ No normative assets are included. | DR1, DR2 | Real-browser computed animations under reduced motion, coarse pointer | Layer entrance and exit (Tooltip, Popover, menu); Dialog; modal BottomSheet; a presentation switch | An animated transform, size, or position; a spring; a property other than opacity; over 200 ms; a change with no cross-fade | | DR2 momentum | Real-browser computed animations under reduced motion after a touch throw | A flick that coasts and settles at an edge; a release that snaps to a detent, dismisses, or pages to a target | A coast replaced by a cross-fade or stopped at release; a snap, dismissal, or page that travels instead of cross-fading | | DR2 values | Real-browser computed animations under reduced motion, coarse and fine pointer | A determinate ProgressBar's fill and another continuous value changing in place | A value that animates, cross-fades, or adds an element under reduced motion fails | +| DR2 colors | Real-browser computed animations under reduced motion, coarse and fine pointer | Button's press onset; ClickableCard's, SelectableCard's and Thumbnail's hover, border and selection colors | A hover or selection color, or a press onset, that animates or cross-fades; a touch release fade over 200 ms | | DR1, DR3 | Real-browser computed animations under reduced motion, fine pointer | The same components | Any animated property, or a frame where the change is partly applied | | DR4 | Component prop and theme review plus the browser checks above | A component with a motion prop or theme motion value set | A prop or theme value that restores travel or a longer animation under reduced motion | @@ -174,6 +185,21 @@ Under reduced motion they take the new value at once on any pointer. Rejected: a cross-fade between the old and new value on a touch screen. +### DEC-4 — A color-only change of state applies at once + +**Reference:** `design:reduced-motion/DEC-4` +**Decider:** `cixzhang`, `2026-11-03`; companion clause per the owner's motion rule (2026-11-04) + +A control's hover and selection colors, and the onset of its press color, +change where they are and move nothing, so reduced motion has nothing to +remove from them. Under reduced motion they take the new color at once on any +pointer, as iOS does and as an in-place value does (DEC-3). A color that +changes with a part that cross-fades, such as Switch's track with its thumb, +follows that cross-fade, so the two never show different states. A touch +press's release keeps its fade of 200 ms or less, which confirms the tap. + +Rejected: a cross-fade of a hover or selection color on a touch screen. + ## Open questions None. diff --git a/docs/specs/AST-094-touch-presentations/spec.md b/docs/specs/AST-094-touch-presentations/spec.md index f01a260ee831e..0d65f89e670ab 100644 --- a/docs/specs/AST-094-touch-presentations/spec.md +++ b/docs/specs/AST-094-touch-presentations/spec.md @@ -229,6 +229,10 @@ a measured, sourced difference exists. and MUST keep advancing at display rate through a main-thread block. It MUST NOT animate layout properties, and MUST NOT step a frame from script when the compositor can run the curve. + A press shows at once. Every press release that fades does so on the + theme's exit motion tokens (`--duration-exit`, `--ease-exit`), on every + device and pointer, by the owner's decision (2026-11-03); under Reduce + Motion, `design:reduced-motion` DR2 applies. - **FR6 — Motion is interruptible.** A finger MUST be able to catch any animation mid-flight at the value on screen. That value and its velocity MUST come from the curve, not from a layout read. A new target MUST keep the diff --git a/docs/specs/AST-108-theme-motion/spec.md b/docs/specs/AST-108-theme-motion/spec.md index cffa239ea7f82..377cecdc12f03 100644 --- a/docs/specs/AST-108-theme-motion/spec.md +++ b/docs/specs/AST-108-theme-motion/spec.md @@ -173,7 +173,7 @@ defineTheme({ `spec:AST-012` FR4 (`reducedMotion` stays root-owned), `architecture:theme-application` INV1 (`'always'` reaches nested providers), `architecture:theme-authoring-contract` (the internal motion resolver), - `component:Button` FR15 (its release fade follows `tempo`), the draft `design:motion` DR3, and `spec:AST-090` are amended to + `component:Button` FR15 (its release fade follows `tempo` through `--duration-exit`, which references a tempo-scaled duration), the draft `design:motion` DR3, and `spec:AST-090` are amended to match. ## Verification diff --git a/docs/specs/components/Button/Button.spec.md b/docs/specs/components/Button/Button.spec.md index abd2f106d4776..3c84ce9bd2c60 100644 --- a/docs/specs/components/Button/Button.spec.md +++ b/docs/specs/components/Button/Button.spec.md @@ -16,6 +16,7 @@ verified_by: packages/core/src/ButtonGroup/ButtonGroup.test.tsx, packages/core/src/Button/__tests__/Button.a11y.test.tsx, packages/core/src/Button/__tests__/Button.a11y.chromium.spec.ts, + packages/core/src/__tests__/ReducedMotionPress.a11y.chromium.spec.ts, packages/core/src/Layout/__tests__/edgeCompensation.test.tsx, packages/core/src/utils/pressFeedback.test.ts, ] @@ -37,15 +38,15 @@ system_specs: [spec:AST-005, spec:AST-085, spec:AST-086] ## Contract at a glance -| Area | Contract | -| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Public contract | None changes. This record states the shipped Button surface: `label`, variant, size, elevation, content mode, loading and `clickAction`, `isInterruptible`, disabled, tooltip, width, form attributes, and link mode. | -| Behavior | A named native `