Core
Primary is the anchor
Settings

How the five derived colors are placed around the anchor.

Theme builder

Navigation

Building consistent navigation patterns with links and buttons

Navigation patterns cover how users move through an interface. Each pattern follows the same accessibility principles and uses the same link primitive — ButtonLink — to keep visual language consistent.


A horizontal navigation bar for top-level site sections. Use <nav> as the landmark, mark the active page with aria-current="page", and use ghost ButtonLink elements so the links match the design system focus ring.

Rules:

  • Always wrap the list in a <nav> landmark.
  • The active link receives aria-current="page" — do not use only a visual active state.
  • Do not use primary ButtonLink variants in nav bars — a fill draws too much attention for a list of destinations. Ghost is the default; outlined marks the current one.

The single most important navigation decision is choosing the right element.

UseElementWhy
Navigate to a URL<a> / ButtonLinkHas an href; browser history, open-in-tab, right-click all work correctly
Trigger an action<button> / ButtonNo href; activates JS behavior without changing the URL

Rule: Never use a <button> to navigate and never use an <a> to trigger an action. Swapping them breaks keyboard users, screen readers, and browser affordances.

html
<!-- Navigate to a page — use ButtonLink (<a>) -->
<ButtonLink href="/settings">Settings</ButtonLink>

<!-- Trigger an action — use Button (<button>) -->
<Button variant="primary" type="submit">Save changes</Button>

See ButtonLink for the full props API.


Breadcrumbs show the user’s location in a page hierarchy. Use <nav aria-label="Breadcrumb"> with an ordered list. Separate crumbs with a visible, aria-hidden separator. The last item represents the current page and should not be a link.

Rules:

  • Use <ol> — breadcrumbs are ordered by hierarchy.
  • The separator (/) is purely decorative; add aria-hidden="true" so screen readers skip it.
  • The current page item uses aria-current="page" and is plain text, not a link — linking to the current page is redundant.

Pagination

Pagination lets users move between pages of a data set. Previous and next are ghost ButtonLink elements; page numbers use ghost, with the current page outlined. The active page is marked aria-current="page".

Rules:

  • Wrap in <nav aria-label="Pagination"> so the landmark is announced distinctly from other navs.
  • Each page number link must have a descriptive aria-label (e.g. "Page 3") — numeric-only link text is ambiguous out of context.
  • The active page item receives aria-current="page" and a higher-emphasis variant (outlined) to distinguish it visually without relying on color alone.
  • Disable or hide the previous link on page 1 and the next link on the last page.

Tab bar

A CSS-only tab strip for switching between views within the same page context. Use role="tablist" / role="tab" / role="tabpanel" and aria-selected to communicate state. No JavaScript is required when content is always present; add a script only when panels need to show and hide.

Profile panel content.

Rules:

  • The active tab is indicated by aria-selected="true" and a bottom border in bg-primary — never color alone.
  • Use <a> tabs when each tab maps to a URL (deep-linkable); use <button> tabs when content is toggled in place with JavaScript.
  • Inactive tabs use text-dark/55 and a transparent bottom border to show the same structural affordance at a lower emphasis.
  • Each tab names its panel with aria-controls, and the panel carries role="tabpanel" with a matching id.
  • Add keyboard arrow-key navigation when using JavaScript-toggled panels (Left/Right to move focus, Home/End for first/last), and a roving tabindex so the strip is one stop rather than three.

Accessibility

Landmark roles

Wrap every navigation region in a <nav> element. This creates a landmark that screen reader users can jump to directly.

html
<nav aria-label="Main">…</nav>
<nav aria-label="Breadcrumb">…</nav>
<nav aria-label="Pagination">…</nav>

Multiple nav landmarks

When a page contains more than one <nav>, each must have a distinct aria-label so users can tell them apart. Generic labels like "Navigation" are unhelpful when there are two.

html
<!-- Two navs on the same page — both need distinct labels -->
<nav aria-label="Main">…site-wide links…</nav>
<nav aria-label="On this page">…in-page anchor links…</nav>

Keyboard focus

  • All ButtonLink elements use :focus-visible with outline-primary — never suppress the outline.
  • Tab order must follow visual reading order. Do not use positive tabindex values to reorder focus.
  • For JavaScript-driven tab bars, implement arrow-key navigation within the tablist so keyboard users do not have to Tab through every tab to reach the panel.
  • Skip links (e.g. “Skip to main content”) should appear as the first focusable element on pages with long navigation blocks.
html
<!-- Skip link — first element in <body> -->
<a href="#main-content" class="skip-link">Skip to main content</a>