Tabs

Organism
Underline, pill, vertical, with badges.

Playground

Every control sets a real prop of TabsProps. The JSX below updates as you go.
Props
Look
pick one
active
number
1
block
boolean
disabled
boolean
JSX · TabsProps
<Tabs underline active={1} />

Variants

8 examples from src/organisms/tabs/tabs.md, each rendered live from its own JSX.
Pill
<Tabs pills tabs={["All", "Active", "Archived"]} />
Vertical
<Tabs vertical tabs={["General", "Security", "Notifications"]} />
Responsive vertical
responsive renders a vertical rail as the horizontal underline look when the component's own container is at or below sm (640): container-measured, so a settings rail inside a narrow column flattens to a top tab bar instead of starving the panel beside it.
<Tabs vertical responsive tabs={["General", "Security", "Notifications"]} />
Badge counts
<Tabs tabs={[{ label: "All", badge: "142" }, { label: "Active", badge: "89" }, { label: "Archived", badge: "53" }]} />
Disabled tab
<Tabs tabs={["Overview", { label: "Billing", disabled: true }, "Settings"]} />
Block
<Tabs block tabs={["Overview", "Activity", "Settings"]} />
Scrollable overflow
A row longer than its container pans horizontally instead of clipping, at any container width and with no prop: the scroller is inert while the row fits, and on Android it then takes no sideways drag either, so a tap that drifts sideways still selects its tab and a swipe that starts across the row scrolls the page (while TalkBack explores by touch the scroller stays enabled, so the exploring finger reaches every tab). Selecting a tab (press or arrow key) scrolls it fully into view with a sliver of its neighbor left showing. block shares the row equally and never overflows; a vertical rail stacks instead.
<Tabs tabs={["General", "Security", "Notifications", "Billing", "Integrations", "Advanced"]} />
Wrapped overflow
wrap lays a row longer than its container out on further lines inside one track instead of panning it, so every tab is on screen at once: the track fills its container and grows taller, its corners staying concentric with the pills. Reach for it where the strip is a page's own navigation on a phone (the docs example rail); keep the scroller where the strip sits in a toolbar that must stay one line tall. block never overflows and wins over wrap; a responsive vertical rail honors wrap once it flattens.
<Tabs wrap tabs={["General", "Security", "Notifications", "Billing", "Integrations", "Advanced", "Members", "Audit log"]} />

States

Hover, focus and press are pinned so they can be compared side by side; the rest are real props.
default
hover
focus

Anatomy

1
Tab list
2
Tab
3
Active indicator
4
Badge
Dimmed parts appear only in some states or variants.

Props

13 props on TabsProps, grouped as they are declared in source. Required props are marked *.
Prop
Type
Default
Description
Props
5 props
tabs
TabItem[]
–
Triggers, left to right. Strings, or { label, badge } for a count.
active
number
–
Index of the active trigger; omit for uncontrolled use.
defaultActive
number
–
Initial active index for uncontrolled use (a bare <Tabs /> switches out of the box).
onSelect
(index: number) => void
–
Called with the pressed trigger's index (both modes). Matches TabBar and ButtonGroup, which also fire onSelect for the active-index change.
testID
string
–
E2E hook forwarded to the tablist row.
Look
pick one
3 props
pills
boolean
false
vertical
boolean
false
underline
boolean
false
Layout
5 props
block
boolean
false
wrap
boolean
false
responsive
boolean
false
Responsive (vertical look only): render the EXISTING horizontal underline look when the component's own CONTAINER is at or below sm (640), where a side rail would starve the panel beside it. Container-measured with a viewport seed. pills and underline are unaffected.
disabled
boolean
false
style
StyleProp<ViewStyle>
–
Outer layout composition only (width/flex within a parent), never a restyle hook.

Accessibility and keyboard

Keyboard
ArrowLeft
ArrowRight
Moves between tabs
Home
End
First · last tab
Enter
Space
Activates the focused tab
Tab
Leaves for the panel
Semantics
Roles
tab
tablist
States
disabled
selected
ARIA on web
aria-disabled
aria-selected
Below the platform minimum, the touch area grows to 44 pt on iOS and 48 dp on Android without moving the layout.

Platform skins

The playground's props on each platform: same tree, native shapes, type and touch sizes.
Web
control 10 · field 10 · card 16 · Instrument Sans
iOS
capsule controls · field 8 · card 12 · SF Pro · 44 pt
Android
Material 3 stadium · field 4 · card 12 · Roboto · 48 dp

Usage

<Tabs tabs={["General", "Security", "Notifications"]} />
On the web the underline look marks the active tab with a glowing accent bar on the row's hairline, pills sit on a rounded track with a raised thumb, and the vertical rail washes its active row in the accent. iOS draws its segmented control (UISegmentedControl): underline and pills are a capsule track with a raised thumb, and vertical is a grouped rail. Android draws Material 3's tabs. In glass mode a filled selection is a control-layer pane behind the selected label, in the platform skin's selected tint with readable foreground ink. Solid mode keeps the complete platform treatment.

Tokens

Read from the playground render. Values follow the scheme and the ion hue.
--fd
--fm
--ft
--ion
--ion-glow
--ln
--sf4
--tx
--tx2

Related components