Skip to content

UnderlineNav: Add overflow: hidden when calculating items for overflow menu#7504

Open
TylerJDev wants to merge 12 commits intomainfrom
tylerjdev/overflow-underlinenav
Open

UnderlineNav: Add overflow: hidden when calculating items for overflow menu#7504
TylerJDev wants to merge 12 commits intomainfrom
tylerjdev/overflow-underlinenav

Conversation

@TylerJDev
Copy link
Member

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

Credits to @siddharthkp & @francinelucca for the original PR: #7463

Changelog

New

Changed

  • Adds overflow: hidden to container when loading items

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 TylerJDev requested a review from a team as a code owner February 6, 2026 15:11
@github-actions github-actions bot added the staff Author is a staff member label Feb 6, 2026
@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
Copy link
Contributor

github-actions bot commented Feb 6, 2026

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

@changeset-bot
Copy link

changeset-bot bot commented Feb 6, 2026

🦋 Changeset detected

Latest commit: 62e4086

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

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 aims to reduce layout shift (CLS) in UnderlineNav by hiding overflow while the component calculates which items should move into the overflow (“More”) menu (fix for primer issue #6291).

Changes:

  • Add a ready prop to the shared UnderlineWrapper and emit a data-ready attribute.
  • Update UnderlineWrapper CSS to hide overflow until data-ready='true'.
  • Track isReady in UnderlineNav and pass it through to UnderlineWrapper.
  • Add a patch changeset entry.

Reviewed changes

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

File Description
packages/react/src/internal/components/UnderlineTabbedInterface.tsx Adds ready prop and data-ready attribute on the shared wrapper component.
packages/react/src/internal/components/UnderlineTabbedInterface.module.css Hides overflow by default and restores it when data-ready='true'.
packages/react/src/UnderlineNav/UnderlineNav.tsx Introduces isReady state and passes it to UnderlineWrapper.
.changeset/clever-geese-cover.md Patch changeset documenting the CLS-related UnderlineNav update.
Comments suppressed due to low confidence (1)

packages/react/src/UnderlineNav/UnderlineNav.tsx:170

  • The new isReady/ready gating is user-visible behavior meant to prevent CLS, but it isn’t covered by a unit test. Since this component already has a comprehensive test suite, it would be good to add a test asserting the wrapper is initially non-ready (overflow hidden) and becomes ready after the first successful overflow calculation/resize observer pass.
    // Track whether the initial overflow calculation is complete to prevent CLS
    const [isReady, setIsReady] = useState(false)

TylerJDev and others added 3 commits February 6, 2026 10:41
…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>
@github-actions github-actions bot requested a deployment to storybook-preview-7504 February 6, 2026 15:45 Abandoned
box-shadow: inset 0 -1px var(--borderColor-muted);

/* Hide overflow until calculation is complete to prevent CLS */
overflow: visible;
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we can do this entirely within css and avoid any of the useEffect() updates, just with something like:

.UnderlineWrapper {
  &:not(:has([class*='prc-UnderlineNav-MoreButton-'])) {
    overflow-x: hidden;
  }
}

You might need to update the classNames a bit (or move it to the UnderlineNav component directly)

Copy link
Member

Choose a reason for hiding this comment

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

Very nice! Although probably the same thing perf wise because the MoreButton appears after a javascript pass as well, right?

Thinking out loud, do we need to test this for cases where there is an overflow because of icons and counters, so the MoreButton may never appear, or is that already covered?

Copy link
Member Author

Choose a reason for hiding this comment

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

Thinking out loud, do we need to test this for cases where there is an overflow because of icons and counters, so the MoreButton may never appear,

Can the component still overflow without the menu? I haven't seen that, as I assumed that we also took those into account when determining whether to show the menu or not.

Copy link
Contributor

@iansan5653 iansan5653 left a comment

Choose a reason for hiding this comment

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

Running this locally, I noticed that data-ready is not set to true, even though it is in the Storybook preview.

I tracked it down to this condition:

if (!isReady && widths.length > 0 && widths.length === validChildren.length) {
  setIsReady(true)
}

Logging widths.length vs validChildren.length shows that widths is twice as long as validChildren. The fact that it's exactly double smells like a potential StrictMode bug.

<Component
className={clsx(classes.UnderlineWrapper, className)}
ref={ref as ForwardedRef<HTMLDivElement>}
data-ready={ready === undefined ? undefined : ready ? 'true' : 'false'}
Copy link
Member

Choose a reason for hiding this comment

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

More descriptive name please. data-hydrated / data-overflow-handled?

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.

Left a few comments, approving in advance

Co-authored-by: Siddharth Kshetrapal <siddharthkp@github.com>
Copy link
Contributor

@liuliu-dev liuliu-dev left a comment

Choose a reason for hiding this comment

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

🎉

@primer-integration
Copy link

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

@primer-integration
Copy link

Integration test results from github/github-ui:

Passed  CI   Passed
Running  VRT   Running
Passed  Projects   Passed

@github-actions github-actions bot requested a deployment to storybook-preview-7504 February 13, 2026 14:31 Abandoned
@github-actions github-actions bot temporarily deployed to storybook-preview-7504 February 13, 2026 14:40 Inactive
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.

Left a few code improvements, approving in advance though

// Only mark as ready once widths have been measured for all valid children
const widths = displayIcons ? childWidthArray : noIconChildWidthArray
if (!isHydrated && widths.length > 0 && widths.length === validChildren.length) {
setIsHydrated(true)
Copy link
Member

Choose a reason for hiding this comment

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

Oh if it's not just hydration but also calculations, maybe we should call it data-overflow-measured or something similar?

className={className}
ref={navRef}
data-variant={variant}
hydrated={isHydrated}
Copy link
Member

Choose a reason for hiding this comment

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

Can we pass data-hydrated={isHydrated} and let it spread. Not a fan of the extra prop

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.

5 participants