Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .changeset/list-row-press-compositor.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
7 changes: 7 additions & 0 deletions .changeset/press-release-exit-tokens.md
Original file line number Diff line number Diff line change
@@ -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
7 changes: 7 additions & 0 deletions .changeset/reduced-motion-button-cards.md
Original file line number Diff line number Diff line change
@@ -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
36 changes: 31 additions & 5 deletions docs/design/reduced-motion.md
Original file line number Diff line number Diff line change
Expand Up @@ -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: []
Expand All @@ -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

Expand All @@ -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 |
Expand All @@ -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
Comment on lines +82 to +84

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 makes every selection colour apply at once under reduced motion, but component:Switch FR9 and FR15 still cross-fade the switch's track colour with its thumb under a finger, and Switch ships that fade. Either scope it here and in DEC-4 (for example, "…except a colour that changes with a part that cross-fades, such as Switch's track with its thumb (component:Switch FR15)") or amend Switch FR9 and FR15.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in #8326 (c377f91), merged here in 29b854e: DR2 and DEC-4 now say 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).

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
Expand Down Expand Up @@ -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 |

Expand Down Expand Up @@ -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.
Expand Down
4 changes: 4 additions & 0 deletions docs/specs/AST-094-touch-presentations/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion docs/specs/AST-108-theme-motion/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading