Skip to content

Conversation

@jonrohan
Copy link
Member

@jonrohan jonrohan commented Jan 22, 2026

Part of https://github.com/github/pull-requests/issues/22213

Summary

Optimizes CSS selector performance in ActionList by replacing expensive universal selectors (*) and broad :has() queries with targeted class selectors. :is(.ActionListItem:not(:has([aria-disabled], [disabled]), [data-has-subitem="true"]):where([data-loading="true"]), .ActionListItem:not(:has([aria-disabled], [disabled]), [data-has-subitem="true"]):has([data-loading="true"])) *

Problem

During LCP (Largest Contentful Paint) analysis, we identified a selector in ActionList that was attempting to match ~200k elements but matching none:

The issues were:

  1. Universal selector (& *) - Forces the browser to check every descendant element
  2. Broad :has([data-loading='true']) - Scans all descendants looking for the data-loading attribute

Solution

  1. Replaced & * with explicit class targets:

    • .ItemLabel
    • .Description
    • .LeadingVisual
    • .TrailingVisual
    • .VisualWrap
  2. Removed :has([data-loading='true']) entirely - After investigation, data-loading is set directly on the <li> element via Item.tsx, so the :has() variant was unnecessary. The separate rule at line 369 already handles TrailingAction-specific loading states.

Related Issue(s)

Before/After

Aspect Before After
Loading state selector &:where([data-loading='true']), &:has([data-loading='true']) { & * { ... } } &:where([data-loading='true']), & [data-loading='true'] { & .ItemLabel, & .Description, ... { ... } }
Elements checked ~200k (all descendants) Only elements with specific classes
:has() scope All descendants Direct child with data-loading attribute

Testing & Verification

  • Build passes (npm run build)
  • Unit tests pass (npm test - 505 tests)
  • CSS linting passes (npx stylelint)
  • Verified loading state styling in Storybook
  • Tested with ActionList items using loading prop
  • Tested with TrailingAction containing loading buttons

Merge checklist

  • Added/updated tests
  • Added/updated documentation
  • Tested in all supported browsers
  • Reviewed for accessibility impact (no changes to accessibility behavior)

Screenshots

N/A - This is a performance optimization with no visual changes.

@changeset-bot
Copy link

changeset-bot bot commented Jan 22, 2026

🦋 Changeset detected

Latest commit: 79b2187

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

@github-actions github-actions bot added the staff Author is a staff member label Jan 22, 2026
@github-actions
Copy link
Contributor

👋 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. Or, apply the integration-tests: skipped manually label to skip these checks.

@github-actions github-actions bot added the integration-tests: recommended This change needs to be tested for breaking changes. See https://arc.net/l/quote/tdmpakpm label Jan 22, 2026
@github-actions github-actions bot temporarily deployed to storybook-preview-7468 January 22, 2026 22:57 Inactive
@@ -278,7 +278,11 @@

&:where([data-loading='true']),
&:has([data-loading='true']) {
Copy link
Contributor

Choose a reason for hiding this comment

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

is the [data-loading] really at any level of depth, or is it possibly on the ListItem wrappers directly? that would probably be another nice win

Copy link
Member Author

Choose a reason for hiding this comment

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

Theres is a .TrailingAction data-loading that can happen, but we can make the has more specific here to avoid scanning all the descendants.

@github-actions github-actions bot requested a deployment to storybook-preview-7468 January 23, 2026 18:22 Abandoned
@github-actions github-actions bot temporarily deployed to storybook-preview-7468 January 23, 2026 18:34 Inactive
Optimizes CSS selector performance in ActionList by replacing expensive universal selectors (`*`) and broad `:has()` queries with targeted class selectors.
@jonrohan jonrohan marked this pull request as ready for review January 23, 2026 21:03
@jonrohan jonrohan requested a review from a team as a code owner January 23, 2026 21:03
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Improves ActionList loading-state CSS performance by replacing expensive universal/:has() selectors with more targeted class-based selectors.

Changes:

  • Replaced & * loading-state styling with a curated set of ActionList child classes.
  • Removed the broad :has([data-loading='true']) loading selector path.
  • Added a changeset documenting the performance optimization.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
packages/react/src/ActionList/ActionList.module.css Updates loading-state selectors to reduce matching work and avoid broad descendant scans.
.changeset/orange-walls-buy.md Publishes the change as a patch with a note about selector performance optimization.

Comment on lines 279 to +280
&:where([data-loading='true']),
&:has([data-loading='true']) {
& * {
& > [data-loading='true'] {
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

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

& > [data-loading='true'] likely never matches with the current ActionList.Item markup. data-loading is set on the <li> itself (Item.tsx:210), and when TrailingAction is loading the attribute is on the nested button inside .TrailingAction, not on a direct child of the <li>. Consider removing this selector (keeping only &:where([data-loading='true'])) to avoid dead/incorrect CSS, or update it to match an actual DOM structure that needs support.

See below for a potential fix:

    &:where([data-loading='true']) {

Copilot uses AI. Check for mistakes.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@primer-integration
Copy link

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

@primer-integration
Copy link

Integration test results from github/github-ui:

Passed  CI   Passed
Passed  VRT   Passed
Passed  Projects   Passed

All checks passed!

Copy link
Member

@siddharthkp siddharthkp left a comment

Choose a reason for hiding this comment

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

Nice!

Is this something we can add to our stylelint-config or is it more nuanced?

@jonrohan jonrohan added this pull request to the merge queue Jan 26, 2026
Merged via the queue into main with commit 731fb71 Jan 26, 2026
52 checks passed
@jonrohan jonrohan deleted the remove_expensive_star_query_actionlist branch January 26, 2026 17:38
@primer primer bot mentioned this pull request Jan 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

integration-tests: recommended This change needs to be tested for breaking changes. See https://arc.net/l/quote/tdmpakpm staff Author is a staff member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants