Skip to content

Navigation

Tabs follow the WAI-ARIA tabs pattern: a role="tablist" of role="tab" buttons and one role="tabpanel" per tab. The CSS keys on the roles and on aria-selected, so the markup is the API. Tabs switch panels with JavaScript; the CSS hides panels that carry hidden.

Weapons: twelve items, sorted by damage per second.
<div class="sb-tabs" data-tabs>
<div role="tablist" aria-label="Inventory">
<button type="button" role="tab" id="t-weapons" aria-selected="true" aria-controls="p-weapons">Weapons <span class="sb-tab-count">12</span></button>
<button type="button" role="tab" id="t-cyber" aria-selected="false" aria-controls="p-cyber" tabindex="-1">Cyberware <span class="sb-tab-count">4</span></button>
<button type="button" role="tab" id="t-consum" aria-selected="false" aria-controls="p-consum" tabindex="-1">Consumables</button>
<button type="button" role="tab" id="t-locked" aria-selected="false" aria-controls="p-locked" tabindex="-1" disabled>Locked</button>
</div>
<div role="tabpanel" id="p-weapons" aria-labelledby="t-weapons" tabindex="0">Weapons: twelve items, sorted by damage per second.</div>
<div role="tabpanel" id="p-cyber" aria-labelledby="t-cyber" tabindex="0" hidden>Cyberware: four installed mods.</div>
<div role="tabpanel" id="p-consum" aria-labelledby="t-consum" tabindex="0" hidden>Consumables panel.</div>
<div role="tabpanel" id="p-locked" aria-labelledby="t-locked" tabindex="0" hidden>Locked.</div>
</div>
Selector / class Effect
sb-tabs Wrapper. Underline variant by default; the active tab gets a 2px primary indicator with a bloom.
[role="tablist"] The row (or column, when vertical) of tabs.
[role="tab"] A tab button.
[aria-selected="true"] The active tab.
[role="tabpanel"] A panel. Hidden when it has the hidden attribute.
:disabled, [aria-disabled="true"] Dimmed, not clickable.
:focus-visible Focus ring on the tab or panel.
sb-tab-count Small count chip inside a tab.
sb-tab-icon Leading icon slot inside a tab; mark it aria-hidden.
Pill variant, small.
Pill, stretch and large.
Vertical tabs: up and down arrows move the selection.
Vertical pill.
<div class="sb-tabs sb-tabs-pill sb-tabs-sm" data-tabs>
<div role="tablist" aria-label="Range">
<button type="button" role="tab" id="t-p1" aria-selected="true" aria-controls="p-p1">Day</button>
<button type="button" role="tab" id="t-p2" aria-selected="false" aria-controls="p-p2" tabindex="-1">Week</button>
<button type="button" role="tab" id="t-p3" aria-selected="false" aria-controls="p-p3" tabindex="-1">Month</button>
</div>
<div role="tabpanel" id="p-p1" aria-labelledby="t-p1" tabindex="0">Pill variant, small.</div>
<div role="tabpanel" id="p-p2" aria-labelledby="t-p2" tabindex="0" hidden>Week.</div>
<div role="tabpanel" id="p-p3" aria-labelledby="t-p3" tabindex="0" hidden>Month.</div>
</div>
<div class="sb-tabs sb-tabs-pill sb-tabs-stretch sb-tabs-lg" data-tabs>
<div role="tablist" aria-label="Journal">
<button type="button" role="tab" id="t-s1" aria-selected="false" aria-controls="p-s1" tabindex="-1">Map</button>
<button type="button" role="tab" id="t-s2" aria-selected="true" aria-controls="p-s2">Journal</button>
<button type="button" role="tab" id="t-s3" aria-selected="false" aria-controls="p-s3" tabindex="-1">Shards</button>
</div>
<div role="tabpanel" id="p-s1" aria-labelledby="t-s1" tabindex="0" hidden>Map.</div>
<div role="tabpanel" id="p-s2" aria-labelledby="t-s2" tabindex="0">Pill, stretch and large.</div>
<div role="tabpanel" id="p-s3" aria-labelledby="t-s3" tabindex="0" hidden>Shards.</div>
</div>
<div class="sb-tabs sb-tabs-vertical" data-tabs>
<div role="tablist" aria-label="Settings" aria-orientation="vertical">
<button type="button" role="tab" id="t-v1" aria-selected="true" aria-controls="p-v1">Audio</button>
<button type="button" role="tab" id="t-v2" aria-selected="false" aria-controls="p-v2" tabindex="-1">Video</button>
<button type="button" role="tab" id="t-v3" aria-selected="false" aria-controls="p-v3" tabindex="-1">Controls</button>
</div>
<div role="tabpanel" id="p-v1" aria-labelledby="t-v1" tabindex="0">Vertical tabs: up and down arrows move the selection.</div>
<div role="tabpanel" id="p-v2" aria-labelledby="t-v2" tabindex="0" hidden>Video.</div>
<div role="tabpanel" id="p-v3" aria-labelledby="t-v3" tabindex="0" hidden>Controls.</div>
</div>
<div class="sb-tabs sb-tabs-vertical sb-tabs-pill" data-tabs>
<div role="tablist" aria-label="Account" aria-orientation="vertical">
<button type="button" role="tab" id="t-w1" aria-selected="true" aria-controls="p-w1">Profile</button>
<button type="button" role="tab" id="t-w2" aria-selected="false" aria-controls="p-w2" tabindex="-1">Security</button>
</div>
<div role="tabpanel" id="p-w1" aria-labelledby="t-w1" tabindex="0">Vertical pill.</div>
<div role="tabpanel" id="p-w2" aria-labelledby="t-w2" tabindex="0" hidden>Security.</div>
</div>
Class Effect
sb-tabs-pill Segmented control on a sunken track; the active tab is a filled pill instead of an underline.
sb-tabs-vertical Tablist as a column beside the panel. Set aria-orientation="vertical" on the tablist too.
sb-tabs-stretch Tabs share the available width.
sb-tabs-sm, sb-tabs-lg Smaller and larger tabs.

