Skip to content

Fix and refactor UnderlineNav to resolve CLS issues and improve performance/code quality#7506

Open
iansan5653 wants to merge 98 commits into
mainfrom
underline-nav-full-css-spike
Open

Fix and refactor UnderlineNav to resolve CLS issues and improve performance/code quality#7506
iansan5653 wants to merge 98 commits into
mainfrom
underline-nav-full-css-spike

Conversation

@iansan5653

@iansan5653 iansan5653 commented Feb 6, 2026

Copy link
Copy Markdown
Contributor

This refact/bugfix PR reworks UnderlineNav's internal logic to rely mostly on CSS for calculating overflow, eliminating layout shift issues. In addition, it removes the use of the React.Children API in favor of context-based solutions, and replaces the custom overflow menu with an ActionMenu.

See https://github.com/github/primer/issues/6291#issuecomment-3861950186 for context.

The CSS overflow logic works like this:

  1. Overflowing menu items wrap onto subsequent lines, which are clipped by their container using overflow: hidden, creating the illusion that they disappeared
  2. The initial visibility of the overflow menu button and tab icons is controlled by scroll-state container queries to detect overflow using pure CSS
  3. Pure CSS cannot, however, tell us what to render inside the overflow menu. For this, each list item registers itself using context:
    1. The parent component provides register/unregister functions through context
    2. Each item component registers an IntersectionObserver to update itself with the parent when it appears/disappears. They determine whether they are overflowed by checking ref.current.offsetHeight (which will be greater than zero for an item that has wrapped). If they are overflowed, they set aria-hidden/tabIndex="-1" and notify the parent via the registry.
    3. The parent renders menu items for all currently-overflowing items in the overflow menu
    4. If any item has ever overflowed, all tab icons are suppressed via React state for the rest of the lifecycle of the component (this prevents flickering caused by the tab icons creating/removing space)

The benefits are pretty significant:

  1. The browser will calculate overflow for us before we ever even paint on the page - no waiting on a JS bundle or React hydration (this benefit is shared with the lighter-weight approach)
  2. Calculations are nearly pure CSS, so performance is buttery smooth for the whole component lifecycle
  3. We get to clean up tons of complex code, remove several effects, and remove hacky Children APIs entirely
  4. Bonus: Component consumption is much less fragile. Without Children APIs, we no longer need direct children, so consumers can wrap their underline items in fragments and even wrapper components

However, there are some caveats / user-facing changes:

  1. The most significant is that we can no longer ensure that the current item will not overflow into the menu. This is impossible to implement with the new approach; even if we were to extract the current item and render it outside the overflow menu, there's no place to put it since it would just wrap out of view if rendered into the list. If rendered next to the list, it would be pushed too far to the right due to the space caused by wrapping items in the list. I even tried implementing some JS to swap it with the last visible item, but that still can result in overflow if the current item is larger than that item. Instead, I've added some styling to indicate the current item inside the menu.
  2. The overflow menu button must be right-aligned, as opposed to appearing right next to the last button, because the list of items will always take up as much space as possible before wrapping. Honestly I think this is a nice design improvement.
Screen.Recording.2026-06-19.at.4.28.38.PM.mov

Changelog

New

Changed

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

Merge checklist

TylerJDev and others added 8 commits February 6, 2026 15:05
…e.module.css

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…e.tsx

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@changeset-bot

changeset-bot Bot commented Feb 6, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 8a780ac

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 Minor

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 6, 2026
@github-actions

github-actions Bot commented Feb 6, 2026

Copy link
Copy Markdown
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.

@iansan5653 iansan5653 changed the base branch from main to tylerjdev/overflow-underlinenav February 6, 2026 20:28
@github-actions github-actions Bot requested a deployment to storybook-preview-7506 February 6, 2026 20:31 Abandoned
@github-actions github-actions Bot temporarily deployed to storybook-preview-7506 February 6, 2026 20:40 Inactive
@iansan5653

Copy link
Copy Markdown
Contributor Author

BTW, I think the flex inspector in Chrome's devtools does a really nice job of showing what's going on here / where the tabs are going:

Screen.Recording.2026-02-06.at.5.00.33.PM.mov

@primer

primer Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

🤖 Lint issues have been automatically fixed and committed to this PR.

1 similar comment
@primer

primer Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

🤖 Lint issues have been automatically fixed and committed to this PR.

@primer-integration

primer-integration Bot commented Jun 24, 2026

Copy link
Copy Markdown

Integration test results from github/github-ui PR:

Running  CI   Running
Failed  VRT   Failed
Passed  Projects   Passed

VRT check ensures that when visual differences are detected, the PR cannot proceed until someone acknowledges the changes by adding the "visual difference acknowledged" label.

Need help? If you believe this failure is unrelated to your changes, please reach out to the Primer team for assistance.

@primer

primer Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

🤖 Lint issues have been automatically fixed and committed to this PR.

1 similar comment
@primer

primer Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

🤖 Lint issues have been automatically fixed and committed to this PR.

@iansan5653

Copy link
Copy Markdown
Contributor Author

Testing in preview showed a few small issues:

  1. Test failures caused by outdated selectors and the need to set the viewport size. These are resolved by https://github.com/github/github-ui/pull/24511/changes/b2f650f87b7d74db379c0903ffbf3dca62f706b9 which needs to be pulled into the release integration PR
  2. Some layout quirks in PRs are fixed by https://github.com/github/github-ui/pull/24554 which can be shipped before this change is released
  3. A tiny 1px discrepancy in the decoration applied to the "More" button was fixed in this PR
  4. The current item inside the overflow menu was missing the current styling in PRs; this was caused by a class override from the consumer. Fixed in this PR to force using our class only when inside the menu (this makes sense, consumers don't need to style the item inside the menu even though they may need to set classes on the top-level tabs)

@primer

primer Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

🤖 Lint issues have been automatically fixed and committed to this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Canary Release Apply this label when you want CI to create a canary release of the current PR 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.

7 participants