Breadcrumbs: Only show the previous breadcrumb on narrow viewports#7735
Breadcrumbs: Only show the previous breadcrumb on narrow viewports#7735liuliu-dev wants to merge 9 commits intomainfrom
Conversation
🦋 Changeset detectedLatest commit: f54db6f 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 |
|
|
🤖 Lint issues have been automatically fixed and committed to this PR. |
|
👋 Hi from github/github-ui! Your integration PR is ready: https://github.com/github/github-ui/pull/18292 |
There was a problem hiding this comment.
Pull request overview
Updates Breadcrumbs to collapse on narrow viewports (≤544px) by hiding most crumbs and exposing only the prior crumb(s) as back-navigation links, with a new prop to control how many items remain visible.
Changes:
- Add
visibleItemsOnNarrowprop (default1) and render-time data attributes to mark crumbs for narrow-viewport hiding. - Add narrow-viewport CSS rules to hide marked items and remove the trailing separator on the last visible item.
- Add Storybook example + Playwright coverage for narrow vs wide behavior and custom visible counts.
Show a summary per file
| File | Description |
|---|---|
| packages/react/src/Breadcrumbs/Breadcrumbs.tsx | Adds visibleItemsOnNarrow prop and wraps breadcrumb items with narrow-viewport data attributes. |
| packages/react/src/Breadcrumbs/Breadcrumbs.module.css | Adds media-query styles to hide data-narrow-hidden items and suppress the last separator on narrow viewports. |
| packages/react/src/Breadcrumbs/Breadcrumbs.features.stories.tsx | Adds a Storybook story demonstrating custom visibleItemsOnNarrow. |
| e2e/components/Breadcrumbs.test.ts | Adds AVT coverage for narrow/wide behavior and custom narrow visible count. |
| .changeset/slow-sides-feel.md | Announces the new visibleItemsOnNarrow prop as a minor change. |
Copilot's findings
- Files reviewed: 5/5 changed files
- Comments generated: 5
| const wrappedChildren = React.Children.map(children, child => <li className={classes.ItemWrapper}>{child}</li>) | ||
| const containerRef = useRef<HTMLElement>(null) | ||
| const childArray = useMemo(() => getValidChildren(children), [children]) | ||
| const visibleCountOnNarrow = Math.max(1, Math.min(visibleItemsOnNarrow, childArray.length - 1)) |
There was a problem hiding this comment.
visibleCountOnNarrow can become NaN if a consumer passes visibleItemsOnNarrow={NaN} (Math.min/Math.max will propagate NaN), which then causes all items to be treated as hidden on narrow viewports. Consider sanitizing visibleItemsOnNarrow with Number.isFinite (and/or an integer clamp) before computing visibleCountOnNarrow so invalid values fall back to the default behavior.
| const visibleCountOnNarrow = Math.max(1, Math.min(visibleItemsOnNarrow, childArray.length - 1)) | |
| const sanitizedVisibleItemsOnNarrow = Number.isFinite(visibleItemsOnNarrow) ? visibleItemsOnNarrow : 1 | |
| const visibleCountOnNarrow = Math.max(1, Math.min(sanitizedVisibleItemsOnNarrow, childArray.length - 1)) |
| const applyNarrow = validChildren.length > 1 | ||
| const isLast = index === validChildren.length - 1 | ||
| const isVisibleOnNarrow = applyNarrow && index >= validChildren.length - 1 - visibleCountOnNarrow && !isLast | ||
| const isLastVisibleOnNarrow = applyNarrow && index === validChildren.length - 2 | ||
| return ( | ||
| <li | ||
| className={classes.ItemWrapper} | ||
| key={index} | ||
| data-narrow-hidden={isVisibleOnNarrow ? undefined : applyNarrow ? '' : undefined} | ||
| data-narrow-last={isLastVisibleOnNarrow ? '' : undefined} |
There was a problem hiding this comment.
On narrow viewports the "current page" crumb is hidden via display: none (because the last item always gets data-narrow-hidden). This removes the aria-current="page" item from the accessibility tree, which can make the breadcrumb trail lose the current-location context for screen reader users. Consider keeping the current page accessible (e.g., visually-hidden rather than display:none, or alternative markup/ARIA so the current page is still conveyed).
| const finalChildren = React.useMemo(() => { | ||
| if (overflowMenuEnabled) { | ||
| if (overflow === 'wrap' || menuItems.length === 0) { | ||
| return React.Children.map(children, child => <li className={classes.ItemWrapper}>{child}</li>) | ||
| return wrapItemsWithNarrowAttrs(children, visibleCountOnNarrow) | ||
| } |
There was a problem hiding this comment.
The narrow-viewport collapsing is only implemented for the wrap rendering path (ItemWrapper + data-narrow-*). When the overflow menu feature flag is enabled and overflow is menu/menu-with-root with menuItems.length > 0, the rendered <li>s use BreadcrumbsItem and never receive data-narrow-hidden/data-narrow-last, so the narrow collapse behavior won’t apply there. If the intent is for the narrow behavior to be consistent across overflow modes, consider applying the same narrow attributes (and CSS) to the BreadcrumbsItem render path as well.
e2e/components/Breadcrumbs.test.ts
Outdated
| const items = page.locator('li') | ||
|
|
||
| // "Home" should be hidden, "About" (previous) visible, "Team" (current) hidden | ||
| await expect(items.nth(0)).toBeHidden() | ||
| await expect(items.nth(1)).toBeVisible() | ||
| await expect(items.nth(2)).toBeHidden() |
There was a problem hiding this comment.
These tests use page.locator('li'), which isn’t scoped to the Breadcrumbs component and could become flaky if the story renders other lists (or if future Breadcrumbs variants render nested lists like overflow menus). Consider scoping to the Breadcrumbs nav/ol (e.g., page.getByRole('navigation', {name: 'Breadcrumbs'}).locator('li')) so the assertions only target the component under test.
…ttps://github.com/primer/react into liuliu/only-show-the-previous-breadcrumb-on-narrow
Closes https://github.com/github/primer/issues/6371
Changelog
Changed
On narrow viewports (≤544px), breadcrumbs collapse to show only the previous (parent) item as a back link. The current page is hidden since you're already on it. Consumers can pass
visibleItemsOnNarrowto control how many items stay visible.Screen.Recording.2026-04-09.at.4.13.54.PM.mov
Rollout strategy
Testing & Reviewing
Merge checklist