fix(webkit): [ENG-47034] truncate the breadcrumb on one row and pin every glyph size - #837
Open
herbert-julio-azion wants to merge 5 commits into
Open
fix(webkit): [ENG-47034] truncate the breadcrumb on one row and pin every glyph size#837herbert-julio-azion wants to merge 5 commits into
herbert-julio-azion wants to merge 5 commits into
Conversation
The trail had no way to give ground when space ran out: `breadcrumb-item` was
`shrink-0` with a `whitespace-nowrap` label, and `breadcrumb-list` was
`flex-wrap`. So a long segment could neither shrink nor ellipsize, and the row
either wrapped to a second line or ran off-screen with no way to reach it.
Measured in real Chromium at 375px with real Tailwind output (v4 compile API
against each source state), trail in a 375px container:
items, 2 entries, long current list 442px wide (67px past the container),
2 rows -> 375px, 1 row, nothing past it
items, 3 entries, long current list 442px, 2 rows -> 375px, 1 row
hand-composed, 5 segments list 375px but 2 rows (wrapped)
-> 1 row, scrolls
items, 4 short labels 195px, 1 row -> unchanged
The shrink chain needs every flex level to be able to go below its content
width, so: `min-w-0` on the nav, on the list, on the list's `li` children
(declared by the list so it holds for a hand-composed trail too, where the `li`
comes from the consumer) and on the item; `truncate` on the label; `flex-nowrap`
so the row never becomes two. `overflow-x-auto` on the list is the escalation
for a trail that cannot fit even fully ellipsized: shrinking happens first, so
the scrollbar only appears once truncation is exhausted, and focusing an item
does not push its ring into a vertical scrollbar (measured).
The overflow menu is unaffected: its panel Teleports to `body`, so the new
scroll container cannot clip it.
Also here:
- The breadcrumb now owns its collapse breakpoint in
`composables/use-breadcrumb-collapse.ts` instead of importing the overlay
layer's `use-overlay-mobile`. Same 767px, same behaviour; it is a navigation
concern, and this is where a future container-driven rule belongs.
- `breadcrumb.test.ts`: asserts that a hand-composed trail keeps every segment
at this (mobile) viewport and grows none of the collapse machinery, which was
untested in either mode.
- Storybook: LongLabel, LongLabelMobile and ComposedMobile. The component
description no longer implies the collapse applies to a hand-composed trail.
- Spec: documented the responsive contract for both modes, and corrected the
stories list (it named three stories that do not exist).
Not in this PR, deliberately: collapse driven by the space actually available
rather than the viewport, and the desktop-trail flash before the media query
resolves. Both are design changes rather than defect fixes; the single-row rules
above are what make the current viewport rule survive a narrow container.
…rflow icon slot
Every breadcrumb glyph sized itself by inheritance, so its rendered size was
decided by whichever ancestor typography (or host stylesheet) happened to win,
while the box next to it was a hard-coded 0.875rem. Measured in a real browser
against this repo's compiled CSS and against a consuming app's real built CSS
bundle (Tailwind v3 with `important: true` plus a PrimeVue theme that ships
`.azion .pi { font-size: 1rem }`):
- separator chevron: 16px glyph inside a 14px box, in both environments;
- overflow-menu ellipsis: 12px inside the design system, 16px in the consumer,
because it carried no font-size utility at all and the host rule won.
`text-[length:inherit]` is replaced by an explicit `text-[0.875rem]` on the item
and separator glyphs, and the ellipsis gets the same box plus font-size. All
glyphs now measure 14px in a 14px box, identically in both environments.
The collapsed overflow menu also passed its icon through a `#leading` slot, but
`Dropdown.Option` declares `left` / `right`, so the slot was dead and the icon
never rendered (measured: 0 option icons before, 1 after). Renamed to `#left`
and sized like the others; a regression test covers it.
…h size
The trail truncates instead of wrapping: the item root is a block-level flex box, so it
takes the width its parent gives it (an inline-level box resolved shrink-to-fit against
its own max-content, leaving a 134px anchor inside a 92px item with no ellipsis), and the
label carries `min-w-0` as the last link in the shrink chain.
Every glyph is pinned with `!`. Measured 14x14 at 375px and at 1280px, but inside a
consuming app `.azion .pi { font-size: 1rem }` is unlayered CSS and beats anything in
`@layer utilities`, which is how the arrow grew outside the design system.
The overflow trigger used `px-1` where an item uses `px-[var(--spacing-xs)]`, leaving
4px between the ellipsis and the next chevron; it now matches an item.
The composed and mobile-viewport stories are gone: the set documents the versions and the
mobile viewport is exercised by hand. The collapse keeps its coverage, rendered directly
instead of through the removed story, and the orphaned baselines are pruned.
… given The LongLabel story hardcoded `class="max-w-[20rem]"`, a 320px cap that ignores the viewport, so the label ellipsized at any window size and read as the component truncating with space to spare. Measured without the cap: 1440px and 800px show the label in full (560/560), 375px ellipsizes (281/560). The component only truncates when the row runs out of room; the cap was the constraint.
herbert-julio-azion
marked this pull request as ready for review
August 3, 2026 18:35
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes ENG-47034.
What was wrong
Three separate defects, each measured before and after.
The trail wrapped instead of truncating. The shrink chain reached the anchor but stopped
there: the item root was
inline-flex, and an inline-level box resolves its widthshrink-to-fit against its own max-content, so inside a hand-composed
<li>(which arriveswith no class, hence a block) the anchor stayed at its text width and spilled out of the
item the row had already shrunk. Measured a 134px anchor inside a 92px item, label never
ellipsizing. The root is now a block-level
flex, so it takes the width its parent givesit, and the label carries
min-w-0as the last link in the chain. Measured after: thecomposed trail fits 375/375 with every label ellipsized.
The glyphs changed size outside the design system. Measured 14x14 at both 375px and
1280px, so they were already fixed inside webkit; in a consuming app
.azion .pi { font-size: 1rem }is unlayered CSS and beats anything in@layer utilities.Every glyph is now pinned with
!.The overflow trigger sat on top of the chevron. It used
px-1where a breadcrumb itemuses
px-[var(--spacing-xs)], leaving 4px between the ellipsis and the next chevron. Itnow matches an item.
The long label truncated with room to spare. That was the story, not the component: it
hardcoded
class="max-w-[20rem]", a 320px cap that ignores the space available. Measuredwithout it: 1440px and 800px show the label in full (560/560), 375px ellipsizes (281/560).
Note that truncation is driven by the space available, not by a breakpoint: it is
flex-shrinkagainst whatever width the parent gives. Only the collapse into the overflowmenu is viewport-driven.
Stories
The composed and mobile-viewport stories are gone: the set documents the versions and the
mobile viewport is exercised by hand. The collapse keeps its coverage, rendered directly
instead of through the removed story, and the 18 orphaned baselines are pruned.
Verified
28 tests pass (1 pre-existing skip), lint clean, the 89 story files comply with
storybook-source.md, spec and checksum updated.