Skip to content

Navigation containers

components specs/components/navigation.kmd

Primary navigation surfaces for switching between top-level destinations — Navigation bar (bottom, mobile), Navigation rail (side, tablet), and Navigation drawer (side, desktop / wide). Material parity (`/components/navigation-bar`, `/components/navigation-drawer`, `/components/navigation-rail`). These three are ONE adaptive component family, picked by window-size class.

When this spec applies

Primary triggers

All triggers

Specification body

Spec — Navigation containers

Facet Visual of Koder Design. Material parity: https://m3.material.io/components/navigation-bar, https://m3.material.io/components/navigation-rail, https://m3.material.io/components/navigation-drawer.

Adaptive trio — pick by window-size class

ClassContainerAnchorDestinations visible
Compact (< 600 dp)Navigation barBottom edge3-5
Medium (600-839 dp)Navigation railLeft edge3-7
Expanded (840-1199 dp)Rail (default) or drawer (expanded)Left edge3-7 / unlimited
Large (≥ 1200 dp)Drawer (standard)Left edgeunlimited

Same component family — destinations / icons / order stay consistent; only the layout container swaps. Implementations must share state (selected destination, badges) across the three.

R1 — Navigation bar (Compact)

Anatomy:

┌──────────────────────────────────────────────┐
│  ⌂        ⌥        ⚙                          │
│ Home    Inbox   Settings                       │
└──────────────────────────────────────────────┘
  • Height: 80 px (incl. safe-area bottom inset)
  • Items: 3-5 destinations (never 1 or 2 — use back nav or tabs; never > 5 — use rail / drawer)
  • Container bg: surface-container
  • Per-item: 24 px icon + 12 px gap + label (label-medium)
  • Selected: pill-shaped tonal secondary-container behind icon + bold label color
  • Optional: badge on icon (per components/badges.kmd)
  • Safe area: respects gesture-nav inset on Android / iOS

R2 — Navigation rail (Medium / Expanded compact)

Anatomy:

┌──────┐
│  ☰   │  ← optional menu button
│  ────│
│  ⌂   │
│ Home │
│      │
│  ⌥   │
│ Inbox│
│      │
│  ⚙   │
│ Set… │
│  ────│
│  ⊕   │  ← optional FAB at top OR bottom
└──────┘
  • Width: 80 dp
  • Items: 3-7 destinations
  • Per-item: 24 px icon + 4 px gap + 1-line label (label-medium, can be hidden via prop)
  • Selected indicator: full-width tonal block behind item (height 56 px), with secondary-container bg
  • Optional FAB: anchored to top (after menu button) OR bottom
  • Header: optional menu / app icon button at top

R3 — Navigation drawer (Expanded / Large)

Two sub-variants:

Sub-variantModalUse
Modal drawerYes (scrim)Overlay drawer triggered by menu icon
Standard drawerNoAlways-visible side panel (≥ Large)

Anatomy (standard drawer):

┌─────────────────────┐
│  App name           │
│ ──────────────────  │
│  ⌂   Home           │
│  ⌥   Inbox      • 3 │
│  ⚙   Settings       │
│ ──────────────────  │
│  Recent             │
│  📄  Document 1     │
│  📄  Document 2     │
│  📄  Document 3     │
│ ──────────────────  │
│  + New              │
└─────────────────────┘
  • Width: 256-360 dp (default 320 dp)
  • Items: full-width list rows; selected has secondary-container tonal bg
  • Per-item: 24 px leading icon + 12 px gap + label (label-large) + optional trailing badge
  • Section headers: subheader divider (per dividers.kmd § R5)
  • Container bg: surface-container-low (standard) / surface-container (modal, with elevation)

R4 — Consistent state across variants

ConceptBehavior
Selected destinationSame destination highlighted regardless of container
BadgeSame badge count / dot across variants
OrderSame destination order across variants
IconsSame icon glyph; can adapt label visibility (rail can hide labels)

Implementations expose a single "destinations" source of truth; the view layer renders the appropriate container per window-size class.

R5 — Selected state animation

ContainerAnimation
BarTonal pill animates between icons (motion-medium)
RailTonal block fades in/out under new selected (motion-fast)
DrawerTonal bg fades to selected row (motion-fast)

Reduced motion: instant change, no slide.

R6 — Header content

ContainerHeader
BarNone
RailOptional menu / app icon button at top
DrawerApp name + optional user / org switcher

Drawer header is title-medium (16/24, weight 500); app icon 32 px to the left.

