Design System

Navigation & Wayfinding

Tabs, segmented control, breadcrumbs, pagination, sidebar, top navigation, groups, collapsed rails and mobile patterns — one consistent set of V2 interaction states.

What is this?

The V2 navigation family answers three questions on every screen: where am I, what else is here, and how do I get back. Each component owns one of those jobs, and none of them duplicates another.

When to use it

  • Sidebar and Top Navigation for moving between modules.
  • Tabs for sections of one record; Segmented Control for views of one dataset.
  • Breadcrumbs for depth and back-navigation; Pagination for long result sets.

How to use it

  • Signal the active state with weight, surface and an indicator together.
  • Group destinations with quiet headings, not boxes or heavy dividers.
  • Keep counts in Badges and icons in Lucide at the standard icon sizes.

What to avoid

  • Building a second Pagination, Tooltip, Overlay or Button.
  • Letting Tabs and Segmented Control look interchangeable.
  • Shrinking the desktop sidebar to serve as mobile navigation.

Choosing a pattern

Scope decides the component — not how the option list happens to look.

Navigation components by scope and behaviour
ComponentScopeChangesUse for
Sidebar NavigationApp shellThe pageModules: Dashboard, Residents, Services, Assets, Settings
Top NavigationApp shellThe pageBrand, a few destinations, search, notifications, account
TabsOne pageThe panelSections of a record: Overview, Care plan, Activity
Segmented ControlOne regionThe renderingViews of the same data: table / rows, 7 / 30 / 90 days
BreadcrumbsOne pageNothing — it links upDepth and return path on detail pages
PaginationOne data regionThe result windowLong tables and lists
Mobile NavigationApp shell (mobile)The pageBottom bar for top destinations, drawer for the rest

Components

Tabs

Switches between sibling sections of one record or one workspace — each tab reveals a different panel of content while the page context stays the same.

import { Tabs, TabList, Tab, TabPanel } from "@/design-system";

When to use

  • A resident record split into Overview, Care plan, Activity and Documents.
  • Between three and seven peer sections that belong to the same page.
  • Where each section has enough content to stand on its own.

When not to use

  • For switching how the same content is rendered — use Segmented Control.
  • For moving between pages of the app — use Sidebar or Top Navigation.
  • For a single section, or for more than about seven sections.

Line tabs with icons, counts and badges

Amelia Johnson — Willow House, room 14. Admitted 4 March 2025.

Pill and enclosed variants

148 residents across 12 sites.
24 open requests, oldest raised 3 days ago.

Full-width and scrollable

Full width for two or three peers on narrow surfaces; the list scrolls horizontally instead of wrapping when the labels overflow.

Cleaning — weekly, Willow House.
Workspace summary.

Accessibility

  • Radix Tabs provides the tablist/tab/tabpanel roles, roving tab order and arrow-key navigation.
  • The active tab is marked by weight, a 2px indicator and colour together — never colour alone.
  • Disabled tabs stay in the DOM with disabled semantics and a non-interactive cursor.
  • Focus is visible on every trigger via the shared ds-focus-ring token.

Segmented Control

Switches how the same content is presented or filtered — density, date range, list versus table — without changing which content is on screen.

import { SegmentedControl, Segment } from "@/design-system";

When to use

  • Two to five mutually exclusive views of one dataset.
  • Table / card view switches on a list screen.
  • Short ranges such as 7 / 30 / 90 days on a dashboard.

When not to use

  • As primary navigation between pages or record sections.
  • For multi-select filters — use ToggleGroup or Chips.
  • For long option labels that would force the control to wrap.

Two and three or more options

With icons, and full width

Accessibility

  • Built on Radix ToggleGroup: single selection, roving focus and arrow-key movement.
  • An empty selection is never allowed — one segment is always pressed.
  • The selected segment reads through aria-pressed plus a raised surface and stronger text, not colour alone.
  • Icon-only segments still carry a visible or visually-hidden text label.

Pagination

Moves through pages of a list or table. This is the single Pagination implementation in the system — navigation extends it with a footer composition and a page-size control rather than replacing it.

import { Pagination, PaginationBar, PaginationPageSize } from "@/design-system";

When to use

  • Tables and lists longer than one page of results.
  • Below the data region, as the last element of the table card.
  • With a result summary so the user knows the size of the set.

When not to use

  • For fewer results than one page — hide the control entirely.
  • For streams and feeds where infinite scroll is the established pattern.
  • As navigation between unrelated pages of the app.

Page numbers with first / last

Rows-per-page control

Rows per page

Table footer composition

PaginationBar pairs the result summary and page-size control with the same Pagination component.

Showing 51–75 of 288

Rows per page

Accessibility

  • A nav landmark labelled “Pagination” containing a list of page buttons.
  • The current page is marked with aria-current="page" and a filled background.
  • Previous / next / first / last are IconButtons with accessible labels and real disabled state at the ends.
  • The result summary is announced politely when the page changes.

Tabs vs Segmented Control

The two patterns are close enough to be confused, so the distinction is written down rather than left to taste.

