Skip to content

ActionList: respect prefers-reduced-motion for the multi-select checkmark - #8653

Open
adityalenova wants to merge 1 commit into
primer:mainfrom
adityalenova:fix/actionlist-checkmark-reduced-motion
Open

adityalenova wants to merge 1 commit into
primer:mainfrom
adityalenova:fix/actionlist-checkmark-reduced-motion

Conversation

@adityalenova

Copy link
Copy Markdown

What

Respect prefers-reduced-motion for the checkmark wipe on multi-select ActionList items. Closes #8648.

.MultiSelectCheckbox::before animated checkmarkOut unconditionally and the selected state animated checkmarkIn unconditionally, so the clip-path wipe ran for visitors who asked for reduced motion. The wipe is decorative here — whether the mark is shown is decided by visibility — so it belongs behind @media (prefers-reduced-motion: no-preference), the way Skeleton, ProgressBar, SegmentedControl, Breadcrumbs and TooltipV2 already do it. This is the same change proposed for Checkbox in #8647, applied to the copy of that pattern that lives in ActionList.

How

&::before {
  /* an unchecked item holds the mark clipped either way */
  clip-path: inset(var(--base-size-16) 0 0 0);

  @media (prefers-reduced-motion: no-preference) {
    transition: visibility 0s linear 230ms;
    animation: checkmarkOut 80ms cubic-bezier(0.65, 0, 0.35, 1);
  }
}

/* selected */
&::before {
  visibility: visible;
  clip-path: inset(0 0 0 0);

  @media (prefers-reduced-motion: no-preference) {
    transition: visibility 0s linear 0s;
    clip-path: inset(var(--base-size-16) 0 0 0);
    animation: checkmarkIn 80ms cubic-bezier(0.65, 0, 0.35, 1) forwards 80ms;
  }
}

Two details that keep the animated result byte-for-byte the same:

  • the selected state re-declares the clipped clip-path inside the media query, because checkmarkIn uses fill-mode: forwards with an 80 ms delay and without backwards fill — without that, the mark would flash un-clipped before the wipe starts;
  • transition: visibility 0s linear 230ms moves with the animation, so reduced motion hides the mark immediately instead of holding it visible for 230 ms.

The default (inset(0 0 0 0)) is the value the animation lands on, so with motion reduced the mark is simply present, and there is no state where a selected item shows no mark.

Verification

The multi-select checkbox only renders when the list is not role="menu" — Selection falls back to the single-select checkmark for menus — so the new e2e tests use the FilteredActionList > MultiSelectWithSelectAll story, which is a role="listbox" list with selectionVariant="multiple". toHaveCSS cannot target ::before, so the tests read the computed style of the pseudo-element.

$ npx playwright test e2e/components/ActionList.test.ts -g "checkmark"
  2 passed (3.6s)
  • reducedMotion: 'reduce' → animation-name: none, clip-path: inset(0px), visibility: visible; clicking the item off gives visibility: hidden and transition-delay: 0s.
  • reducedMotion: 'no-preference' → animation-name still resolves to checkmarkIn, and after unchecking, checkmarkOut with transition-delay: 0.23s.

npx stylelint packages/react/src/ActionList/ActionList.module.css reports no new problems (the one warning it prints is the pre-existing -webkit-tap-highlight-color one further down the file), and npx prettier --check passes on all three files.

A changeset is included (patch).

Notes

No ActionList story renders .MultiSelectCheckbox with role="menu", so if you want a dedicated story for this state I can add one to ActionList.features.stories.tsx instead — say the word.

…mark

The clip-path wipe on the multi-select checkbox is decorative - the mark is
shown and hidden through `visibility` - so move the animation and the 230ms
hide delay behind @media (prefers-reduced-motion: no-preference), the same way
Skeleton, ProgressBar and SegmentedControl opt in to motion. With motion
reduced the mark is simply present when the item is selected.

The animated path keeps every declaration it uses today, including the clipped
start value that covers the 80ms delay on checkmarkIn, so the rendered end
state is unchanged and the existing baselines stay valid.
@adityalenova
adityalenova requested a review from a team as a code owner October 31, 2026 12:02
@changeset-bot

changeset-bot Bot commented Oct 31, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 2884b42

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@primer/react Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@adityalenova
adityalenova requested review from jonrohan and a balanced review from Copilot October 31, 2026 12:02

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

This branch was successfully deployed

1 active deployment
github-pages — 2884b429 Deployed Oct 31, 2026 by adityalenova via deploy-preview #30290
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ActionList: multiselect checkmark animation ignores prefers-reduced-motion

2 participants