The same look works for navigation between pages. sb-tablist is a plain container, sb-tab styles an <a> or <button>, sb-tab-active marks the current one and sb-tabpanel wraps the content. Use aria-current="page" rather than ARIA tab roles for this pattern, because the items are links.

Link-based tabs navigate between pages instead of toggling panels.
<div class="sb-tabs">
<nav class="sb-tablist" aria-label="Project">
<a class="sb-tab sb-tab-active" href="#link-based-tabs" aria-current="page">Overview</a>
<a class="sb-tab" href="#link-based-tabs"><span class="sb-tab-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9"/></svg></span>Specs</a>
<a class="sb-tab" href="#link-based-tabs" aria-disabled="true">Changelog</a>
</nav>
<div class="sb-tabpanel">Link-based tabs navigate between pages instead of toggling panels.</div>
</div>
Class Effect
sb-tablist Tab row for non-ARIA markup.
sb-tab A tab item for non-ARIA markup.
sb-tab-active Active item.
sb-tabpanel Panel for non-ARIA markup.

The CSS does not switch tabs. The demos on this page use a small delegated script (data-tabs on the wrapper) that sets aria-selected, roving tabindex and the panels’ hidden, and handles Arrow, Home and End keys. In a framework, use the Vue components or your own state; the markup contract stays the same.

native-behaviors.js
// Native behaviors for the plain-HTML component demos. The CSS classes need none of this; it only
// wires the platform features the markup relies on.
export function bindNativeBehaviors() {
document.addEventListener('click', (event) => {
const target = event.target
if (!(target instanceof Element)) return
// [data-open="#id"] opens a modal <dialog>; [data-close] closes the dialog it sits in.
const opener = target.closest('[data-open]')
if (opener) {
const dialog = document.querySelector(opener.getAttribute('data-open'))
if (dialog instanceof HTMLDialogElement && !dialog.open) dialog.showModal()
return
}
const closer = target.closest('[data-close]')
if (closer) closer.closest('dialog')?.close()
})
// Tab lists: click and arrow keys move selection (roving tabindex), Home and End jump.
for (const tabs of document.querySelectorAll('[data-tabs]')) {
const list = tabs.querySelector('[role="tablist"]')
if (!list) continue
const vertical = list.getAttribute('aria-orientation') === 'vertical'
const enabledTabs = () => [...list.querySelectorAll('[role="tab"]:not(:disabled)')]
const select = (tab, focus) => {
for (const item of list.querySelectorAll('[role="tab"]')) {
const selected = item === tab
item.setAttribute('aria-selected', String(selected))
item.tabIndex = selected ? 0 : -1
const panel = document.getElementById(item.getAttribute('aria-controls'))
if (panel) panel.hidden = !selected
}
if (focus) tab.focus()
}
list.addEventListener('click', (event) => {
const tab = event.target instanceof Element ? event.target.closest('[role="tab"]') : null
if (tab && !tab.disabled) select(tab, false)
})
list.addEventListener('keydown', (event) => {
const all = enabledTabs()
const index = all.indexOf(document.activeElement)
if (index < 0) return
const previousKey = vertical ? 'ArrowUp' : 'ArrowLeft'
const nextKey = vertical ? 'ArrowDown' : 'ArrowRight'
let target
if (event.key === nextKey) target = all[(index + 1) % all.length]
else if (event.key === previousKey) target = all[(index - 1 + all.length) % all.length]
else if (event.key === 'Home') target = all[0]
else if (event.key === 'End') target = all[all.length - 1]
if (!target) return
event.preventDefault()
select(target, true)
})
}
// The indeterminate state of a checkbox has no attribute; it is a DOM property.
for (const box of document.querySelectorAll('[data-indeterminate]')) box.indeterminate = true
}

Breadcrumbs are a <nav aria-label="Breadcrumb"> around an ordered list. The separator is drawn by CSS between items (a slash by default), so the markup stays a plain list. The current page is the item with aria-current="page"; it is styled as the end of the trail and need not be a link.

<div class="sb-stack sb-stack-3">
<nav class="sb-breadcrumbs" aria-label="Breadcrumb">
<ol>
<li><a href="#sb-breadcrumbs">Home</a></li>
<li><a href="#sb-breadcrumbs">Docs</a></li>
<li><a href="#sb-breadcrumbs">Components</a></li>
<li aria-current="page">Breadcrumbs</li>
</ol>
</nav>
<nav class="sb-breadcrumbs sb-breadcrumbs-chevron" aria-label="Breadcrumb with chevrons">
<ol>
<li><a href="#sb-breadcrumbs">Home</a></li>
<li><span class="sb-breadcrumbs-ellipsis" aria-label="More">...</span></li>
<li><a href="#sb-breadcrumbs">Overlays</a></li>
<li aria-current="page">Chevron separators</li>
</ol>
</nav>
<nav class="sb-breadcrumbs sb-breadcrumbs-dot sb-breadcrumbs-sm" aria-label="Breadcrumb with dots, small">
<ol>
<li><a href="#sb-breadcrumbs">Home</a></li>
<li><a href="#sb-breadcrumbs">Dot separators</a></li>
<li aria-current="page">Small</li>
</ol>
</nav>
<nav class="sb-breadcrumbs sb-breadcrumbs-lg" aria-label="Breadcrumb with custom separators, large">
<ol>
<li><a href="#sb-breadcrumbs">Home</a></li>
<li class="sb-breadcrumbs-separator" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 6l6 6-6 6"/></svg></li>
<li><a href="#sb-breadcrumbs">Custom separator</a></li>
<li class="sb-breadcrumbs-separator" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 6l6 6-6 6"/></svg></li>
<li aria-current="page">Large</li>
</ol>
</nav>
<nav class="sb-breadcrumbs sb-breadcrumbs-eyebrow" aria-label="Breadcrumb as eyebrow">
<ol>
<li><a href="#sb-breadcrumbs">HUD</a></li>
<li><a href="#sb-breadcrumbs">Inventory</a></li>
<li aria-current="page">Weapons</li>
</ol>
</nav>
</div>
Class / selector Effect
sb-breadcrumbs The nav wrapper; slash separators.
sb-breadcrumbs-chevron Chevron separators.
sb-breadcrumbs-dot Dot separators.
sb-breadcrumbs-sm, -lg Smaller and larger type.
sb-breadcrumbs-eyebrow Uppercase mono, for use as a section eyebrow.
sb-breadcrumbs-icon Icon slot inside a link; mark it aria-hidden.
sb-breadcrumbs-ellipsis Truncation marker for collapsed levels; give it an aria-label.
sb-breadcrumbs-separator An <li> that renders its own separator. When present, the automatic one is turned off. Mark it aria-hidden="true".
[aria-current="page"] The current item.
:focus-visible Focus ring on links.

Pagination is a <nav aria-label="Pagination"> around a list. Items are links for server-rendered pages or buttons for client state. The current page carries aria-current="page" (or data-selected).

