Tabs

Molecule

Horizontal navigation between related panels. Each .tab carries a bottom-border that hairlines along with its siblings; the active tab swaps the hairline for a solid dark rule. Composes with an optional leading icon + an optional small inline badge. Values from Figma node 3817-106 (light) + 5934-1968 (dark). Two alignment axes: Horizontal (default — icon · label · badge in a row) and Vertical (.tab-vertical — icon stacks above, label-row below). Tabs compose with the canonical Badge component — drop a .badge.badge-label.badge-info inside any tab and the badge inherits the matching height per tab size automatically (sm → 12 px, md → 16 px, lg → 20 px) via cascade. Three sizes (lg 48 / md 40 default / sm 32) × four states (Default · Hover · Active · Disabled).

When to use

✅ Do
  • Use to organise related content within the same view (Overview, Details, History)
  • Keep tab labels short — 1–3 words
  • Always show at least 2 tabs — a single tab is not a tab group
  • Compose with Badge for notification counts on specific tabs
❌ Don't
  • Use for navigation between separate pages — use sidebar Links instead
  • Show more than 7 tabs in a single row — consider consolidating
  • Use .tab-badge — it was removed in v2.20.0. Use .badge.badge-label instead
  • Mix horizontal and vertical tab layouts on the same page

DigiLawyer Patterns

No specific DigiLawyer patterns documented yet — to be added.

Anatomy

A Tabs component is a row of tab elements sharing a bottom hairline. The active tab shows a solid accent rule.

  1. 1Tabs wrapper.tabs. Flex row sharing a bottom hairline border.
  2. 2Tab.tab. Individual tab button. Add size modifier.
  3. 3Active indicator.tab.active. Swaps the hairline for a solid bottom border in --tab-active-border colour.
  4. 4Icon slot.tab-icon. Optional leading icon, scales with size.
  5. 5Badge.badge.badge-label inside the tab. Size inherits from the parent tab size modifier.

Default

A .tabs row contains a flat list of .tab buttons. Mark the active tab with aria-selected="true" (preferred) or .is-active. Each tab can carry an optional leading .tab-icon and an optional inline .badge.badge-label.badge-info count pill (composes with the Badge component). Click a tab to switch panels — the demo below wires it up with a 6-line script.

Panel 1 content. Switch tabs above to see other panels.
HTML
<div class="tabs" role="tablist">
  <button class="tab" role="tab" aria-selected="true">
    <span class="tab-icon"><svg></svg></span>
    Label
    <span class="badge badge-label badge-info">text</span>
  </button>
  <button class="tab" role="tab">Label</button>
</div>

Sizes

Three sizes per Figma symbols. .tab-lg uses Body 1 (16/24) Medium; default md uses Body 2 (14/24) Medium; .tab-sm drops to Caption 1 (12/16) Medium. The leading icon stays 16×16 across all sizes (it's an affordance, not a content scaler). Horizontal padding is --spacing-sm (12px) at every size.

States

Four states. Default and Disabled share a thin 10%-black hairline border; Hover lifts the bg subtly to #F6F6F6 (light) / #2D2D2D (dark) and darkens the text; Active swaps the hairline for a solid #2D2D2D (light) / #FFFFFF (dark) rule. Hover is browser-native via :hover; .is-hover forces it at rest for the matrix. Use aria-selected="true" for Active (recommended) — falls back to .is-active for non-interactive demos.

Alignment

Two axes per Figma node 5934-1968. Horizontal (default — no modifier) lays the icon · label · badge in a single row at tab heights 48 / 40 / 32 px. Vertical (.tab-vertical) stacks the icon above a label-row at heights 64 / 52 / 48 px. Each size must live in its own .tabs row — the container uses align-items: stretch, so mixing sizes in one row would stretch the small tabs to the height of the large ones. Icons scale per size: horizontal 20 / 16 / 14 · vertical 24 / 20 / 16 (lg / md / sm).

Horizontal
Vertical (.tab-vertical)
lg
48 / 64
md
40 / 52
sm
32 / 48

Full matrix

3 sizes × 4 states = 12 cells, mirroring Figma 1:1.

Default
Hover
Active
Disabled
Large · 48
Medium · 40
Small · 32

Responsive

The .tabs wrapper already handles horizontal scroll on narrow screens — no extra class needed. On viewports narrower than 744 px, overflow-x: auto activates and the scrollbar is hidden via scrollbar-width: none. Use as many tabs as the content requires; the strip scrolls silently.

Frame above simulates a 390 px mobile viewport — the tab strip scrolls horizontally with no visible scrollbar.

HTML
<div class="tabs" role="tablist">
  <button class="tab tab-md active" role="tab">Label</button>
</div>

Accessibility

Follows the WAI-ARIA Tabs pattern.

Attribute / BehaviourValueNotes
Tab list rolerole="tablist"Set on the .tabs wrapper
Tab rolerole="tab"Set on each .tab button
Active tabaria-selected="true"Set on the active tab; aria-selected="false" on all others
Tab panelrole="tabpanel"The content area controlled by each tab
Panel associationaria-controls="panel-id" on tab + aria-labelledby="tab-id" on panelLinks each tab to its controlled panel
Keyboard — switch / Move focus and activate tabs within the tablist

API

Class / attrOnDescription
.tabs<div role="tablist">The row container — flex with shared bottom alignment. Set role="tablist" and aria-label to describe the group.
.tab<button role="tab">Single tab. Flex container with three slots (icon · label · badge). Default size 40px (md), Body 2 (14/24) Medium typography. Pair aria-controls with the corresponding tabpanel.
.tab-lg.tabLarge — 48px tall, Body 1 (16/24) Medium typography. Use as a top-level page nav.
(no size class).tabMedium (default) — 40px tall, Body 2 (14/24) Medium typography. The canonical density.
.tab-sm.tabSmall — 32px tall, Caption 1 (12/16) Medium typography. Use in tight surfaces (e.g. inside a card or modal).
.tab-icon<span>Optional leading slot. Wrap an inline SVG; sized 16×16 in all tab sizes.
.badge.badge-label.badge-info<span> inside .tabThe canonical count pill — composes with the standalone Badge component. Auto-scales per parent tab size via cascade: 12 px tall in a .tab-sm, 16 px (default) in a .tab, 20 px in a .tab-lg. Halo border flips from white (light) to #1C1C1C (dark) like every Badge variant. The legacy .tab-badge alias has been retired in v2.22.10.
.tab-vertical.tabAlignment modifier — stacks the icon above the label-row instead of beside it. Tab heights become 64 / 52 / 48 px (lg / md / sm). Wrap label + badge in a .tab-label-row child so they ride together under the icon. Active-state bottom-border still sits at the bottom of the tab.
.tab-label-row<span>Inner row used inside a .tab-vertical to keep the label text + optional .badge side-by-side under the icon. Has no effect on horizontal tabs.
aria-selected="true".tabMarks the currently active tab. Drives the bottom-border swap from hairline to solid --tab-active-border.
.is-active.tabFallback when aria-selected can't be used (rare). Renders identically.
disabled / .is-disabled.tab / <button>Disables the tab and dims the label. Prefer the native HTML disabled on the <button>.
.is-hover.tabDocumentation helper — forces the hover visual at rest for the Full matrix. Not needed in production.