Skip to content

perf(ActionList): replace :has() selectors with data attributes - #7894

Merged
primer[bot] merged 3 commits into
mainfrom
perf/actionlist-has-selectors
May 29, 2026
Merged

perf(ActionList): replace :has() selectors with data attributes#7894
primer[bot] merged 3 commits into
mainfrom
perf/actionlist-has-selectors

Conversation

@mattcosta7

Copy link
Copy Markdown
Contributor

Closes #

Replaces four :has(...) selectors in ActionList with JS-derived data attributes computed from props/slots at render time. Same DOM output otherwise; reduces the per-Item style-recalculation cost.

ActionList items render many times per page in real GitHub views (navigation, dropdowns, issue lists), so each :has() selector evaluates many times on every DOM mutation inside the list. Hoisting the state to data attributes turns those into constant-time attribute matches.

Replacements:

WhereBeforeAfter
ActionListItem&:has(> .TrailingAction)&[data-has-trailing-action]
ActionListItem&:has(.TrailingAction [data-loading='true']):not([data-is-disabled])&[data-trailing-action-loading]:not([data-is-disabled])
InactiveButtonWrap&:has(.TrailingVisual) / &:has(.LeadingVisual)&[data-position='trailing'] / &[data-position='leading']
TrailingActionButton&[data-loading='true']:has([data-component='buttonContent'])&[data-loading='true'][data-has-label='true']

The new attributes are derived from state the components already track:

  • data-has-trailing-actionslots.trailingAction presence
  • data-trailing-action-loadingslots.trailingAction?.props.loading
  • data-position ← the existing position prop on VisualOrIndicator
  • data-has-label ← the text-Button branch of TrailingAction (vs the IconButton branch)

Changelog

New

  • data-has-trailing-action, data-trailing-action-loading on ActionList.Item
  • data-position on the inactive-state visual wrapper
  • data-has-label on the text-Button variant of ActionList.TrailingAction

Changed

  • Internal: ActionList styling no longer relies on :has() for the cases above

Removed

  • Four :has(...) selectors from ActionList.module.css

Rollout strategy

  • Patch release
  • Minor release
  • Major release; if selected, include a written rollout or migration plan
  • None; if selected, include a brief description as to why

Testing & Reviewing

No visual changes expected. Each new data attribute is set exactly when the equivalent :has() selector previously matched.

Existing ActionList unit tests pass locally (138 tests across the Button + ActionList suites).

Companion to #7893 (same pattern applied to Button). Independent — either can land first.

Merge checklist

Hoists state that was being detected via :has() to JS-derived data attributes on the rendering components. Same DOM output otherwise; reduces style-recalculation cost on lists that render many items.
Changes: Item now emits data-has-trailing-action and data-trailing-action-loading from its slot props; InactiveButtonWrap emits data-position from its existing position prop; the text-Button variant of TrailingActionButton emits data-has-label. Four :has() selectors removed from ActionList.module.css.
CopilotAI review requested due to automatic review settings May 28, 2026 15:18
@mattcosta7
mattcosta7 requested a review from a team as a code ownerMay 28, 2026 15:18
@changeset-bot

changeset-botBot commented May 28, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 1e8f2f0

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

This PR includes changesets to release 1 package
NameType
@primer/reactPatch

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

@github-actionsgithub-actionsBot added the integration-tests: recommended This change needs to be tested for breaking changes. See https://arc.net/l/quote/tdmpakpm label May 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Action required

👋 Hi, this pull request contains changes to the source code that github/github-ui depends on. If you are GitHub staff, test these changes with github/github-ui using the integration workflow. Check the integration testing docs for step-by-step instructions. Or, apply the integration-tests: skipped manually label to skip these checks.

To publish a canary release for integration testing, apply the Canary Release label to this PR.

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

Pull request overview

Replaces four :has(...) selectors in ActionList's CSS with JS-derived data attributes computed from props/slots at render time, to reduce per-item style-recalculation cost (especially impactful on Safari and large lists). This follows the same pattern recently applied to Button in #7893 and to ActionList.List's data-mixed-descriptions.

Changes:

  • Add data-has-trailing-action and data-trailing-action-loading on ActionList.Item, and replace the corresponding :has() selectors in ActionList.module.css.
  • Add data-position on InactiveButtonWrap (from the existing position prop on VisualOrIndicator) and data-has-label="true" on the text-Button branch of TrailingAction, replacing the related :has() selectors.
  • Add a patch changeset documenting the perf-only internal change.
Show a summary per file
FileDescription
packages/react/src/ActionList/Item.tsxAdds data-has-trailing-action and data-trailing-action-loading to the <li> root, derived from slots.trailingAction.
packages/react/src/ActionList/Visuals.tsxAdds data-position to InactiveButtonWrap so CSS can target leading vs trailing without :has().
packages/react/src/ActionList/TrailingAction.tsxAdds data-has-label="true" on the text-Button branch so CSS can target it without :has([data-component='buttonContent']).
packages/react/src/ActionList/ActionList.module.cssReplaces four :has(...) selectors (and their stylelint-disable comments) with attribute selectors on the new data attributes.
.changeset/perf-actionlist-has-selectors.mdPatch changeset describing the internal perf change.

Copilot's findings

  • Files reviewed: 5/5 changed files
  • Comments generated: 1

Comment threadpackages/react/src/ActionList/Item.tsx Outdated
Asserts the data attributes the CSS now depends on: data-has-trailing-action and data-trailing-action-loading on Item (from the TrailingAction slot), data-position on the inactive-state visual wrap (leading vs trailing), and data-has-label on the text Button variant of TrailingActionButton (vs the IconButton variant).
@github-actions
github-actionsBottemporarily deployed to storybook-preview-7894 May 28, 2026 15:30 Inactive
…tion
Addresses #7894 review feedback. The previous :has(> .TrailingAction) and :has(.TrailingAction [data-loading='true']) selectors only matched when the TrailingAction was actually rendered in the DOM, which is gated by !inactive && !loading && !menuContext. The slot-presence checks I introduced were broader, so an inactive/loading item or one inside an ActionMenu/SelectPanel/FilteredActionList that was passed a TrailingAction would now get display: flex and the loading label override even though no action element was rendered.
Mirror the render gate on the data attributes so they're set only when the action actually renders. Adds tests for the inactive and loading branches; the menuContext branch can't be exercised in dev tests due to the existing invariant but is documented.
@jonrohanjonrohan added the Canary Release Apply this label when you want CI to create a canary release of the current PR label May 28, 2026
@jonrohan
jonrohan enabled auto-merge May 28, 2026 18:57
@primer-integration

Copy link
Copy Markdown

👋 Hi from github/github-ui! Your integration PR is ready: https://github.com/github/github-ui/pull/21833

@primer-integration

Copy link
Copy Markdown

Integration test results from github/github-ui:

Passed CI  Passed
Passed VRT  Passed
Passed Projects  Passed

All checks passed!

@primer
primerBot disabled auto-merge May 29, 2026 20:11
@primer
primerBot added this pull request to the merge queueMay 29, 2026
Merged via the queue into main with commit af4541dMay 29, 2026
60 checks passed
@primer
primerBot deleted the perf/actionlist-has-selectors branch May 29, 2026 20:21
@primerprimerBot mentioned this pull request May 29, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Canary ReleaseApply this label when you want CI to create a canary release of the current PRintegration-tests: recommendedThis change needs to be tested for breaking changes. See https://arc.net/l/quote/tdmpakpm

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@mattcosta7@jonrohan