<div class="sb-stack">
<nav class="sb-pagination sb-pagination-between" aria-label="Pagination">
<span class="sb-pagination-summary">21-40 of 212</span>
<ul>
<li><a class="sb-pagination-item sb-pagination-prev" href="#sb-pagination">Prev</a></li>
<li><a class="sb-pagination-item" href="#sb-pagination">1</a></li>
<li><a class="sb-pagination-item" href="#sb-pagination" aria-current="page">2</a></li>
<li><a class="sb-pagination-item" href="#sb-pagination">3</a></li>
<li><span class="sb-pagination-ellipsis" aria-hidden="true">...</span></li>
<li><a class="sb-pagination-item" href="#sb-pagination">11</a></li>
<li><a class="sb-pagination-item sb-pagination-next" href="#sb-pagination">Next</a></li>
</ul>
</nav>
<nav class="sb-pagination sb-pagination-outline sb-pagination-sm" aria-label="Pagination, outline and small">
<ul>
<li><button type="button" class="sb-pagination-item sb-pagination-first" aria-label="First page" disabled>&laquo;</button></li>
<li><button type="button" class="sb-pagination-item sb-pagination-prev" aria-label="Previous page" disabled>&lsaquo;</button></li>
<li><button type="button" class="sb-pagination-item" aria-current="page">1</button></li>
<li><button type="button" class="sb-pagination-item">2</button></li>
<li><button type="button" class="sb-pagination-item">3</button></li>
<li><button type="button" class="sb-pagination-item sb-pagination-next" aria-label="Next page">&rsaquo;</button></li>
<li><button type="button" class="sb-pagination-item sb-pagination-last" aria-label="Last page">&raquo;</button></li>
</ul>
</nav>
<nav class="sb-pagination sb-pagination-pill sb-pagination-solid sb-pagination-lg sb-pagination-center" aria-label="Pagination, pill, solid and large">
<ul>
<li><button type="button" class="sb-pagination-item sb-pagination-prev">Prev</button></li>
<li><button type="button" class="sb-pagination-item">4</button></li>
<li><button type="button" class="sb-pagination-item" data-selected>5</button></li>
<li><button type="button" class="sb-pagination-item">6</button></li>
<li><button type="button" class="sb-pagination-item sb-pagination-next" aria-disabled="true">Next</button></li>
</ul>
</nav>
<nav class="sb-pagination sb-pagination-end" aria-label="Pagination, end aligned">
<ul class="sb-pagination-list">
<li><button type="button" class="sb-pagination-item">8</button></li>
<li><button type="button" class="sb-pagination-item" aria-current="page">9</button></li>
</ul>
</nav>
</div>
Class / selector Effect
sb-pagination The nav wrapper (a flex row of its list).
sb-pagination-list Optional class for the list; a bare <ul> inside works too.
sb-pagination-item A page, previous or next control (<a> or <button>).
sb-pagination-prev, -next, -first, -last Edge controls. Icon-only ones need an aria-label.
sb-pagination-ellipsis Gap marker between page numbers.
sb-pagination-summary A text caption such as “21-40 of 212”.
sb-pagination-outline Outlined items.
sb-pagination-solid The current item is filled.
sb-pagination-pill Fully rounded items.
sb-pagination-sm, -lg Smaller and larger items.
sb-pagination-between Spreads the summary and the list to opposite ends.
sb-pagination-center, -end Aligns the list.
[aria-current="page"], [data-selected] The current page.
:disabled, [aria-disabled="true"] Disabled item.
:focus-visible Focus ring.

sb-link styles an inline link: primary ink, a soft underline that brightens on hover. It works on any <a>.

<p>
A <a class="sb-link" href="#sb-link">primary link</a>, a <a class="sb-link sb-link-muted" href="#sb-link">muted link</a>,
a <a class="sb-link sb-link-subtle" href="#sb-link">subtle link</a>, an <a class="sb-link sb-link-external" href="#sb-link">external link</a>,
an <a class="sb-link sb-link-inherit" href="#sb-link">inherited-ink link</a>, a
<a class="sb-link" href="#sb-link"><svg class="sb-link-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10 14L21 3M15 3h6v6M21 14v5a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5"/></svg>link with an icon</a>
and a <a class="sb-link" href="#sb-link" aria-disabled="true">disabled link</a>.
</p>
Class Effect
sb-link Primary ink with a soft underline that brightens, with a text glow, on hover.
sb-link-muted Muted ink that turns primary on hover.
sb-link-subtle No underline until hover.
sb-link-inherit Keeps the surrounding ink.
sb-link-external Appends an arrow glyph. To use your own icon, put an <svg class="sb-link-icon"> inside instead.
sb-link-icon Sizes an inline <svg> to the link text.
sb-link-disabled, [aria-disabled="true"] Dimmed, no pointer events. Also remove href or handle the click so keyboard users cannot follow it.

sb-link-nav turns a link into a navigation item: uppercase display type, no underline. [aria-current] (page or true) and sb-link-active glow.

<nav class="sb-cluster sb-cluster-6" aria-label="Sections">
<a class="sb-link sb-link-nav" href="#navigation-links" aria-current="page">Overview</a>
<a class="sb-link sb-link-nav" href="#navigation-links">Jobs</a>
<a class="sb-link sb-link-nav sb-link-active" href="#navigation-links">Active class</a>
<a class="sb-link sb-link-nav" href="#navigation-links">Settings</a>
</nav>