Skip to content

UnderlineNav: Fix flickering by moving icon visibility control to pure CSS#8108

Open
iansan5653 wants to merge 15 commits into
mainfrom
fix-underlinenav-flicker
Open

UnderlineNav: Fix flickering by moving icon visibility control to pure CSS#8108
iansan5653 wants to merge 15 commits into
mainfrom
fix-underlinenav-flicker

Conversation

@iansan5653

Copy link
Copy Markdown
Contributor

The icons in UnderlineNav can cause flickering as they cause/un-cause the container to overflow when hidden/shown. We use JS to update state to prevent this flickering, but that doesn't run until after hydration. This means that, for certain screen sizes, the icons will flicker after SSR and before client-size hydration.

To fix this, I'm moving the visibility control to pure CSS. However, to make this work we must use CSS as a state machine; otherwise we'll just get flickering for the entire page lifecycle. So I've introduced a second animation, hide-icons, which only plays once and remains applied when paused. Then I updated the scroll-driven animation to control the hide-icons play state, rather than control the visibility directly. The result is that once the animation plays once, it will remain in that state forever - no need for any JS at all.

Changelog

New

Changed

  • Fixed icon flickering before initial render in UnderlineNav

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

@iansan5653 iansan5653 requested a review from a team as a code owner July 2, 2026 14:57
@iansan5653 iansan5653 requested review from TylerJDev and Copilot July 2, 2026 14:57
@changeset-bot

changeset-bot Bot commented Jul 2, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: b3e85f5

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 commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

⚠️ Action required

👋 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. If this doesn't work, you can also use the original workflow here. Check the integration testing docs for step-by-step instructions. Or, apply the integration-tests: skipped manually label to skip these checks.

To publish a canary release for integration testing, apply the Canary Release label 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 Jul 2, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 updates UnderlineNav to avoid SSR→hydration icon flicker by removing the JS “has ever overflowed” state and attempting to move icon-hiding logic entirely into CSS via scroll-driven animations.

Changes:

  • Removed hasEverOverflowed state and the data-hide-icons attribute from UnderlineNav.tsx.
  • Added a new hide-icons animation in UnderlineNav.module.css, intended to run once and then keep icons hidden permanently by controlling animation play state via the existing scroll-driven overflow detection.
  • Minor string literal change for the overflow menu button aria-label.
Show a summary per file
File Description
packages/react/src/UnderlineNav/UnderlineNav.tsx Removes JS-based icon hiding (hasEverOverflowed / data-hide-icons) to rely on CSS-only behavior.
packages/react/src/UnderlineNav/UnderlineNav.module.css Adds a one-shot animation approach intended to hide icons without JS and prevent overflow-driven flicker loops.

Review details

  • Files reviewed: 3/3 changed files
  • Comments generated: 2
  • Review effort level: Low

