Navigation containers

mandatory

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.

Spec — Navigation containers

Facet *isual*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

Class Container Anchor Destinations visible
*ompact*(< 600 dp) Navigation *ar* Bottom edge 3-5
*edium*(600-839 dp) Navigation *ail* Left edge 3-7
*xpanded*(840-1199 dp) Rail (default) or *rawer*(expanded) Left edge 3-7 / unlimited
*arge*(≥ 1200 dp) *rawer*(standard) Left edge unlimited

*ame 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                       │
└──────────────────────────────────────────────┘
  • *eight* 80 px (incl. safe-area bottom inset)
  • *tems* 3-5 destinations (never 1 or 2 — use back nav or tabs;

    never > 5 — use rail / drawer)

  • *ontainer bg* surface-container
  • *eritem* 24 px icon + 12 px gap + label (`labelmedium`)
  • *elected* pillshaped tonal `secondarycontainer` behind icon +

    bold label color

  • Optional: badge on icon (per components/badges.kmd)
  • *afe 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
└──────┘
  • *idth* 80 dp
  • *tems* 3-7 destinations
  • *eritem* 24 px icon + 4 px gap + 1line label

    (label-medium, can be hidden via prop)

  • *elected indicator* full-width tonal block behind item (height

    56 px), with secondary-container bg

  • *ptional FAB* anchored to top (after menu button) OR bottom
  • *eader* optional menu / app icon button at top

R3 — Navigation drawer (Expanded / Large)

Two sub-variants:

Sub-variant Modal Use
*odal drawer* Yes (scrim) Overlay drawer triggered by menu icon
*tandard drawer* No Always-visible side panel (≥ Large)

Anatomy (standard drawer):

┌─────────────────────┐
│  App name           │
│ ──────────────────  │
│  ⌂   Home           │
│  ⌥   Inbox      • 3 │
│  ⚙   Settings       │
│ ──────────────────  │
│  Recent             │
│  📄  Document 1     │
│  📄  Document 2     │
│  📄  Document 3     │
│ ──────────────────  │
│  + New              │
└─────────────────────┘
  • *idth* 256-360 dp (default 320 dp)
  • *tems* fullwidth list rows; selected has `secondarycontainer`

    tonal bg

  • *er-item* 24 px leading icon + 12 px gap + label

    (label-large) + optional trailing badge

  • *ection headers* subheader divider (per dividers.kmd § R5)
  • *ontainer bg* surface-container-low (standard) /

    surface-container (modal, with elevation)

R4 — Consistent state across variants

Concept Behavior
Selected destination Same destination highlighted regardless of container
Badge Same badge count / dot across variants
Order Same destination order across variants
Icons Same 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

Container Animation
Bar Tonal pill animates between icons (motion-medium)
Rail Tonal block fades in/out under new selected (motion-fast)
Drawer Tonal bg fades to selected row (motion-fast)

Reduced motion: instant change, no slide.

R6 — Header content

Container Header
Bar None
Rail Optional menu / app icon button at top
Drawer App name + optional user / org switcher

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

Container Footer
Bar None
Rail Optional FAB or settings icon at bottom
Drawer Optional 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 swipetodismiss
  • Always reflects current destination
  • Can be collapsed to rail via prop (usercontrolled or layoutdriven)

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
  • Skiptocontent link before nav for screen readers (matches app

    bar contract)

R11 — States

State Visual change
Rest Base, icon on-surface-variant, label on-surface
Hover State layer 8% under icon / row
Pressed State layer 12%
Focused 2 px focus ring on item OR ring outside tonal block
Selected Tonal secondary-container bg + icon on-secondary-container + label weight 600
Disabled 38% opacity (rare on primary nav; usually use sub-screen disabling)

R12 — Density

Density Bar height Rail width Drawer width
Compact 64 px 72 dp 256 dp
Default 80 px 80 dp 320 dp
Comfortable 96 px 96 dp 360 dp

R13 — Per-preset variation

Preset Bar Rail Drawer
material3 Pill tonal under icon Tonal block, optional labels Tonal selected row, FAB-style add
material2 Solid bg, no pill (just color change on selected) No tonal block Filled bg per item
ios_cupertino Tab bar style, label always visible n/a (use bar) Source list with indent levels
gnome n/a (desktop preset; use rail) Adwaita header bar w/ pages Sidebar style w/ accent on selected
windows_11 n/a (desktop preset) NavigationView compact w/ icons + label below NavigationView expanded
brutalist Sharp icons, thick border-top Solid block selected, 2 px right border Heavy 4 px borders between rows
terminal_classic Function key row [F1 Home] [F2 Inbox] n/a Numbered 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 *ithin-context*switching

    (NOT for top-level destinations)

  • components/app-bars.kmd — modal drawer trigger lives in top bar
  • foundations/elements.kmd — Container + Navigator families

Source: ../home/koder/dev/koder/meta/docs/stack/specs/components/navigation.kmd