Navigation Rail
Navigation rails provide ergonomic lateral navigation for medium-to-large screens.
Navigation rails show primary destinations on the side of a screen. They help users navigate between distinct app sections at the top hierarchical level. Expressive navigation rails feature spring-based active indicator expansion, custom M3 shapes, and smooth hover ripples.
Introduction
The MD3 Expressive Navigation Rail is a versatile navigation component that scales with screen size. It transitions from a compact icon-only rail to an expanded sidebar with labels, or even a floating spatial orbiter. It is optimized for lateral navigation on tablets, foldable devices, and desktop environments.
Anatomy
- Container: The vertical wrapper anchored to the left (or right) side of the screen.
- Header: Optional slot at the top, typically containing a menu toggle button (
IconButton) or brand icon. - FAB Slot: A dedicated area for a primary action (Floating Action Button).
- Navigation Item: A destination within the rail, consisting of an icon and an optional label.
- Active Indicator: A shape-morphing pill or SVG background (
pill,circle,sunny,flower,star, etc.) that animates to highlight the active destination. - Footer: Optional slot at the bottom for secondary actions or profile links.
Variants
Collapsed & Expanded
Click the menu button in the rail header to toggle smoothly between collapsed (icon-only) and expanded (sidebar with labels) states.
Expressive Shapes
Customize the active indicator shape across the entire rail using the shape prop or per-item shape overrides. Supports M3 Expressive shapes like sunny, flower, star, circle, and pill.
Modal
A special variant for compact screens where the rail behaves like a drawer, sliding in over a scrim while maintaining rail-specific item layouts.
XR (Spatial)
Designed for spatial interfaces, this variant floats away from the screen edge. It supports both "Contained" and "Spatialized" layouts for the FAB.
Features
Label Visibility
Control how labels appear in collapsed mode:
- Labeled: Always visible (default).
- Auto: Visible only for the active item.
- Unlabeled: Icons only.
Adaptive Width
Use the narrow prop to reduce the rail width for very tight layouts or strict tablet split-screens.
Custom Animation
You can customize the spring behavior of the active indicator pill using the activeIndicatorTransition prop. This allows for even more expressive transitions.
<NavigationRail
activeIndicatorTransition={{
type: "spring",
bounce: 0.6,
duration: 0.5
}}
>
{/* items */}
</NavigationRail>
Usage
Basic Usage
import { NavigationRail, NavigationRailItem, Icon, IconButton } from "@bug-on/m3-expressive";
<NavigationRail header={<IconButton icon="menu" />}>
<NavigationRailItem icon={<Icon name="home" />} label="Home" selected />
<NavigationRailItem icon={<Icon name="search" />} label="Search" />
<NavigationRailItem icon={<Icon name="settings" />} label="Settings" />
</NavigationRail>
Expressive Indicator Shape
<NavigationRail shape="flower" header={<IconButton icon="menu" />}>
<NavigationRailItem icon={<Icon name="home" />} label="Home" selected />
<NavigationRailItem icon={<Icon name="search" />} label="Search" />
</NavigationRail>
With FAB & Header Toggle
<NavigationRail
variant={isExpanded ? "expanded" : "collapsed"}
header={
<IconButton
icon={isExpanded ? "menu_open" : "menu"}
onClick={() => setIsExpanded(!isExpanded)}
/>
}
fab={<FAB icon={<Icon name="add" />} extended={isExpanded}>Compose</FAB>}
>
<NavigationRailItem icon={<Icon name="home" />} label="Home" selected />
<NavigationRailItem icon={<Icon name="search" />} label="Search" />
</NavigationRail>
Next.js & Router Integration (asChild)
Use asChild on NavigationRailItem to seamlessly integrate with client-side routing components like Next.js <Link> or React Router <Link>:
import Link from "next/link";
import { NavigationRail, NavigationRailItem, Icon } from "@bug-on/m3-expressive";
<NavigationRail>
<NavigationRailItem
icon={<Icon name="home" />}
label="Home"
selected={pathname === "/"}
asChild
>
<Link href="/" />
</NavigationRailItem>
<NavigationRailItem
icon={<Icon name="settings" />}
label="Settings"
selected={pathname === "/settings"}
asChild
>
<Link href="/settings" />
</NavigationRailItem>
</NavigationRail>
Best Practices
Do
- Use for applications with 3 to 7 primary destinations.
- Keep the most important action in the FAB slot at the top.
- Provide labels unless the icons are universally understood.
- Use header toggle button to allow users to expand/collapse the rail.
Don't
- Don't use a Navigation Rail and a Navigation Bar simultaneously for the same destinations.
- Don't use long labels that might wrap or be cut off.
- Avoid placing too many items in the footer; keep it for 1-2 secondary actions.
Accessibility
- Keyboard: Uses a roving tabindex for efficient navigation with Arrow keys.
- Roles: The container uses
role="navigation"and the item list usesrole="menubar". - States: Selected items use
aria-current="page". - Touch: All interactive elements meet the MD3 48x48dp minimum touch target requirement.
API Reference
NavigationRail
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "collapsed" | "expanded" | "modal" | "xr" | "collapsed" | Visual style and behavior. |
labelVisibility | "labeled" | "auto" | "unlabeled" | "labeled" | Controls label display in collapsed mode. |
shape | "pill" | "circle" | "sunny" | "flower" | "star" | "soft-square" | "rounded-rect" | MD3ShapeName | "pill" | Active indicator shape. |
shapeSize | number | 56 | Shape indicator container size in px (for square/custom shapes). |
narrow | boolean | false | Sets a narrower width (56dp vs 80dp). |
header | ReactNode | — | Content at the top (e.g., Menu toggle button). |
fab | ReactNode | — | Slot for a Floating Action Button. |
footer | ReactNode | — | Content at the bottom. |
open | boolean | false | Controls visibility for the modal variant. |
onClose | () => void | — | Triggered when the modal scrim is clicked. |
fabPlacement | "contained" | "spatialized" | "contained" | FAB position in xr variant. |
activeIndicatorTransition | Transition | — | Custom spring transition for the active indicator. |
NavigationRailItem
| Prop | Type | Default | Description |
|---|---|---|---|
selected | boolean | false | Marks the item as active. |
icon | ReactNode | — | Icon node (animates if library <Icon>). |
label | ReactNode | — | Descriptive text label. |
shape | NavigationItemShape | — | Per-item shape override for indicator. |
shapeSize | number | — | Override shape container size for this item (px). |
hideLabel | boolean | false | Hide label at individual item level (retains aria-label). |
badge | ReactNode | — | Optional badge (number or dot). |
disabled | boolean | false | Disables interaction. |
onClick | () => void | — | Selection handler. |
asChild | boolean | false | Merges component behavior onto a single child element (e.g. Next.js <Link>). |