Tabs

Use in pages to manage sub-pages

Tabs organizes related panels behind a controlled or uncontrolled, keyboard-navigable list of triggers.

Features

  • Accessible, composable, customizable, and animatable

  • Sizing with controlled or uncontrolled state

  • Automatic and manual activation modes

  • Full keyboard navigation

Installation

Tabs is already installed in tamagui, or you can install it independently:

yarn add @tamagui/tabs

Usage

// copy-paste: these skins import from files you own. copy each registry item
// below into your app, then adjust the relative import paths to fit.
// components/tamagui/Tabs.tsx (registry item "tabs")
// npm dependencies: @tamagui/core @tamagui/tabs @tamagui/text
import { SizableText } from 'tamagui';
import { Tabs } from "../components/tamagui/Tabs";
export default () => <Tabs defaultValue="tab1" width={400}>
<Tabs.List>
<Tabs.Tab value="tab1">
<SizableText>Tab 1</SizableText>
</Tabs.Tab>
<Tabs.Tab value="tab2">
<SizableText>Tab 2</SizableText>
</Tabs.Tab>
</Tabs.List>
<Tabs.Content value="tab1">
<H5>Tab 1</H5>
</Tabs.Content>
<Tabs.Content value="tab2">
<H5>Tab 2</H5>
</Tabs.Content>
</Tabs>;

Headless usage

To use headless tabs without @tamagui/core, install @tamagui/tabs-headless:

yarn add @tamagui/tabs-headless

The useTabs hook provides all the state and accessibility props needed to build custom tabs with any styling solution:

import { useTabs } from '@tamagui/tabs-headless'
function MyTabs() {
const { tabsProps, listProps, getTabProps, getContentProps, value } = useTabs({
defaultValue: 'tab1',
orientation: 'horizontal',
})
return (
<div {...tabsProps}>
<div {...listProps}>
<button {...getTabProps('tab1')}>Tab 1</button>
<button {...getTabProps('tab2')}>Tab 2</button>
</div>
<div {...getContentProps('tab1')}>{value === 'tab1' && <p>Content 1</p>}</div>
<div {...getContentProps('tab2')}>{value === 'tab2' && <p>Content 2</p>}</div>
</div>
)
}

API reference

Tabs

Root tabs component. Extends View. Passing the size prop to this component will affect the descendants.

