Centrim Design System — a token-first, accessible React component 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.
| Component | Scope | Changes | Use for |
|---|---|---|---|
| Sidebar Navigation | App shell | The page | Modules: Dashboard, Residents, Services, Assets, Settings |
| Top Navigation | App shell | The page | Brand, a few destinations, search, notifications, account |
| Tabs | One page | The panel | Sections of a record: Overview, Care plan, Activity |
| Segmented Control | One region | The rendering | Views of the same data: table / rows, 7 / 30 / 90 days |
| Breadcrumbs | One page | Nothing — it links up | Depth and return path on detail pages |
| Pagination | One data region | The result window | Long tables and lists |
| Mobile Navigation | App shell (mobile) | The page | Bottom 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
Pill and enclosed variants
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.
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
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
Table footer composition
PaginationBar pairs the result summary and page-size control with the same Pagination component.
Showing 51–75 of 288
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 | Segmented Control | |
|---|---|---|
| Purpose | Navigate between related content sections | Switch between related views, modes or filters |
| Content | Each tab reveals a different panel | The same content, rendered differently |
| Placement | Under the page or record header | Inside a toolbar, card header or filter bar |
| Count | 3–7 sections | 2–5 options |
| Example | Overview / Care plan / Activity / Documents | Table / Rows / Calendar · 7 / 30 / 90 days |
| Styling | Full-width capable, underline or enclosed | Compact, 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.
| State | Treatment |
|---|---|
| Default | Secondary text, transparent surface |
| Hover | Primary text on the hover surface token — never a colour shift alone |
| Focus | Shared ds-focus-ring, always visible and never removed |
| Active / selected | Medium weight + tinted surface + indicator bar or underline + aria-current |
| Disabled | Disabled text token, pointer events off, still announced |
| Expanded | aria-expanded=true, chevron rotated, children revealed with the ds-collapsible animation |
| Collapsed | Icon only, label kept as screen-reader text; branches open a tone-matched hover flyout |
| Breakpoint | Primary navigation | Secondary navigation |
|---|---|---|
| Desktop (≥ 1280px) | Expanded sidebar with groups and nesting | Tabs, breadcrumbs, segmented control at full width |
| Tablet (768–1279px) | Collapsed icon rail with hover flyouts for branches | Tabs scroll horizontally; breadcrumbs collapse the middle |
| Mobile (< 768px) | Bottom bar plus drawer | Full-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.
| Token | Utility | Used for |
|---|---|---|
| --color-sidebar | bg-sidebar | Rail background — brand navy #050B42, never pure black |
| --color-sidebar-hover | hover:bg-sidebar-hover | Hover fill, one step up the same navy ramp |
| --color-sidebar-active | bg-sidebar-active | Active destination fill |
| --color-sidebar-foreground | text-sidebar-foreground | Item label at rest on hover, and nested active labels (18.5:1) |
| --color-sidebar-foreground-muted | text-sidebar-foreground-muted | Group headings and inactive item labels (12.8:1) |
| --color-sidebar-foreground-active | text-sidebar-foreground-active | Active item label on the active fill (12.8:1) |
| --color-sidebar-foreground-disabled | text-sidebar-foreground-disabled | Disabled destinations — dimmer hue, no opacity fade (7.2:1) |
| --color-sidebar-icon | [&_svg]:text-sidebar-icon | Lucide icons at the standard sidebar icon size |
| --color-sidebar-icon-active | [&_svg]:text-sidebar-icon-active | Icon inside the active destination |
| --color-sidebar-indicator | bg-sidebar-indicator | Active indicator bar and nested active border (9.3:1 on the rail) |
| --color-sidebar-border | border-sidebar-border | Footer divider, group divider on the collapsed rail, nested rail |
| --color-sidebar-border-focus | ds-sidebar-focus-ring | Keyboard focus outline, recoloured for the navy background |
| Concern | Behaviour |
|---|---|
| Light mode | The rail stays navy — it is not flipped to white just because the application canvas is light |
| Dark mode | Same navy background by design, not an inversion; only the divider and focus ring switch to their dark-theme counterparts |
| Active state | Never colour alone: aria-current="page", medium weight, raised fill and an indicator bar |
| Hover | Stays inside the navy family — a fill change, not a hue change |
| Focus | ds-sidebar-focus-ring: 2px outline with a 2px offset, visible on navy and never removed |
| Disabled | A dimmer navy-safe foreground that still clears 4.5:1, plus pointer-events off; opacity is not used to signal it |
| In-page section nav | tone="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.