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.
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.
Modal
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.
<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 anaria-label. Items are native<button>s; the active one carriesaria-current="page". - Tab order: header (menu button, FAB) first, then the destinations.
- Badges are announced after the label (
badgeLabelto customize). An item without alabelneeds anaria-label. - The open modal rail is a
dialog(aria-modal) named bymodalLabel(default "Navigation rail"); focus returns to the opener when it collapses.
Props
NavigationRail
| Prop | Type | Default | Description |
|---|---|---|---|
arrangement | enum | top | Vertical placement of the destinations below the header. |
defaultValue | string | — | Uncontrolled initial destination value. |
header | ReactNode | — | Optional header content (e.g. a menu button and/or FAB) pinned at the top. |
hideOnCollapse | boolean | false | With `modal`: hide the rail entirely while collapsed; expanding slides the expanded rail in from the leading edge. Ignored without `modal`. |
modal | boolean | false | Modal 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`). |
modalLabel | string | Navigation rail | Accessible 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. |
value | string | — | Controlled selected destination value. |
variant | enum | collapsed | `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
NavigationRailItem can also be used standalone (outside a rail) with its
own selected / expanded props.
| Prop | Type | Default | Description |
|---|---|---|---|
badge | ReactNode | — | Badge on the icon: content (e.g. `"3"`) for a large badge, `true` for a small dot. |
badgeLabel | string | — | Accessible text for the badge, announced after the label. Defaults to `"{badge} new notifications"` for a counting badge and `"New notification"` for a dot. |
expanded | boolean | — | 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). |
label | ReactNode | — | The item label. Without a label, give the item an `aria-label` (which then also replaces the announced badge text). |
selected | boolean | — | Selected state when used standalone (outside a `NavigationRail`). |
selectedIcon | ReactNode | — | 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. |