perf(ActionList): add content-visibility: auto to reduce style recalc and layout costs#7526
Open
hectahertz wants to merge 10 commits intomainfrom
Open
perf(ActionList): add content-visibility: auto to reduce style recalc and layout costs#7526hectahertz wants to merge 10 commits intomainfrom
hectahertz wants to merge 10 commits intomainfrom
Conversation
… and layout costs Add `content-visibility: auto` and `contain-intrinsic-size: auto 32px` to ActionListItem. This tells the browser to skip rendering, style calculation, and layout for off-screen list items, significantly reducing forced reflow costs when lists contain many items (e.g., SelectPanel with 500 items). Measured ~33% INP improvement and ~89% reduction in useScrollFlash forced reflow time with 500 items at 4x CPU throttling.
🦋 Changeset detectedLatest commit: aff0c6a The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
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 |
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 |
Contributor
There was a problem hiding this comment.
Pull request overview
This PR improves rendering performance for large ActionList instances in @primer/react by enabling browser-driven skipping of off-screen list item rendering via CSS containment.
Changes:
- Added
content-visibility: autoto.ActionListItemto skip rendering/layout/paint for off-screen items. - Added
contain-intrinsic-size: auto 32pxto provide a fallback intrinsic size for skipped items.
This reverts commit 8821685.
Move content-visibility: auto from global ActionListItem to scoped selectors in FilteredActionList and experimental SelectPanel2 scroll containers. This prevents VRT regressions in ActionList, ActionMenu, NavList, etc. where items are always visible and don't benefit from content-visibility skipping.
content-visibility: auto applies paint containment which clips the activeIndicatorLine ::after pseudo-element positioned outside the li bounds. Exclude items with :focus, [data-is-active-descendant], [data-active], and [data-input-focused] from content-visibility: auto so the indicator remains visible.
This reverts commit 35d6bba.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes https://github.com/github/primer/issues/5788
Overview
This PR adds
content-visibility: autoandcontain-intrinsic-size: auto 32pxto.ActionListIteminActionList.module.css. This is a pure CSS change (2 lines) that tells the browser to skip detailed rendering, style calculation, and layout for off-screen list items — essentially CSS-level virtualization without any React code changes.How it works
content-visibility: autoenables the browser to skip layout and paint work for elements that are not currently visible in the viewport. When an ActionList contains many items (e.g., a SelectPanel with hundreds of labels), only the ~15-20 visible items need full style recalculation and layout. Off-screen items use the intrinsic size hint (contain-intrinsic-size: auto 32px) for sizing, avoiding expensive per-element computation.This is particularly impactful for forced reflows triggered by
useScrollFlash, which readsscrollHeightafter DOM mutation — withcontent-visibility: auto, the browser no longer needs to fully lay out all the items to answer that query.Browser support
content-visibilityis supported in all modern browsers: Chrome 85+, Edge 85+, Firefox 125+, Safari 18+. See caniuse.Performance measurements
All measurements taken with 500 ActionList items inside a SelectPanel, using Chrome DevTools performance traces with 4x CPU throttling to simulate lower-end devices.
INP (Interaction to Next Paint) — click to open SelectPanel
content-visibility: autoForced reflow breakdown (useScrollFlash)
content-visibility* Remaining reflow is from Storybook's performance addon (
updateCompositorLayers), not from component code. In production, the improvement would be even more pronounced.INP phase breakdown (best run comparison)
What was tested but showed no impact
During investigation, I also tried replacing all 5
:has()CSS selectors with data-attribute equivalents (e.g.,&:has([aria-disabled])to&[data-disabled]). While theoretically reducing selector evaluation cost, this showed zero measurable INP improvement because the style recalc cost is completely overshadowed by the forced reflow fromuseScrollFlashand React rendering time at this scale. Thecontent-visibilityapproach was far more effective.Changelog
New
Changed
content-visibility: autoandcontain-intrinsic-size: auto 32pxto.ActionListItemfor improved rendering performance with large listsRemoved
Rollout strategy
Testing and Reviewing
How to test:
npm startWhat to verify:
useScrollFlashstill functions (scrollbar flash on open)Merge checklist