MD3
Expressive
MATERIAL DESIGN 3 EXPRESSIVE

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.
Loading demo...

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.

Loading demo...

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.

Loading demo...

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.

Loading demo...

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).
Loading demo...

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.

Loading demo...

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-label automatically 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

PropTypeDefaultDescription
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.
shapeSizenumber56Shape indicator container size in px (for square/custom shapes).
labelVisibility"labeled" | "auto" | "unlabeled""labeled"Label visibility mode below icon for all items.
hideOnScrollbooleanfalseSlides the bar out of view on scroll down.
elevatedbooleanfalseAdds a subtle top shadow (or full shadow for XR).
fixedbooleantrueWhether to fix to the viewport or stay absolute.
scrollContainerRefRefObjectThe element to track for scroll hiding.
activeIndicatorTransitionTransitionCustom spring transition for active indicators.
itemClassNamestringAdditional CSS classes for navigation bar items.
PropTypeDefaultDescription
selectedbooleanfalseMarks the item as the active destination.
iconReactNodeIcon element. Animates if using library <Icon>.
labelReactNodeDescriptive text label.
shapeNavigationItemShapePer-item indicator shape override.
shapeSizenumberOverride shape container size for this item (px).
hideLabelbooleanfalseHide label at individual item level (retains aria-label).
badgeReactNodeOptional numeric or dot indicator.
disabledbooleanfalseMutes appearance and blocks interaction.
onClick() => voidSelection handler.
aria-labelstringCustom accessibility label.
asChildbooleanfalseMerges component behavior onto a single child element (e.g. Next.js <Link>).