ContainerFooter
BarNone
RailOptional FAB or settings icon at bottom
DrawerOptional settings / sign-out section, separated by divider

R8 — Modal drawer mode

Modal drawer slides in from the edge with a scrim (40% black overlay on the rest of the page).

  • Trigger: menu icon button in app bar ()
  • Dismiss: scrim tap, Esc key, swipe-left gesture, menu icon again
  • Width: same 256-360 dp
  • Z-order: above app bar, above bottom bar, below dialogs

Use modal drawer on Compact / Medium classes when standard drawer would dominate the screen. Switch to standard drawer at Expanded / Large by default.

R9 — Standard drawer mode

Always visible alongside main content. Used at Expanded / Large classes for primary navigation.

  • No scrim
  • No swipe-to-dismiss
  • Always reflects current destination
  • Can be collapsed to rail via prop (user-controlled or layout-driven)

R10 — Accessibility

  • Container: role="navigation" + aria-label="Primary"
  • Each destination: role="link" (if it navigates URL) OR role="button" (if it triggers in-app routing)
    • aria-current="page" on selected destination
  • Badges: included in destination's aria-label ("Inbox, 3 unread")
  • Modal drawer trigger: aria-haspopup="dialog" + aria-expanded
  • Modal drawer container: role="dialog" + aria-modal="true" + aria-labelledby (pointing to drawer header) when open
  • Keyboard:
    • Tab: enters first destination
    • Arrow Up / Down (rail / drawer) OR Left / Right (bar): moves between destinations
    • Enter / Space: navigates
    • Esc: closes modal drawer
  • Skip-to-content link before nav for screen readers (matches app bar contract)

R11 — States

StateVisual change
RestBase, icon on-surface-variant, label on-surface
HoverState layer 8% under icon / row
PressedState layer 12%
Focused2 px focus ring on item OR ring outside tonal block
SelectedTonal secondary-container bg + icon on-secondary-container + label weight 600
Disabled38% opacity (rare on primary nav; usually use sub-screen disabling)

R12 — Density

DensityBar heightRail widthDrawer width
Compact64 px72 dp256 dp
Default80 px80 dp320 dp
Comfortable96 px96 dp360 dp

R13 — Per-preset variation

PresetBarRailDrawer
material3Pill tonal under iconTonal block, optional labelsTonal selected row, FAB-style add
material2Solid bg, no pill (just color change on selected)No tonal blockFilled bg per item
ios_cupertinoTab bar style, label always visiblen/a (use bar)Source list with indent levels
gnomen/a (desktop preset; use rail)Adwaita header bar w/ pagesSidebar style w/ accent on selected
windows_11n/a (desktop preset)NavigationView compact w/ icons + label belowNavigationView expanded
brutalistSharp icons, thick border-topSolid block selected, 2 px right borderHeavy 4 px borders between rows
terminal_classicFunction key row [F1 Home] [F2 Inbox]n/aNumbered side menu 1) Home 2) Inbox

R14 — Forbidden patterns

  • ❌ Navigation bar on Expanded / Large window-size class (use rail or drawer)
  • ❌ Navigation drawer on Compact (use bar or modal drawer)
  • ❌ Mixing bar + drawer on the same screen (pick one container)
  • ❌ Navigation bar with 1 or 2 items (use back navigation or tabs)
  • ❌ Navigation bar with > 5 items (overflow to drawer / rail)
  • ❌ Tab bar pattern (URL-distinct destinations as Tabs component) — use nav containers when destinations are top-level
  • ❌ Navigation rail without selected indicator (defeats purpose)
  • ❌ Standard drawer on Compact (consumes too much screen real estate)
  • ❌ Navigation that hides on scroll (primary nav must always be reachable; secondary or sticky toolbars can hide)
  • ❌ Bar / rail with destinations that route to different orgs / tenants (use tenant switcher in drawer header instead)
  • app-layout/window-size-classes.kmd — adaptive container per class
  • app-layout/safe-area.kmd — bar bottom inset rule
  • themes/elevation.kmd — modal drawer scrim role
  • themes/color-roles.kmdsecondary-container for selected
  • themes/typography.kmdlabel-medium / label-large
  • interaction/states.kmd — hover / focused / selected layers
  • components/badges.kmd — per-destination badge contract
  • components/tabs.kmd — sibling for within-context switching (NOT for top-level destinations)
  • components/app-bars.kmd — modal drawer trigger lives in top bar
  • foundations/elements.kmd — Container + Navigator families

References