Tabs compared with Segmented Control
TabsSegmented Control
PurposeNavigate between related content sectionsSwitch between related views, modes or filters
ContentEach tab reveals a different panelThe same content, rendered differently
PlacementUnder the page or record headerInside a toolbar, card header or filter bar
Count3–7 sections2–5 options
ExampleOverview / Care plan / Activity / DocumentsTable / Rows / Calendar · 7 / 30 / 90 days
StylingFull-width capable, underline or enclosedCompact, inset track, never full-bleed across a page

States, groups and responsive behaviour

Every navigation component uses the same interaction states, so a hover or a focus ring means the same thing wherever it appears.

Shared navigation states
StateTreatment
DefaultSecondary text, transparent surface
HoverPrimary text on the hover surface token — never a colour shift alone
FocusShared ds-focus-ring, always visible and never removed
Active / selectedMedium weight + tinted surface + indicator bar or underline + aria-current
DisabledDisabled text token, pointer events off, still announced
Expandedaria-expanded=true, chevron rotated, children revealed with the ds-collapsible animation
CollapsedIcon only, label kept as screen-reader text; branches open a tone-matched hover flyout
Responsive navigation behaviour
BreakpointPrimary navigationSecondary navigation
Desktop (≥ 1280px)Expanded sidebar with groups and nestingTabs, breadcrumbs, segmented control at full width
Tablet (768–1279px)Collapsed icon rail with hover flyouts for branchesTabs scroll horizontally; breadcrumbs collapse the middle
Mobile (< 768px)Bottom bar plus drawerFull-width tabs or scrolling tabs; pagination stacks above the page-size control

Sidebar / Side Navigation colour

The rail keeps a dark navy identity in both themes, so it owns a small set of component-scoped semantic tokens. The background is the exact brand navy (#050B42); state fills and content resolve to the locked blue or neutral ramps — no per-component hex.

Sidebar colour tokens and their usage
TokenUtilityUsed for
--color-sidebarbg-sidebarRail background — brand navy #050B42, never pure black
--color-sidebar-hoverhover:bg-sidebar-hoverHover fill, one step up the same navy ramp
--color-sidebar-activebg-sidebar-activeActive destination fill
--color-sidebar-foregroundtext-sidebar-foregroundItem label at rest on hover, and nested active labels (18.5:1)
--color-sidebar-foreground-mutedtext-sidebar-foreground-mutedGroup headings and inactive item labels (12.8:1)
--color-sidebar-foreground-activetext-sidebar-foreground-activeActive item label on the active fill (12.8:1)
--color-sidebar-foreground-disabledtext-sidebar-foreground-disabledDisabled destinations — dimmer hue, no opacity fade (7.2:1)
--color-sidebar-icon[&_svg]:text-sidebar-iconLucide icons at the standard sidebar icon size
--color-sidebar-icon-active[&_svg]:text-sidebar-icon-activeIcon inside the active destination
--color-sidebar-indicatorbg-sidebar-indicatorActive indicator bar and nested active border (9.3:1 on the rail)
--color-sidebar-borderborder-sidebar-borderFooter divider, group divider on the collapsed rail, nested rail
--color-sidebar-border-focusds-sidebar-focus-ringKeyboard focus outline, recoloured for the navy background
Sidebar colour behaviour by mode and state
ConcernBehaviour
Light modeThe rail stays navy — it is not flipped to white just because the application canvas is light
Dark modeSame navy background by design, not an inversion; only the divider and focus ring switch to their dark-theme counterparts
Active stateNever colour alone: aria-current="page", medium weight, raised fill and an indicator bar
HoverStays inside the navy family — a fill change, not a hue change
Focusds-sidebar-focus-ring: 2px outline with a 2px offset, visible on navy and never removed
DisabledA dimmer navy-safe foreground that still clears 4.5:1, plus pointer-events off; opacity is not used to signal it
In-page section navtone="surface" opts a nav inside page content back onto the surface/text tokens, so it inherits the card it sits in

Rules

Do

  • Group destinations under quiet, uppercase-free headings: Overview, Management, Settings.
  • Combine weight, surface and an indicator for the active item so it survives greyscale.
  • Keep Lucide icons at the standard --size-icon-sm rail size and align them on one axis.
  • Give collapsed rails a visually-hidden label, and reveal nested branches in the rail-toned hover flyout.
  • Pair Residents → list → details → activity with breadcrumbs so the way back is always visible.

Don't

  • Do not build another Pagination, Tooltip, Drawer or Button for navigation.
  • Do not use a Segmented Control as primary navigation, or Tabs as a filter.
  • Do not rely on colour alone, or add heavy borders and boxes around groups.
  • Do not truncate labels in the collapsed rail — keep them as screen-reader text and use the flyout.
  • Do not introduce new semantic colours or arbitrary spacing for a one-off nav style.

Why it is designed this way

Navigation is the part of the system users touch on every screen, so it is built entirely from components that already exist: Badge for counts, Tooltip and Drawer from the overlay system, IconButton for compact controls, and the single Pagination implementation from the data-display family. Navigation adds composition — PaginationBar, SidebarCollapsibleItem — never a parallel implementation.

Active state is deliberately over-specified: weight, surface, indicator and aria-current together. That keeps wayfinding legible in dark mode, in greyscale and for screen-reader users, and it means no screen needs to invent its own emphasis to make the current page obvious.