Tab
Presentational top tabs with controlled selection, badges, scrollable layouts and optional swipe between panels.
Installation
npx axiom add tabpnpm dlx axiom add tabyarn dlx axiom add tabbun x axiom add tabAlso copies: icon, badge, text, tappable and useControllableState. Installs react-native-reanimated.
Colors come from tab.{underline,pill}.{default,selected,disabled} in the component tokens.
Usage
import { Tab } from "@/components/ui/tab";
<Tab value={section} onValueChange={setSection}>
<Tab.List>
<Tab.Item value="all">All</Tab.Item>
<Tab.Item value="unread">Unread</Tab.Item>
<Tab.Item value="archived">Archived</Tab.Item>
</Tab.List>
<Tab.Content>
<Tab.Panel value="all">
<Feed />
</Tab.Panel>
<Tab.Panel value="unread">
<UnreadFeed />
</Tab.Panel>
<Tab.Panel value="archived">
<Archive />
</Tab.Panel>
</Tab.Content>
</Tab>;Props
Tab
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | — | Value of the selected tab in controlled usage. |
defaultValue | string | — | Initial selection when the component owns its state. |
onValueChange | (value: string) => void | — | Called after an enabled tab is pressed. Use it to swap nearby content, not to push a new screen. |
children | ReactNode | — | A Tab.List and optional content. |
| View props | ViewProps | — | Passed to the root View, including style, accessibility props and ref. |
Tab.List
Tab.List renders a View by default. With scrollable, it renders a horizontal ScrollView and keeps each item at its natural width.
| Prop | Type | Default | Description |
|---|---|---|---|
variant | 'underline' | 'pill' | 'underline' | Uses an indicator below the label or a filled capsule behind it. |
scrollable | boolean | false | Changes the list from View to ScrollView. |
| View or ScrollView props | ViewProps | ScrollViewProps | — | Passed to the rendered native component. contentContainerStyle is available when scrollable is true. |
Tab.Item
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | — | Stable identifier returned through onValueChange. |
children | ReactNode | — | Short label. One or two words scan best. |
icon | IconName | ReactNode | — | Optional icon before the label. Avoid icons when every tab is already clear in text. |
badge | number | string | boolean | — | Count, short status or dot next to the label. |
disabled | boolean | false | Blocks selection and exposes the disabled accessibility state. |
accessibilityLabel | string | label text | Spoken label when the visible text needs more context. |
| Tappable props | TappableProps | — | Passed to the press target. Internal press and layout handlers are composed with yours. |
Tab.Content
Tab.Content is a View. It renders only the selected Tab.Panel.
| Prop | Type | Default | Description |
|---|---|---|---|
| View props | ViewProps | — | Passed to the content View. |
Tab.Pager
Tab.Pager is a horizontal ScrollView with pagingEnabled. Use it instead of Tab.Content when users should be able to swipe between panels.
| Prop | Type | Default | Description |
|---|---|---|---|
| ScrollView props | ScrollViewProps | — | Passed to the pager. Internal layout and momentum handlers are composed with yours. |
Tab.Panel
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | — | Value of the Tab.Item this panel belongs to. |
children | ReactNode | — | Content shown while that tab is selected. |
| View props | ViewProps | — | Passed to the panel View. Its layout handler is composed with yours. |
The indicator travels on a spring, and lands without animating the first time: it has nothing to travel from until the items have been measured. Reduced motion moves it without animation.
Tab sets the tab list, tab and selected accessibility roles. Panels are optional. Without Tab.Content or Tab.Pager, the screen can render its content elsewhere from the selected value.
Swipe to switch
Use Tab.Pager when the panels contain peer views such as feeds, inboxes or order lists. Use Tab.Content when a panel owns horizontal interaction, for example a carousel, a pannable chart or rows with swipe actions.
<Tab value={section} onValueChange={setSection} style={{ flex: 1 }}>
<Tab.List>
<Tab.Item value="all">All</Tab.Item>
<Tab.Item value="mentions">Mentions</Tab.Item>
</Tab.List>
<Tab.Pager>
<Tab.Panel value="all">
<Feed />
</Tab.Panel>
<Tab.Panel value="mentions">
<Mentions />
</Tab.Panel>
</Tab.Pager>
</Tab>The pager needs a bounded width and height. It uses the native ScrollView paging behavior, measures each panel at runtime and updates the selected value when scrolling settles. Pressing a tab scrolls to the measured position of its panel. Off-screen panels stay hidden from the screen reader.
Tab.Pager passes its props to the underlying ScrollView, so scrollEnabled, decelerationRate, keyboard behavior and scroll callbacks remain configurable.
Use cases
Switching one data view
Tabs work when the content changes but the screen purpose stays the same. Keep the filter state local to the screen.
<Tab value={status} onValueChange={setStatus}>
<Tab.List>
<Tab.Item value="active">Active</Tab.Item>
<Tab.Item value="past">Past</Tab.Item>
</Tab.List>
</Tab>Categories that need horizontal scrolling
When labels cannot fit without shrinking, let the row scroll. The selected tab should be brought into view after selection.
<Tab value={category} onValueChange={setCategory}>
<Tab.List scrollable>
{categories.map((item) => (
<Tab.Item key={item.id} value={item.id}>
{item.label}
</Tab.Item>
))}
</Tab.List>
</Tab>Compact mode switch with pills
The pill variant works for a short, local mode switch. For two or three equally sized options inside a form, prefer SegmentedControl.
<Tab value={view} onValueChange={setView}>
<Tab.List variant="pill">
<Tab.Item value="grid" icon="grid">
Grid
</Tab.Item>
<Tab.Item value="map" icon="map">
Map
</Tab.Item>
</Tab.List>
</Tab>Related
- BottomTabBar, for top-level destinations
- SegmentedControl, for compact form choices
- Badge