Comment thread packages/react/src/UnderlineNav/UnderlineNav.tsx
Comment on lines +37 to 45
@keyframes hide-icons {
0% {
display: inline;
}

100% {
display: none;
}
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'm like 99% sure this is incorrect. Browsers will animate the property but (obviously) not smoothly; they'll discretely step from one value to the next.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This turned out to be due to not setting an initial value for the variable. Which feels like a browser bug, but it's easy to solve by initializing it.

@github-actions github-actions Bot requested a deployment to storybook-preview-8108 July 2, 2026 15:02 Abandoned
@github-actions github-actions Bot temporarily deployed to storybook-preview-8108 July 2, 2026 15:19 Inactive
@iansan5653 iansan5653 added the Canary Release Apply this label when you want CI to create a canary release of the current PR label Jul 2, 2026
@github-actions github-actions Bot temporarily deployed to storybook-preview-8108 July 2, 2026 15:57 Inactive
@iansan5653 iansan5653 added the update snapshots 🤖 Command that updates VRT snapshots on the pull request label Jul 2, 2026
@github-actions github-actions Bot temporarily deployed to storybook-preview-8108 July 2, 2026 16:16 Inactive
@iansan5653 iansan5653 removed integration-tests: recommended This change needs to be tested for breaking changes. See https://arc.net/l/quote/tdmpakpm Canary Release Apply this label when you want CI to create a canary release of the current PR update snapshots 🤖 Command that updates VRT snapshots on the pull request labels Jul 2, 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 Jul 2, 2026
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

⚠️ Action required

👋 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. If this doesn't work, you can also use the original workflow here. Check the integration testing docs for step-by-step instructions. Or, apply the integration-tests: skipped manually label to skip these checks.

To publish a canary release for integration testing, apply the Canary Release label to this PR.

@github-actions github-actions Bot requested a deployment to storybook-preview-8108 July 2, 2026 21:08 Abandoned
@github-actions github-actions Bot temporarily deployed to storybook-preview-8108 July 2, 2026 21:18 Inactive
@iansan5653 iansan5653 force-pushed the fix-underlinenav-flicker branch from 366cedc to b79ae2e Compare July 2, 2026 21:31
Comment on lines -32 to +28
overflow: hidden;
flex: 1;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

overflow: hidden had no effect here because there's no explicit height set on this container. And it's unnecessary since the parent already has that applied.

flex: 1 fixes a separate bug where sometimes the button wouldn't float all the way to the right. It makes the tab containers take up more space to push the overflow button further out. This prevents it from looking like it's floating in space.

Comment on lines +5 to +7
animation:
detect-overflow linear,
delay-visibility forwards 0ms 5ms;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

When testing on Chrome with 20x CPU slowdown, I could see an initial paint where the icons are visible, followed by a quick update to hide them. I tried several things to resolve this, including conditionally setting the animation duration to 0 seconds and also conditionally changing the start state of the animation, but they all caused unexpected side effects.

I actually think this initial flicker may be entirely unavoidable, because the browser has to paint the tabs to determine if overflow happens in order to determine whether to hide the icons or not. So I just accepted it and decided to hide the tabs with a very short animation, which delays their initial render by an almost imperceptible 5 ms. This is just long enough to get that initial paint out of the way.

animation-fill-mode: forwards;
animation-timing-function: linear;
animation-play-state: var(--UnderlineNav_hide-icons-play-state);
animation-duration: 0.1ms; /* must be greater than 0 */

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Interestingly, this can't be arbitrarily small; 0.0001ms is treated as 0 in Chrome. 0.1ms seems to work very consistently though.

I did try making the duration dynamic, instead of the play state, and setting the duration to 0 to immediately resolve the animation when I want to toggle it. That didn't work though; the style didn't 'stick' if the duration was increased again.

Comment on lines +37 to 45
@keyframes hide-icons {
0% {
display: inline;
}

100% {
display: none;
}
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This turned out to be due to not setting an initial value for the variable. Which feels like a browser bug, but it's easy to solve by initializing it.

@iansan5653 iansan5653 added the update snapshots 🤖 Command that updates VRT snapshots on the pull request label Jul 2, 2026
@github-actions github-actions Bot requested a deployment to storybook-preview-8108 July 2, 2026 21:35 Abandoned
@github-actions github-actions Bot temporarily deployed to storybook-preview-8108 July 2, 2026 21:48 Inactive
@primer-integration

Copy link
Copy Markdown

⚠️ Integration PR Outdated

This integration PR does not have the latest commit from the primer/react PR.

Integration PR references: 56140680c341a199079ac3ad783c54566e49f5c3
Latest commit on primer/react PR: 6b360c15c21b0384f0aef3a30aedb5ce6e19fc41

Please update the integration PR to reference the latest commit from the primer/react PR before reviewing workflow results.

@llastflowers llastflowers 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 Jul 2, 2026
@github-actions github-actions Bot removed the update snapshots 🤖 Command that updates VRT snapshots on the pull request label Jul 2, 2026
@github-actions github-actions Bot temporarily deployed to storybook-preview-8108 July 2, 2026 23:04 Inactive
@llastflowers llastflowers added the update snapshots 🤖 Command that updates VRT snapshots on the pull request label Jul 2, 2026
@github-actions github-actions Bot removed the update snapshots 🤖 Command that updates VRT snapshots on the pull request label Jul 3, 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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants