Tabs
A horizontal tab list for choosing one sibling content panel, with a bold underline marking the controlled active tab.
Implementation previewEvery example. In one place.
Explore every named example, including states, compositions, and theme variants.
Example source
packages/design-system/src/components/controls/Tabs/Tabs.stories.tsxExport PlaygroundImplementation 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.
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
| Prop | Type | Presence | Notes |
|---|---|---|---|
items | TabItem[] | Required | Tabs in render order. Past roughly five, switch `fit` to `content` or use a picker. |
activeId | string | Required | Controlled `id` of the active tab. An id matching no item hides the underline entirely. |
onSelect | ((id: string) => void) | undefined | Optional | Called with the pressed tab's `id`. Never fires for the tab that is already active. |
ariaLabel | string | undefined | Optional | Accessible name for the tablist, such as `Dashboard sections`. |
fit | TabsFit | undefined | Optional | How tabs claim width. Defaults to `equal`.Source default: 'equal' |
className | string | undefined | Optional | |
haptic | HapticFeedback | undefined | Optional | Haptic feedback for tab changes. Defaults to `true` (`selection`).Source default: true |
testId | string | undefined | Optional | Selector 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.jsonParts
- 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
Sizes
None specifiedMeasurements
- 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.