MD3
Expressive
MATERIAL DESIGN 3 EXPRESSIVE

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.

Loading demo...

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.

Loading demo...

A special variant for compact screens where the rail behaves like a drawer, sliding in over a scrim while maintaining rail-specific item layouts.

Loading demo...

XR (Spatial)

Designed for spatial interfaces, this variant floats away from the screen edge. It supports both "Contained" and "Spatialized" layouts for the FAB.

Loading demo...

Features

Label Visibility

Control how labels appear in collapsed mode:

  • Labeled: Always visible (default).
  • Auto: Visible only for the active item.
  • Unlabeled: Icons only.
Loading demo...

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 uses role="menubar".
  • States: Selected items use aria-current="page".
  • Touch: All interactive elements meet the MD3 48x48dp minimum touch target requirement.

API Reference

PropTypeDefaultDescription
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.
shapeSizenumber56Shape indicator container size in px (for square/custom shapes).
narrowbooleanfalseSets a narrower width (56dp vs 80dp).
headerReactNodeContent at the top (e.g., Menu toggle button).
fabReactNodeSlot for a Floating Action Button.
footerReactNodeContent at the bottom.
openbooleanfalseControls visibility for the modal variant.
onClose() => voidTriggered when the modal scrim is clicked.
fabPlacement"contained" | "spatialized""contained"FAB position in xr variant.
activeIndicatorTransitionTransitionCustom spring transition for the active indicator.
PropTypeDefaultDescription
selectedbooleanfalseMarks the item as active.
iconReactNodeIcon node (animates if library <Icon>).
labelReactNodeDescriptive text label.
shapeNavigationItemShapePer-item shape override for indicator.
shapeSizenumberOverride shape container size for this item (px).
hideLabelbooleanfalseHide label at individual item level (retains aria-label).
badgeReactNodeOptional badge (number or dot).
disabledbooleanfalseDisables interaction.
onClick() => voidSelection handler.
asChildbooleanfalseMerges component behavior onto a single child element (e.g. Next.js <Link>).