Skip to content
Cloaked Design SystemControls

Tabs

A horizontal tab list for choosing one sibling content panel, with a bold underline marking the controlled active tab.

Implementation preview
COMPLETE STORYBOOK REFERENCE

Every example. In one place.

Explore every named example, including states, compositions, and theme variants.

5 stories
SELECTED EXAMPLE

Playground

Open controls
Playground selectedOpen canvas
Example sourcepackages/design-system/src/components/controls/Tabs/Tabs.stories.tsxExport Playground

Implementation preview Local Storybook build 0.2.0-preview.1. Source specification status does not approve this runtime.

Stories + source JSON ↗

Local specification source status: preview. Implementation preview pending sign-off.

A horizontal tab list for choosing one sibling content panel, with a bold underline marking the controlled active tab.

Design-owned component contract ↗Version 0.2.0-preview.1 · Implementation preview pending sign-off
Implementation API 1 entry point · source-derived preview

Read the actual callable API and preserve its composition constraints. Each union branch is a complete alternative; props marked forbidden cannot be supplied.

import { Tabs } from '@cloakedinc/design-system';
TabsTabsProps

packages/design-system/src/components/controls/Tabs/Tabs.tsx:56

PropTypePresenceNotes
itemsTabItem[]RequiredTabs in render order. Past roughly five, switch `fit` to `content` or use a picker.
activeIdstringRequiredControlled `id` of the active tab. An id matching no item hides the underline entirely.
onSelect((id: string) => void) | undefinedOptionalCalled with the pressed tab's `id`. Never fires for the tab that is already active.
ariaLabelstring | undefinedOptionalAccessible name for the tablist, such as `Dashboard sections`.
fitTabsFit | undefinedOptionalHow tabs claim width. Defaults to `equal`.Source default: 'equal'
classNamestring | undefinedOptional
hapticHapticFeedback | undefinedOptionalHaptic feedback for tab changes. Defaults to `true` (`selection`).Source default: true
testIdstring | undefinedOptionalSelector for the strip, and the stem for each tab's own. Without it no tab gets a selector, `TabItem.testId` included.

Only explicit destructured parameter initializers are extracted. Other runtime defaults remain in implementation source.

Overview

A horizontal tab strip (role="tablist") for switching between sibling content panels — Overview / Activity / Settings.

When to use

Reach for Tabs when each option reveals a different panel of content and you want them navigable inline. It scales to more items than a SegmentedControl. In-app it splits the activity views in ActionsDetail. The one it is most confused with is SegmentedControl: use that instead when the options are a compact 2–4 value filter that re-slices a single view rather than swapping panels. Past ~5 tabs, prefer a picker over cramming the strip.

Choosing a fit

equal divides the strip evenly and suits sets that fit the width, up to roughly five panels. content sizes each tab to its label and scrolls the strip horizontally, for sets that overflow the equal fit — the underline tracks the active label's width instead of a computed column.

Component specification

$schema../../../specs/component-spec.schema.json
Parts
Id
tab-list
Label
Tab list
Description
Owns the accessible group name, horizontal spacing, divider, backdrop, and (content fit) horizontal scrolling.
Id
tab
Label
Tab
Description
Native button exposing tab semantics and controlled selected state.
Id
label
Label
Tab label
Description
Names the sibling content panel.
Id
active-indicator
Label
Active indicator
Description
Bottom border marking the selected tab.
Allowed Design Choices
Variants
Underline
Label
Underline
Description
Horizontal text tabs with one bold bottom indicator.
Sizes
Fits
Equal
Label
Equal
Description
Sibling tabs divide the strip evenly; use when the labels fit the width.
Content
Label
Content
Description
Each tab is as wide as its label and the strip scrolls horizontally; use for sets too long to divide legibly.
Variants
Id
underline
Label
Underline
Description
Sibling tabs with one selected underline; equal-width by default, or content-sized and scrolling under the content fit.
Platforms
  • ios
  • android
  • web
