Skip to content

feat: add navigation rail - #5106

Open
marius-ck wants to merge 5 commits into
callstack:mainfrom
marius-ck:mariuspasca-callstack-feat/add-navigation-rail
Open

feat: add navigation rail#5106
marius-ck wants to merge 5 commits into
callstack:mainfrom
marius-ck:mariuspasca-callstack-feat/add-navigation-rail

Conversation

@marius-ck

@marius-ck marius-ck commented Sep 7, 2026

Copy link
Copy Markdown

Motivation

Paper has no Material Design 3 navigation rail. The closest existing option is Drawer.CollapsedItem, which only covers the collapsed icon layout, has no expanded state, no header slot, and no modal presentation — so apps targeting medium and large window classes have to hand-roll side navigation.

This PR adds NavigationRail with the full MD3 behaviour:

  • Two layouts, one component. Collapsed (96dp, stacked icon + label) and expanded (220–360dp, icon + label rows), switched with the expanded prop rather than by swapping components. expandedWidth is clamped to the spec range.
  • Layout morphing. The panel width, container color and item geometry animate between states with Reanimated CSS transitions on the theme's motion tokens (300ms, emphasized). The indicator grows 32dp → 56dp and stretches to cover icon and label, the icon stays at a fixed leading offset so it never shifts, and the stacked and row labels cross-fade. Every item inherits this from context, so new destinations need no extra wiring.
  • overlay mode. The rail keeps its collapsed footprint in the layout and the expanded panel floats above the content behind a scrim, for layouts that can't afford to reflow. onDismiss fires on scrim press.
  • NavigationRail.Modal. A standalone expanded rail on its own layer, sliding in over 400ms (emphasizedDecelerate) and out over 200ms (emphasizedAccelerate), RTL-aware, with scrim, hardware back and escape handling. It stays mounted through the exit so the slide-out actually plays.
  • animated flag. Opts out of every rail animation in one place, including the items and the modal's mount timing.
  • Accessibility. Items are role="tab" with aria-selected / aria-disabled, labels drive the accessibility name, hidden label copies are aria-hidden, a focus ring appears on keyboard focus only, and reduced motion skips layout motion while keeping fades.
  • Theming. Colors come from role tokens: surface collapsed, surfaceContainer expanded, secondaryContainer / onSecondaryContainer for the active indicator and icon, scrim for the backdrop. Spec values live in a per-component tokens.ts alongside a utils.ts, matching FAB and Switch.

docs/6.x/docs/guides/migration.md gains a Drawer section pointing Drawer.CollapsedItem users at the new component, and the docs site picks up the three components plus their theme colors.

Related issue

No existing issue. The gap: MD3 lists the navigation rail as the primary navigation pattern for medium and expanded window classes, and Paper ships no equivalent — Drawer.CollapsedItem covers only the collapsed rail, with no expanded rows, header slot, modal variant, or transition between states.

Test plan

Automated

yarn typecheck
yarn lint
yarn test

New suites under src/components/__tests__/NavigationRail/ cover render snapshots for both layouts, the collapsed and clamped widths, the overlay footprint and scrim fade plus dismiss, the label swap between layouts, the modal's slide-out-before-unmount and its instant unmount when animated={false}, scrim dismissal, active-icon and indicator state, accessibility names, press and disabled behaviour, badges, and the resolveItemColors / clampExpandedWidth helpers.

Manual — example app, "Navigation Rail" screen

  1. Toggle Expanded. The panel widens to 220dp, the background shifts to surfaceContainer, each indicator grows and stretches over its label, labels cross-fade from beneath the icon to beside it, the header FAB expands to show its label, and the toggle icon cross-fades. Collapse and confirm it reverses cleanly with no text wrapping or jumping mid-transition.
  2. Toggle Overlay, then expand. The content no longer reflows; the panel floats above it with a scrim and rounded trailing corners. Press the scrim to collapse.
  3. Toggle Animated off and repeat 1 and 2 — every change should snap with no motion. Toggle it back on.
  4. Toggle Labels off to check icon-only items stay centred, and step through the top / center / bottom alignment chips.
  5. Press Open modal rail. It slides in from the start edge and, on scrim press or Android back, slides out before unmounting rather than fading in place.
  6. Tap destinations and confirm the active indicator animates in both layouts. Note the badges: a count sits on the icon's trailing corner, a dot sits on the indicator's trailing corner, both right-aligned with matching clearance.
  7. Enable Reduce motion in OS accessibility settings and confirm layout motion is skipped while fades still run.
  8. Check an RTL locale: the rail mirrors and the modal slides from the correct edge.

https://app.notion.com/p/callstack/Add-Navigation-Rail-37a5d027c0f881cc93c4d8195e84e175?v=3285d027c0f880a68ccb000c1ce3c56e&source=copy_link

https://m3.material.io/components/navigation-rail/overview

Simulator Screenshot - iPhone 17 - 2026-09-09 at 12 23 14 Simulator Screenshot - iPhone 17 - 2026-09-09 at 12 23 28 Simulator Screenshot - iPhone 17 - 2026-09-09 at 12 23 34

@marius-ck
marius-ck marked this pull request as ready for review September 8, 2026 12:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant