Design System

Navigation & page-level patterns

How the V2 navigation components combine into an application shell and a page: sidebar, top navigation, mobile drawer, breadcrumbs, tabs, page header, page sections and the filter bar. Navigation is declared once as data and rendered onto every surface.

What is this?

A single application shell plus a repeatable page structure. The shell owns global navigation; the page owns identity, related views and content.

When to use it

  • Building any authenticated application screen
  • Adding a new area to the product navigation
  • Composing a page from a header, related views and sections

How to use it

  • Declare destinations once as AppNavSection data
  • Give the page one PageHeader and one Tabs group at most
  • Group content with PageSection, refine it with FilterBar

What to avoid

  • A second navigation system for the same purpose
  • Tabs standing in for primary navigation
  • Per-screen widths, spacing or active-state styling

Navigation differentiation

Each pattern answers one question. They are not interchangeable.

Which navigation pattern to reach for
PatternAnswersUse it forNever use it for
SidebarWhere can I go?Primary application areasActions or view switching
Top navigationWhat is global?Brand, search, notifications, accountDeep hierarchy
BreadcrumbWhere am I?Location within a hierarchyPrimary navigation
TabsWhich view of this?Related views of one record or listMoving between app areas
Page headerWhat is this page?Title, description, primary actionFiltering content
Filter barWhich subset?Search, filters, active filter chipsNavigating away

Why it is designed this way

Navigation is declared as data, once. The shell renders the same destinations into the sidebar, the collapsed rail, the header links and the mobile drawer, so the active state and permission gating can never disagree between surfaces.

Application shell

Top navigation, collapsible sidebar, mobile drawer and exactly one main region.

AppShell

The V2 application chrome. Declare navigation as data; the shell renders it to every surface and owns the responsive behaviour.

import { AppShell, type AppNavSection } from "@/design-system";

When to use

  • Every authenticated application screen
  • Whenever navigation must stay identical across breakpoints
  • When sections are permission-gated

When not to use

  • Marketing or unauthenticated pages with no app navigation
  • Inside a dialog or drawer

Residents — complete shell

Collapse the sidebar from its footer control; resize the preview to see the header links collapse into the drawer trigger.

Residents

Manage residents and their information.

4 records

All residents

Search, filter and open a resident record.

ResidentRoomServiceStatus
Amelia HartA-104Personal careActive
Ravi MenonB-212Day supportActive
Grace WhitfieldC-006RespiteInactive
Tomas BergA-118Personal careActive

Navigation data

const NAV: AppNavSection[] = [
  { items: [
    { id: "dashboard", label: "Dashboard", icon: <LayoutDashboard aria-hidden /> },
    { id: "residents", label: "Residents", icon: <Users aria-hidden />,
      children: [{ id: "residents-active", label: "Active" }] },
    { id: "reports", label: "Reports", icon: <BarChart3 aria-hidden />,
      visible: can("reports.read") },
  ] },
];

Accessibility

  • One <main> per page; the sidebar and header are separate landmarks with accessible names.
  • The current destination sets aria-current="page", so the active page is programmatically identifiable — not colour-only.
  • Collapsed rail items keep their label in a Tooltip and in screen-reader-only text.
  • The mobile drawer reuses the existing Drawer: focus trapping, Escape and focus restoration come from one place.
  • Permission-gated destinations are removed, never announced as unreachable.

Page structure

One header, one optional tab group, then sections.

PageSection

One band of page content: title, description, actions and body. Structural only — no surface, so it can hold a table, a card grid or a form without stacking containers.

import { PageSection } from "@/design-system";

When to use

  • Grouping related content inside a page
  • Giving a block of content its own heading and action

When not to use

  • As a card — use Card when the content needs a surface
  • For page identity — that is PageHeader

Section with an action

Recent activity

Latest activity across residents.

Activity list, table or card grid goes here.

Accessibility

  • Renders a <section> with a real heading; set `as` so the outline stays sequential.
  • Section actions are ordinary Buttons and keep the natural tab order.

Responsive & keyboard behaviour

The same navigation data, three widths.

Navigation behaviour by breakpoint
SurfaceDesktop (lg+)Tablet (md)Mobile (<md)
SidebarInline column, expanded or icon railHidden — drawerHidden — drawer
Header linksVisibleVisibleHidden — drawer
Global searchIn the headerIn the headerHidden — page-level search
FiltersInline filter barInline filter barFilter toggle / drawer
TabsInlineInlineHorizontally scrollable
Keyboard behaviour
ContextKeysResult
NavigationTab / Shift+TabMove between interactive elements in reading order
Sidebar branchEnter / SpaceExpand or collapse nested destinations
TabsArrow keysMove between tabs; Enter/Space activates
MenusArrow keys, Home/EndNavigate items; Escape closes
Mobile drawerEscapeCloses and restores focus to the trigger

Do

  • Declare destinations once and pass them to AppShell.
  • Hide destinations the user cannot access with `visible: false`.
  • Keep one PageHeader and at most one tab group per page.
  • Use Container for measure: standard content, wide data pages, full-width tables.
  • Reuse the existing loading, empty, no-results and error states for page-level feedback.

Don't

  • Don't build a second sidebar, drawer, menu or tooltip.
  • Don't use tabs as primary navigation, or breadcrumbs as a menu.
  • Don't style the active state per page, or signal it with colour alone.
  • Don't nest scrolling regions unless the content genuinely requires it.
  • Don't invent page widths, spacing values, breakpoints or z-index layers.