Skip to main content

NavigationRail

MD3 Expressive navigation rail for medium and larger windows: a 96dp collapsed column of destinations that morphs on a spring into an expanded rail (icon beside label, 220–360dp wide), either in-flow or as a modal overlay.

Collapsed and expanded​

variant switches between collapsed (icon over label) and expanded (icon beside label in a pill, as wide as the widest destination). The change is a spring morph. The optional header is pinned at the top — typically a menu button and a FAB; <Fab followContainer> morphs in sync with the rail.

Use the menu button to expand and collapse the rail.
const [expanded, setExpanded] = useState(false)

<NavigationRail
aria-label="Mail"
variant={expanded ? 'expanded' : 'collapsed'}
value={value}
onChange={(event, next) => setValue(next)}
header={
<>
<IconButton
variant="standard"
icon={expanded ? <MenuOpenIcon /> : <MenuIcon />}
aria-label={expanded ? 'Collapse navigation' : 'Expand navigation'}
onClick={() => setExpanded(!expanded)}
/>
<Fab icon={<EditIcon />} label="Compose" followContainer />
</>
}
>
<NavigationRailItem value="inbox" icon={<InboxIcon />} selectedIcon={<InboxFillIcon />} label="Inbox" />
<NavigationRailItem value="outbox" icon={<OutboxIcon />} label="Outbox" badge="3" />
</NavigationRail>

selectedIcon shows the filled icon for the active destination. badge takes content for a large badge or true for a dot; badgeLabel overrides the announced text.

Arrangement​

arrangement places the destinations at the top (default), center, or bottom below the header.

Use the menu button to expand and collapse the rail.

With modal, the expanded rail overlaps the page instead of pushing it: a scrim, focus trapped inside, the rest of the page inert, and Escape / scrim click call onClose. Collapsed, it is a regular 96dp rail.

Expanded, the modal rail overlaps this content with a scrim. Press Escape or click the scrim to collapse it.
<NavigationRail
modal
variant={expanded ? 'expanded' : 'collapsed'}
onClose={() => setExpanded(false)}
…
/>

Hide on collapse​

modal + hideOnCollapse hides the rail entirely while collapsed; expanding slides it in from the leading edge — open it from a button in the page.

<IconButton icon={<MenuIcon />} aria-label="Open navigation" onClick={() => setExpanded(true)} />
<NavigationRail
modal
hideOnCollapse
variant={expanded ? 'expanded' : 'collapsed'}
onClose={() => setExpanded(false)}
…
/>

Accessibility​

  • Renders a <nav> landmark — give it an aria-label. Items are native <button>s; the active one carries aria-current="page".
  • Tab order: header (menu button, FAB) first, then the destinations.
  • Badges are announced after the label (badgeLabel to customize). An item without a label needs an aria-label.
  • The open modal rail is a dialog (aria-modal) named by modalLabel (default "Navigation rail"); focus returns to the opener when it collapses.

Props​

PropTypeDefaultDescription
arrangementenumtopVertical placement of the destinations below the header.
defaultValuestring—Uncontrolled initial destination value.
headerReactNode—Optional header content (e.g. a menu button and/or FAB) pinned at the top.
hideOnCollapsebooleanfalseWith `modal`: hide the rail entirely while collapsed; expanding slides the expanded rail in from the leading edge. Ignored without `modal`.
modalbooleanfalseModal expanded layout: while `variant="expanded"` the rail overlaps the page instead of pushing it — surface-container, 16dp trailing corners, level-2 shadow, a 0.32 scrim, focus trapped inside, the rest of the page inert, Escape / scrim click call `onClose`, and focus returns to the opener on collapse. Collapsed, it is a regular in-flow 96dp rail (or nothing, with `hideOnCollapse`).
modalLabelstringNavigation railAccessible name of the open modal rail (a dialog).
onChange((event: MouseEvent<HTMLButtonElement, MouseEvent>, value: string) => void)—Fires with the triggering event and the newly selected destination value.
onClose(() => void)—With `modal`: called when the scrim is clicked or Escape is pressed.
valuestring—Controlled selected destination value.
variantenumcollapsed`collapsed` (96dp, icon-over-label) or `expanded` (icon beside label in a pill; the rail is as wide as its widest item, 220–360dp). The change is a spring morph.

NavigationRailItem can also be used standalone (outside a rail) with its own selected / expanded props.

PropTypeDefaultDescription
badgeReactNode—Badge on the icon: content (e.g. `"3"`) for a large badge, `true` for a small dot.
badgeLabelstring—Accessible text for the badge, announced after the label. Defaults to `"{badge} new notifications"` for a counting badge and `"New notification"` for a dot.
expandedboolean—Layout when used standalone (outside a `NavigationRail`): `false` = collapsed (icon over label), `true` = expanded (icon beside label in a pill). Toggling runs the same spring morph the rail uses. Inside a `NavigationRail` this is ignored — the rail's `variant` governs.
icon *ReactNode—The item icon (outlined, per m3.material.io, when `selectedIcon` is given).
labelReactNode—The item label. Without a label, give the item an `aria-label` (which then also replaces the announced badge text).
selectedboolean—Selected state when used standalone (outside a `NavigationRail`).
selectedIconReactNode—Icon shown while this destination is selected — typically the filled version of `icon` (m3: filled for the selected destination, outlined for the rest). Falls back to `icon`.
value *string—Destination value; selected state is derived from the parent rail.