Skip to content

ADR-0044: Compact navigation adapts to label width

  • Status: Accepted
  • Date: 2026-10-15 (amended 2026-10-16 — single bounded row; amended 2026-10-17 — selected tab kept in view)

Context

Five equal-width compact navigation items clipped destination names at large system text sizes. A first fix wrapped the overflowing items into extra rows based on measured label width, but on narrow screens or large text this doubled or tripled the bar height (4 + 1 rows), stranded Settings alone on a second row, and ate list content on every screen.

Once the single scrolling row shipped, the scroll position was still never synchronised with the selected route: a restored or deep-linked Settings / Reports could sit outside the visible viewport (its label and Material indicator offscreen) at narrow widths or large text, because nothing brought the selected item into view.

Decision

Measure the full localized labels using the navigation label typography and current density/font scale. Compact navigation keeps one row of five equal-width items whose height is bounded by Material's single NavigationBar row (~80 dp plus system insets). When the measured items no longer fit the available width, the row is laid out at its intrinsic width and scrolls horizontally behind the shared edge fade (scrollFades); otherwise it fills the viewport so Home stays the centre tab. Keep destination order, full labels, the user's font scale, Material selected indicators and semantics, and the ≥48 dp touch targets. Reserve only the measured Material item inset (8 dp per side); never abbreviate labels, truncate them, hide items behind a menu, or wrap to extra rows. Apply system insets once around the complete bar. The medium/expanded navigation rail and tab save/restore policy are unchanged.

The row also keeps the selected destination fully visible. Whenever the selected route, viewport width, or measured item bounds change, an effect brings the selected item into view using a short, minimal animateScrollTo (the smallest movement that reveals the whole item, clamped to the scroll range). The effect is keyed to those inputs only — never to the live scroll value — so a manual scroll is preserved and the bar never repeatedly fights the user. The scroll respects the system animator scale.

Consequences

The bar consumes a single bounded row at every supported width and text size, so it never strands a destination alone or steals a second row from list content. On the narrowest widths or the largest text the off-screen destination requires a horizontal scroll; the edge fade makes that discoverable. Home remains the centre tab whenever the five items fit. A restored or deep-linked selection is always shown complete, so its label and selected indicator can never be hidden.

Verification

Instrumented compact-navigation tests measure each label's laid-out width against its full single-line intrinsic width and its bounds against its item, assert every label shares one vertical band with a bounded bar height, and scroll the row to select all five destinations at 320/411 dp with 100%, 150%, and 200% text in light/dark themes. Selected-tab visibility is asserted for an externally selected (restored/deep-linked) route at 320 dp / 200% text, in RTL, and after a viewport resize, plus a live deep-link launch. Navigation state regressions exercise saved tab state, Home navigation, and Back. Home viewport budget tests assert the summary and two complete recent rows stay above the fold at 320 dp / 100% text when no capture-status notice is shown (see ADR-0053's Amendment for the notice case).