Props

  • value

    string

    The value for the selected tab, if controlled.

  • defaultValue

    string

    The value of the tab to select by default, if uncontrolled.

  • onValueChange

    (value: string) => void

    A function called when a new tab is selected.

  • orientation

    "horizontal" | "vertical"

    Default: 

    horizontal

    The orientation the tabs are laid out in.

  • dir

    "ltr" | "rtl"

    The direction of navigation between toolbar items.

  • activationMode

    "manual" | "automatic"

    Default: 

    automatic

    Whether a tab is activated automatically (on focus) or manually (on click/enter). Automatic activation is web-only; native always activates manually.

  • size

    'xs' | 'sm' | 'md' | 'lg' | 'xl'

    Default: 

    md

    Sets the size of the tab triggers. Passed down via context to all Tabs.Tab children.

  • loop

    boolean

    Default: 

    true

    Whether keyboard navigation should loop from last to first and vice versa.

  • Tabs.List

    Container for the trigger buttons. Supports scrolling by extending Group.

    Props

  • loop

    boolean

    Default: 

    true

    Whether or not to loop over after reaching the end or start of the items. Used mainly for managing keyboard navigation.

  • Since Tabs.List extends Group, the same limitation applies: automatic border radius detection only works when Tabs.Tab is a direct child of Tabs.List. If you wrap tabs in custom components, see the Group docs on nested items for workarounds.

    Tabs.Tab

    Extends YStack, adding:

    Props

  • value

    string

    The value for the tabs state to be changed to after activation of the trigger.

  • onInteraction

    (type: InteractionType, layout: TabLayout | null) => void

    Used for making custom indicators when trigger is interacted with.

  • disabled

    boolean

    Whether the tab is disabled.

  • unstyled

    boolean

    When true, removes all default Tamagui styling.

  • activeStyle

    StylePiece

    Styles to apply when the tab is selected. Accepts a style() piece.

  • activeTheme

    string | null

    Theme to apply when the tab is selected. Set to null for no theme change.

  • Tabs.Content

    Where each tab’s content will be shown. Extends YStack, adding:

    Props

  • value

    string

    Will show the content when the value matches the state of Tabs root.

  • forceMount

    boolean

    Default: 

    false

    Used to force mounting when more control is needed. Useful when controlling animation with Tamagui animations.

  • Headless props

    useTabs return value

    PropertyTypeDescription
    valuestringThe currently selected tab value
    setValue(value: string) => voidFunction to change the selected tab
    direction'ltr' | 'rtl'The resolved text direction
    tabsPropsobjectProps to spread on the tabs container
    listPropsobjectProps to spread on the tab list
    getTabProps(value: string, disabled?: boolean) => objectGet props for a tab trigger
    getContentProps(value: string) => objectGet props for a tab content panel
    contextValueTabsContextValueContext value for component-based API
    Property
    value
    Type
    string
    Description
    The currently selected tab value
    Property
    setValue
    Type
    (value: string) => void
    Description
    Function to change the selected tab
    Property
    direction
    Type
    'ltr' | 'rtl'
    Description
    The resolved text direction
    Property
    tabsProps
    Type
    object
    Description
    Props to spread on the tabs container
    Property
    listProps
    Type
    object
    Description
    Props to spread on the tab list
    Property
    getTabProps
    Type
    (value: string, disabled?: boolean) => object
    Description
    Get props for a tab trigger
    Property
    getContentProps
    Type
    (value: string) => object
    Description
    Get props for a tab content panel
    Property
    contextValue
    Type
    TabsContextValue
    Description
    Context value for component-based API

    useTab

    Hook for individual tab triggers when using the component-based API.

    const { isSelected, tabProps } = useTab({
    value: 'tab1',
    disabled: false,
    onPress: () => {},
    onKeyDown: () => {},
    onFocus: () => {},
    })

    useTabContent

    Hook for tab content panels when using the component-based API.

    const { isSelected, shouldMount, contentProps } = useTabContent({
    value: 'tab1',
    forceMount: false,
    })

    Examples

    Animations

    Here is a demo with more advanced animations using AnimatePresence and Tabs.Tab’s onInteraction prop.

    Source

    v2-look Tabs: token-based trigger sizing with theme background, hover/press/focus styling, and disabled dimming over the unstyled @tamagui/ui Tabs behavior. This is the exact source the registry ships — copy it into your app and the examples above import from your copy.

    components/tamagui/Tabs.tsx
    import { type ComponentSize, createStyledContext, createStyledHOC, Text, styled, withStaticProperties, } from '@tamagui/core' import { wrapChildrenInText } from '@tamagui/text' import { Tabs as TabsBehavior } from '@tamagui/tabs' export type TabsSize = ComponentSize | boolean const TabsContext = createStyledContext<{ size?: TabsSize }>({ size: 'md' }) const tabsTabSize = { xs: { paddingInline: '2', paddingBlock: '1', gap: '1' }, sm: { paddingInline: '3', paddingBlock: '1.5', gap: '1.5' }, md: { paddingInline: '4', paddingBlock: '2', gap: '2' }, lg: { paddingInline: '6', paddingBlock: '2', gap: '2' }, xl: { paddingInline: '8', paddingBlock: '2.5', gap: '2.5' }, } as const const tabsTextSize = { xs: { fontSize: 'xs', lineHeight: 'xs' }, sm: { fontSize: 'sm', lineHeight: 'sm' }, md: { fontSize: 'sm', lineHeight: 'sm' }, lg: { fontSize: 'base', lineHeight: 'base' }, xl: { fontSize: 'lg', lineHeight: 'lg' }, } as const export const TabsFrame = styled(TabsBehavior, { displayName: 'Tabs', context: TabsContext, }) export const TabsList = styled(TabsBehavior.List, { displayName: 'TabsList', }) const TabsTabFrame = styled(TabsBehavior.Tab, { displayName: 'TabsTrigger', context: TabsContext, variants: { unstyled: { false: { alignItems: 'center', justifyContent: 'center', backgroundColor: 'background hover:background-hover press:background-press', borderRadius: 0, borderWidth: 0, cursor: 'pointer', flexDirection: 'row', flexWrap: 'nowrap', userSelect: 'none', outlineColor: 'focus-visible:outline-color', outlineStyle: 'focus-visible:solid', outlineWidth: 'focus-visible:2px', zIndex: 'focus-visible:10', }, }, size: { ...tabsTabSize, true: tabsTabSize.md, }, disabled: { true: { cursor: 'not-allowed', opacity: 0.45, }, }, } as const, defaultVariants: { unstyled: false, size: 'md', }, }) const TabsTabText = styled(Text, { context: TabsContext, fontFamily: 'body', color: 'color', variants: { size: { ...tabsTextSize, true: tabsTextSize.md, }, }, defaultVariants: { size: 'md', }, }) export const TabsTab = createStyledHOC(TabsTabFrame, function TabsTab(props, ref) { const { children, ...frameProps } = props const contextSize = TabsContext.useStyledContext()?.size const size = props.size ?? contextSize ?? 'md' return ( <TabsTabFrame {...frameProps} ref={ref}> {wrapChildrenInText(TabsTabText, { children, size })} </TabsTabFrame> ) }) export const TabsContent = styled(TabsBehavior.Content, { displayName: 'TabsContent', }) export const Tabs = withStaticProperties(TabsFrame, { List: TabsList, Tab: TabsTab, Content: TabsContent, })

    Dependencies

    yarn add @tamagui/core @tamagui/tabs @tamagui/text

    Expects theme tokens: background, background-hover, background-press, outline-color.

    Need raw behavior without any skin? tamagui/unstyled re-exports the @tamagui/ui primitives (advanced).