Skip to content

Conversation

@hectahertz
Copy link
Contributor

@hectahertz hectahertz commented Feb 11, 2026

Closes https://github.com/github/primer/issues/5788

Overview

This PR adds content-visibility: auto and contain-intrinsic-size: auto 32px to .ActionListItem in ActionList.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: auto enables 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 reads scrollHeight after DOM mutation — with content-visibility: auto, the browser no longer needs to fully lay out all the items to answer that query.

Browser support

content-visibility is 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

Run Baseline (original) With content-visibility: auto
1 3,072 ms 2,087 ms
2 3,254 ms 1,987 ms
Avg ~3,163 ms ~2,037 ms
Change -35.6%

Forced reflow breakdown (useScrollFlash)

Metric Baseline With content-visibility Change
useScrollFlash reflow 1,353 ms 143 ms -89.4%
Total forced reflow 1,592 ms 1,170 ms* -26.5%

* 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)

Phase Baseline With change
Input delay 2 ms 2 ms
Processing duration 2,786 ms 1,879 ms
Presentation delay 115 ms 106 ms

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 from useScrollFlash and React rendering time at this scale. The content-visibility approach was far more effective.

Changelog

New

Changed

  • Added content-visibility: auto and contain-intrinsic-size: auto 32px to .ActionListItem for improved rendering performance with large lists

Removed

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 and Reviewing

How to test:

  1. Start Storybook: npm start
  2. Open the SelectPanel Default story
  3. Modify the story to add more items (e.g., 500) to stress test
  4. Open Chrome DevTools Performance panel
  5. Enable 4x CPU throttling
  6. Record a trace while clicking the SelectPanel button to open
  7. Compare INP and forced reflow times with and without this change

What to verify:

  • ActionList items render correctly in all variants (inset, full, with dividers)
  • Scrolling behavior is smooth and items appear correctly when scrolled into view
  • useScrollFlash still functions (scrollbar flash on open)
  • No visual regressions in SelectPanel, ActionMenu, or standalone ActionList usage
  • Keyboard navigation (arrow keys, Home/End, Page Up/Down) works correctly
  • Items with descriptions, leading visuals, trailing actions all render properly

Merge checklist

… 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.
Copilot AI review requested due to automatic review settings February 11, 2026 16:37
@hectahertz hectahertz requested a review from a team as a code owner February 11, 2026 16:37
@hectahertz hectahertz requested a review from TylerJDev February 11, 2026 16:37
@changeset-bot
Copy link

changeset-bot bot commented Feb 11, 2026

🦋 Changeset detected

Latest commit: aff0c6a

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 integration-tests: recommended This change needs to be tested for breaking changes. See https://arc.net/l/quote/tdmpakpm label Feb 11, 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.

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

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: auto to .ActionListItem to skip rendering/layout/paint for off-screen items.
  • Added contain-intrinsic-size: auto 32px to provide a fallback intrinsic size for skipped items.

@github-actions github-actions bot requested a deployment to storybook-preview-7526 February 11, 2026 16:42 Abandoned
@github-actions github-actions bot temporarily deployed to storybook-preview-7526 February 11, 2026 16:51 Inactive
@hectahertz hectahertz added the update snapshots 🤖 Command that updates VRT snapshots on the pull request label Feb 11, 2026
@primer primer bot requested a review from a team as a code owner February 11, 2026 17:12
@github-actions github-actions bot removed the update snapshots 🤖 Command that updates VRT snapshots on the pull request label Feb 11, 2026
@github-actions github-actions bot temporarily deployed to storybook-preview-7526 February 11, 2026 17:22 Inactive
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.
@github-actions github-actions bot removed the update snapshots 🤖 Command that updates VRT snapshots on the pull request label Feb 12, 2026
@github-actions github-actions bot temporarily deployed to storybook-preview-7526 February 12, 2026 10:06 Inactive
@hectahertz hectahertz marked this pull request as ready for review February 12, 2026 10:09
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.
@hectahertz hectahertz added the update snapshots 🤖 Command that updates VRT snapshots on the pull request label Feb 12, 2026
@github-actions github-actions bot requested a deployment to storybook-preview-7526 February 12, 2026 12:43 Abandoned
@hectahertz hectahertz added update snapshots 🤖 Command that updates VRT snapshots on the pull request and removed update snapshots 🤖 Command that updates VRT snapshots on the pull request labels Feb 12, 2026
@github-actions github-actions bot temporarily deployed to storybook-preview-7526 February 12, 2026 12:52 Inactive
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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant