Navigation containers
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 - *er
item* 24 px icon + 12 px gap + label (`labelmedium`) - *elected* pill
shaped 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
- *er
item* 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-containerbg - *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* full
width 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.
R7 — Footer content
| 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 swipe
todismiss - Always reflects current destination
- Can be collapsed to rail via prop (user
controlled or layoutdriven)
R10 — Accessibility
- Container:
role="navigation"+aria-label="Primary" - Each destination:
role="link"(if it navigates URL) ORrole="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
tocontent link before nav for screen readers (matches appbar 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)
Cross-link
app-layout/window-size-classes.kmd— adaptive container per classapp-layout/safe-area.kmd— bar bottom inset rulethemes/elevation.kmd— modal drawer scrim rolethemes/color-roles.kmd—secondary-containerfor selectedthemes/typography.kmd—label-medium/label-largeinteraction/states.kmd— hover / focused / selected layerscomponents/badges.kmd— per-destination badge contractcomponents/tabs.kmd— sibling for *ithin-context*switching(NOT for top-level destinations)
components/app-bars.kmd— modal drawer trigger lives in top barfoundations/elements.kmd— Container + Navigator families