Navigation Bar
Navigation bars provide access to primary destinations at the bottom of the screen.
Navigation bars offer a persistent and convenient way to switch between primary destinations in an app. They are typically fixed at the bottom of the screen and contain 3 to 5 destinations.
Introduction
The MD3 Expressive Navigation Bar is designed for modern, multi-device interfaces. It intelligently adapts its layout based on screen width, ensuring that navigation remains ergonomic and efficient on everything from compact smartphones to large spatial panels.
Anatomy
- Container: The bottom-anchored wrapper that holds navigation destinations. Supports elevation and blur effects.
- Navigation Destination (Item): A clickable area representing a primary destination.
- Active Indicator: A pill or expressive shape background (
pill,circle,sunny,flower,star, etc.) of the currently selected destination. - Icon: Represents the destination's purpose. Supports fill-state animation on selection.
- Label Text: Short text describing the destination.
- Badge (Optional): Numeric or dot indicator for notifications (e.g., unread messages).
Variants
Flexible (Default)
The flexible variant is the recommended MD3 Expressive default. It uses a responsive layout:
- Compact screens (< 600px): Vertical layout (Icon above Label).
- Medium screens (≥ 600px): Horizontal layout (Icon next to Label) to save vertical space.
Expressive Indicator Shapes
Customize active indicators with expressive shapes (pill, circle, sunny, flower, star, soft-square, rounded-rect) at the bar level or per destination item.
Baseline
The baseline variant follows the standard Material Design 3 height (80dp) and always maintains a vertical item layout, regardless of screen width. Use this for a consistent classic mobile look.
XR (Spatial)
The xr variant is designed for spatial and immersive interfaces. It renders as a floating orbiter (pill-shaped container) that is detached from the screen edges, ideal for AR/VR or premium spatial desktop apps.
Label Visibility
Control text label visibility across destinations:
labeled(default): Always displays labels below icons.auto: Shows label text only for the currently active/selected item.unlabeled: Icon-only navigation bar (labels hidden for all items).
Features
Hide on Scroll
To maximize screen real estate, enable hideOnScroll. The navigation bar will slide out of view when scrolling down and reappear instantly when scrolling up.
Accessibility Note: Automatically disabled if prefers-reduced-motion is active or a screen reader is detected.
Automatic Icon Animation
If you use the <Icon> component from @bug-on/m3-expressive, the icon will automatically animate from outlined to filled when selected.
Custom Animation
You can customize the spring behavior of the active indicator pill using the activeIndicatorTransition prop. This allows for even more expressive transitions.
<NavigationBar
activeIndicatorTransition={{
type: "spring",
bounce: 0.7,
duration: 0.6
}}
>
{/* items */}
</NavigationBar>
Usage
Basic Usage
import { NavigationBar, NavigationBarItem, Icon } from "@bug-on/m3-expressive";
export function App() {
return (
<NavigationBar>
<NavigationBarItem
icon={<Icon name="home" />}
label="Home"
selected={true}
/>
<NavigationBarItem
icon={<Icon name="search" />}
label="Search"
/>
<NavigationBarItem
icon={<Icon name="person" />}
label="Profile"
/>
</NavigationBar>
);
}
Expressive Indicator Shapes & Size
<NavigationBar shape="flower" shapeSize={48} labelVisibility="auto">
<NavigationBarItem
icon={<Icon name="home" />}
label="Home"
selected={true}
/>
<NavigationBarItem
icon={<Icon name="explore" />}
label="Explore"
/>
</NavigationBar>
Controlled Navigation with Badges
<NavigationBarItem
icon={<Icon name="notifications" />}
label="Notifications"
selected={currentTab === 'notifs'}
onClick={() => setTab('notifs')}
badge="12"
/>
Next.js & Router Integration (asChild)
Use asChild on NavigationBarItem to delegate element wrapping to client-side routing components like Next.js <Link> without adding redundant DOM nodes:
import Link from "next/link";
import { NavigationBar, NavigationBarItem, Icon } from "@bug-on/m3-expressive";
<NavigationBar>
<NavigationBarItem
icon={<Icon name="home" />}
label="Home"
selected={pathname === "/"}
asChild
>
<Link href="/" />
</NavigationBarItem>
<NavigationBarItem
icon={<Icon name="search" />}
label="Search"
selected={pathname === "/search"}
asChild
>
<Link href="/search" />
</NavigationBarItem>
</NavigationBar>
Best Practices
Do
- Use between 3 and 5 destinations. For more than 5, consider a Navigation Rail or Modal Drawer.
- Use short, clear labels (1-2 words).
- Place the most frequently used destination in the leftmost or center position.
Don't
- Don't use a Navigation Bar for secondary actions or settings.
- Don't use labels that wrap to multiple lines.
- Avoid using purely decorative icons without labels unless the meaning is universally understood.
Accessibility
- Keyboard Support: Full tab order. Items are marked with
role="menuitem". - Active State: The selected item uses
aria-current="page". - Labels: If a label is not provided or hidden,
aria-labelautomatically uses the label text to maintain accessibility for screen readers. - Hiding: When hidden on scroll, the bar is removed from the tab order to prevent focus trapping.
API Reference
NavigationBar
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "flexible" | "baseline" | "xr" | "flexible" | The visual style and responsive behavior. |
itemLayout | "vertical" | "horizontal" | — | Overrides automatic responsive layout. |
shape | "pill" | "circle" | "sunny" | "flower" | "star" | "soft-square" | "rounded-rect" | MD3ShapeName | "pill" | Default shape for active indicators. |
shapeSize | number | 56 | Shape indicator container size in px (for square/custom shapes). |
labelVisibility | "labeled" | "auto" | "unlabeled" | "labeled" | Label visibility mode below icon for all items. |
hideOnScroll | boolean | false | Slides the bar out of view on scroll down. |
elevated | boolean | false | Adds a subtle top shadow (or full shadow for XR). |
fixed | boolean | true | Whether to fix to the viewport or stay absolute. |
scrollContainerRef | RefObject | — | The element to track for scroll hiding. |
activeIndicatorTransition | Transition | — | Custom spring transition for active indicators. |
itemClassName | string | — | Additional CSS classes for navigation bar items. |
NavigationBarItem
| Prop | Type | Default | Description |
|---|---|---|---|
selected | boolean | false | Marks the item as the active destination. |
icon | ReactNode | — | Icon element. Animates if using library <Icon>. |
label | ReactNode | — | Descriptive text label. |
shape | NavigationItemShape | — | Per-item indicator shape override. |
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 numeric or dot indicator. |
disabled | boolean | false | Mutes appearance and blocks interaction. |
onClick | () => void | — | Selection handler. |
aria-label | string | — | Custom accessibility label. |
asChild | boolean | false | Merges component behavior onto a single child element (e.g. Next.js <Link>). |