SizesNone specified
Measurements
Id
tab-gap
Label
Equal-fit tab gap
Kind
exact
Token
--cds-space-24
Value
24
Unit
px
Applies To
  • tab-list
  • tab
Id
tab-list-horizontal-padding
Label
Equal-fit tab-list horizontal padding
Kind
exact
Token
--cds-space-16
Value
16
Unit
px
Applies To
  • tab-list
Id
tab-vertical-padding
Label
Equal-fit tab vertical padding
Kind
exact
Token
--cds-space-12
Value
12
Unit
px
Applies To
  • tab
  • label
  • active-indicator
Id
content-tab-horizontal-padding
Label
Content-fit tab horizontal padding
Kind
exact
Token
--cds-space-20
Value
20
Unit
px
Applies To
  • tab
  • label
Id
content-tab-vertical-padding
Label
Content-fit tab vertical padding
Kind
exact
Token
--cds-space-16
Value
16
Unit
px
Applies To
  • tab
  • label
  • active-indicator
Layout
Alignment
Equal-fit tabs share the available width; content-fit tabs size to their labels and scroll. Both center labels over the common bottom divider.
Flow
Sibling tabs form one horizontal row with the active indicator attached to one tab.
Notes
  • Reach for Tabs when each option reveals a different panel of content and you want them navigable inline; it scales to more items than a SegmentedControl.
  • Use SegmentedControl instead when the options are a compact 2-4 value filter that re-slices a single view rather than swapping panels.
  • Past ~5 tabs, prefer a picker over cramming the strip.
Typography And Color
Typography
Label
Tab label
Description
Use the body ramp for primary navigation labels.
Token
--cds-type-body-size
Colors
Label
Tab label
Description
Use primary text emphasis.
Token
--cds-text-primary
Label
Divider
Description
Use the semantic divider beneath the tab strip.
Token
--cds-divider
Label
Active indicator
Description
Use the bold semantic surface for the selected underline.
Token
--cds-bg-bold
States
Id
inactive
Label
Inactive
Description
Shows a lower-emphasis label with selected false.
Id
active
Label
Active
Description
Shows the active underline and selected true.
Id
focused
Label
Focused
Description
Retains native button keyboard focus indication.
Behavior
Label
Controlled selection
Description
Emit a tab id only when it differs from the controlled active id.
Label
Panel ownership
Description
Leave panel rendering and visibility to the integrating surface.
Label
Sibling scope
Description
Use tabs only for peer content destinations within the same surface.
Label
Active tab reveal
Description
When tabs are sized to their content, scroll the strip the minimum distance to bring the active tab into view — on mount, on external selection changes, and on any reflow that moves it (a font swap or a sibling widening), not only on press. Scroll the strip only; never scroll an enclosing scroll container, which would displace a surface that is mid-transition.
Accessibility
Label
Tab-list name
Description
Provide a stable accessible name for the tab list.
Label
Tab semantics
Description
Expose every item as a tab with its selected state and visible label.
Label
Panel relationship
Description
The integrating surface should connect each tab to its owned tab panel when panels are rendered.
Content Adaptation
Label
Tab count
Description
Keep labels concise and the set at roughly five or fewer. Use the equal fit while the labels fit the width and the content fit once they overflow.
Label
Parallel labels
Description
Use concise peer nouns that describe the content panels.
Motion Haptics
Motion
Label
Selection treatment
Description
Move selection by updating underline and label emphasis without resizing the strip; the content fit also scrolls the newly selected tab into view.
Haptics
Label
Tab feedback
Description
Request the selection haptic after the active tab changes.
Platform Differences
Platforms
  • web
Description
Tabs expose ARIA tab-list and tab roles while the integrating surface owns DOM tab panels and their relationships.
Rationale
The reusable strip cannot know panel ids; native containers likewise connect the selected tab to platform-owned content.
Implementation preview

This design-owned implementation is ready to try and review. It does not establish product approval. Imported specifications retain their original review status.

Inspect version 0.2.0-preview.1