[docs] Container-aware Breadcrumbs experiment - #48964
Draft
LukasTy wants to merge 1 commit into
Draft
Conversation
Add an experiment that collapses Breadcrumbs to fit its container. The wrapper does not use the built-in collapse. maxItems is a switch, not a dial: above the threshold the component always renders the same three-slot layout, and its expanded state never resets. So the wrapper slices the children itself and passes a menu button as an ordinary child, the way the condensed-with-menu demo does. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Deploy previewBundle size
Check out the code infra dashboard for more information about this PR. |
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.
Draft. Docs-only: adds
docs/pages/experiments/responsive-breadcrumbs.tsx. No package change yet.The experiment is a proof of concept for making
Breadcrumbscollapse to fit its container instead of a fixed item count. The design and the implementation plan for the component change are below, for discussion before any code lands inpackages/.Problem
maxItemscollapses on item count. It cannot know whether the row fits. Today a path that exceeds its container wraps to a second line, becauseflexWrap: 'wrap'is hardcoded on the<ol>.What the experiment does
A userland
ResponsiveBreadcrumbswrapper around the published component, no patching. It measures the items in an off-screen mirror, picks the widest layout that still fits, and puts the dropped items in a menu.It does not use the built-in collapse at all. It slices the children itself and passes an
IconButtonas an ordinaryBreadcrumbschild, the way the condensed-with-menu demo does.Run it with
pnpm --filter docs dev, then open/experiments/responsive-breadcrumbs.What the experiment proves
Verified in headless Chromium across 68 container widths, 900px down to 230px:
hrefFindings that shape the design
maxItemsis a switch, not a dial. Once the count passes the threshold, the component always rendersitemsBeforeCollapse + ellipsis + itemsAfterCollapse. TuningmaxItemsalone can never drop items gradually. The experiment drivesitemsAfterCollapsedown a ladder instead.expandedis one way. It is internal state that never resets, so after a click the row overflows its container again and userland cannot undo it. Container-driven collapsing and expand-in-place are incompatible: expanding is exactly the thing that breaks the fit.hrefoff each child to keep the menu item a real link, then wraps it in<li role="none">because an<a>cannot be a direct child of the menu<ul>.itemsBeforeCollapse + itemsAfterCollapseitems remain, dropping stops. A narrower container overflows, because nothing truncates label text.Menu's scroll lock fights the measurement. Opening a defaultMenupads the body, which resizes the container and recomputes the layout under the open menu. NeedsdisableScrollLock.Design
API
'auto'means "fit the container".itemsBeforeCollapseanditemsAfterCollapsekeep their meaning and become the floor. Reusing the existing prop keeps the API one value wider instead of adding a second, conflicting knob.Behavior in
'auto'mode<ol>switches toflex-wrap: nowrap.itemsAfterCollapsewalking down fromcount - 1 - itemsBeforeCollapseto its floor. The first rung that fits wins.ResizeObserveron the root recomputes on container resize.expandedis not used. There is no expand-in-place, so there is no stuck state.The collapse indicator
This is the open question, and the reason this PR is a draft.
Container-driven collapsing makes the current ellipsis-expands-in-place behavior incoherent, so
'auto'needs a different affordance. An overflow menu is the natural one, and it is what the experiment uses. But hardcodingMenuinsideBreadcrumbspulls inPopoverandModal.Breadcrumbscurrently imports onlyTypographyandButtonBase, so that is a real bundle-size regression for every consumer.Recommendation: do not hardcode the menu. Promote the collapse indicator to a real slot that receives the hidden items, ship the ellipsis button as the default, and document the menu as the recommended pattern.
That keeps the weight opt-in and makes the experiment's wrapper expressible in ~20 lines instead of a fork. If reviewers would rather ship a batteries-included menu, that is a separate decision and I am happy to take it the other way.
Measurement
The component can do this better than any wrapper. On first layout it renders uncollapsed, measures every item in a
useLayoutEffect, and commits the collapsed layout before paint -- no flash, and no duplicate DOM tree. Widths are cached per item and re-measured when the children change or the fonts load.Accessibility
<li role="none">.Implementation plan
1. Layout mechanics --
packages/mui-material/src/Breadcrumbs/maxItems="auto"; widen the type andintegerPropTypeto a union.useBreadcrumbsFithook: ladder construction, measurement,ResizeObserver, chosen rung.flexWrap: 'nowrap'toBreadcrumbsOlin auto mode only, so existing behavior is untouched.clientWidth.clientWidthis integer-rounded and hides sub-pixel overflow -- this cost me a real bug in the experiment, a 636.04px row sitting in a 636px box.2. Collapse slot
slots.collapsed/slotProps.collapsedthroughuseSlotProps.ownerStateso the slot can render them.BreadcrumbCollapsedas the default, unchanged for the non-auto path.3. Tests
Breadcrumbs.test.js, jsdom): ladder selection and slot wiring, with stubbed rects. jsdom has no layout, so the fit logic itself cannot be tested there.test/e2e, Playwright): the real assertions -- every rung reachable, no row overflows, layout recomputes on resize. This mirrors the verification already run against the experiment.4. Docs
slots.collapsed, replacing the hand-rolled condensed-with-menu example.Out of scope
Text truncation. It is the only way to go below the floor, it changes what a breadcrumb is, and it should be its own discussion.
Notes for reviewers
.claude/launch.jsonis intentionally not committed.