Navigation
.sb-tabs
Section titled “.sb-tabs”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.
<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. |
Variants and sizes
Section titled “Variants and sizes”<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. |
Link-based tabs
Section titled “Link-based 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.
<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. |
Tab behavior script
Section titled “Tab behavior script”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 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}.sb-breadcrumbs
Section titled “.sb-breadcrumbs”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. |
.sb-pagination
Section titled “.sb-pagination”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>«</button></li> <li><button type="button" class="sb-pagination-item sb-pagination-prev" aria-label="Previous page" disabled>‹</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">›</button></li> <li><button type="button" class="sb-pagination-item sb-pagination-last" aria-label="Last page">»</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
Section titled “.sb-link”sb-link styles an inline link: primary ink, a soft underline that brightens on hover. It works on any <a>.
A primary link, a muted link, a subtle link, an external link, an inherited-ink link, a link with an icon and a disabled link.
<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. |
Navigation links
Section titled “Navigation links”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>