Tabs

Groups the tabs and the corresponding panels.

Tabs is a collection of React components that combine to render an ARIA-compliant tabs pattern.

import { Tabs } from '@wordpress/ui';

View on Storybook

View source on GitHub

PropsPermalink to this section

NameDefaultDescription
value

any

The value of the currently active Tab. Use when the component is controlled. When the value is null, no Tab will be active.

defaultValue0

any

The default value. Use when the component is not controlled. When the value is null, no Tab will be active.

orientation'horizontal'

"horizontal" | "vertical"

The component orientation (layout flow direction).

onValueChange

(value: any, eventDetails: TabsRootChangeEventDetails) => void

Callback invoked when new value is being set.

The event reason is 'none' for user-initiated changes, such as a click or keyboard navigation; 'initial' for the first automatic selection or fallback in uncontrolled roots when defaultValue is omitted or undefined, including when the implicit initial value is disabled or missing; 'disabled' for automatic fallback when the selected tab becomes disabled in uncontrolled roots; or 'missing' for automatic fallback when the selected tab is removed, or when an explicit defaultValue never matches a mounted tab in uncontrolled roots.

For automatic changes, the selected value can be null when no enabled Tab is available as a fallback.

Automatic changes cannot be canceled; calling eventDetails.cancel() for 'initial', 'disabled', or 'missing' has no effect.

className

string

CSS class name to apply to the element.

style

CSSProperties

CSS style to apply to the element.

render

ComponentRenderFn<HTMLAttributesWithRef<any>> | ReactElement<Record<string, unknown>, string | JSXElementConstructor<any>> | undefined

Replaces the component’s default HTML element using a given React element, or a function that returns a React element.

children

ReactNode

The content to be rendered inside the component.

ExamplesPermalink to this section

DefaultPermalink to this section

const Default = () => <Tabs.Root defaultValue="tab1">{[
        <Tabs.List key="list">
            <Tabs.Tab value="tab1">Tab 1</Tabs.Tab>
            <Tabs.Tab value="tab2">Tab 2</Tabs.Tab>
            <Tabs.Tab value="tab3">Tab 3</Tabs.Tab>
        </Tabs.List>,
        <Tabs.Panel value="tab1" key="panel-tab1">
            <ThemedParagraph>Selected tab: Tab 1</ThemedParagraph>
        </Tabs.Panel>,
        <Tabs.Panel value="tab2" key="panel-tab2">
            <ThemedParagraph>Selected tab: Tab 2</ThemedParagraph>
        </Tabs.Panel>,
        <Tabs.Panel value="tab3" key="panel-tab3">
            <ThemedParagraph>Selected tab: Tab 3</ThemedParagraph>
        </Tabs.Panel>,
    ]}</Tabs.Root>;

MinimalPermalink to this section

const Minimal = () => <Tabs.Root>{[
        <Tabs.List variant="minimal" key="list">
            <Tabs.Tab value="tab1">Tab 1</Tabs.Tab>
            <Tabs.Tab value="tab2">Tab 2</Tabs.Tab>
            <Tabs.Tab value="tab3">Tab 3</Tabs.Tab>
        </Tabs.List>,
        <Tabs.Panel value="tab1" key="panel-tab1">
            <ThemedParagraph>Selected tab: Tab 1</ThemedParagraph>
        </Tabs.Panel>,
        <Tabs.Panel value="tab2" key="panel-tab2">
            <ThemedParagraph>Selected tab: Tab 2</ThemedParagraph>
        </Tabs.Panel>,
        <Tabs.Panel value="tab3" key="panel-tab3">
            <ThemedParagraph>Selected tab: Tab 3</ThemedParagraph>
        </Tabs.Panel>,
    ]}</Tabs.Root>;

Size And Overflow PlaygroundPermalink to this section

const SizeAndOverflowPlayground = function SizeAndOverflowPlayground( props ) {
    const [ fullWidth, setFullWidth ] = useState( false );
    return (
        <div>
            <div
                style={ {
                    maxWidth: '40rem',
                    marginBottom: '1rem',
                    color: 'var( --wpds-color-foreground-content-neutral )',
                } }
            >
                <p>
                    This story helps understand how the TabList component
                    behaves under different conditions. The container below
                    (with the dotted red border) can be horizontally
                    resized, and it has a bit of padding to be out of the
                    way of the TabList.
                </p>
                <p>
                    The button will toggle between full width (adding{ ' ' }
                    <code>width: 100%</code>) and the default width.
                </p>
                <p>Try the following:</p>
                <ul>
                    <li>
                        <strong>Small container</strong> that causes tabs to
                        overflow with scroll.
                    </li>
                    <li>
                        <strong>Large container</strong> that exceeds the
                        normal width of the tabs.
                        <ul>
                            <li>
                                <strong>
                                    With <code>width: 100%</code>
                                </strong>{ ' ' }
                                set on the TabList (tabs fill up the space).
                            </li>
                            <li>
                                <strong>
                                    Without <code>width: 100%</code>
                                </strong>{ ' ' }
                                (defaults to <code>auto</code>) set on the
                                TabList (tabs take up space proportional to
                                their content).
                            </li>
                        </ul>
                    </li>
                </ul>
            </div>
            <button
                style={ { marginBottom: '1rem' } }
                onClick={ () => setFullWidth( ! fullWidth ) }
            >
                { fullWidth
                    ? 'Remove width: 100% from TabList'
                    : 'Set width: 100% in TabList' }
            </button>
            <Tabs.Root
                { ...props }
                style={ {
                    ...props.style,
                    width: '20rem',
                    border: '2px dotted red',
                    padding: '1rem',
                    resize: 'horizontal',
                    overflow: 'auto',
                } }
            >
                <Tabs.List
                    style={ {
                        maxWidth: '100%',
                        width: fullWidth ? '100%' : undefined,
                    } }
                >
                    <Tabs.Tab value="tab1">
                        Label with multiple words
                    </Tabs.Tab>
                    <Tabs.Tab value="tab2">Short</Tabs.Tab>
                    <Tabs.Tab value="tab3">
                        Hippopotomonstrosesquippedaliophobia
                    </Tabs.Tab>
                    <Tabs.Tab value="tab4">Tab 4</Tabs.Tab>
                    <Tabs.Tab value="tab5">Tab 5</Tabs.Tab>
                </Tabs.List>

                <Tabs.Panel value="tab1">
                    <ThemedParagraph>Selected tab: Tab 1</ThemedParagraph>
                    <ThemedParagraph>
                        (Label with multiple words)
                    </ThemedParagraph>
                </Tabs.Panel>
                <Tabs.Panel value="tab2">
                    <ThemedParagraph>Selected tab: Tab 2</ThemedParagraph>
                    <ThemedParagraph>(Short)</ThemedParagraph>
                </Tabs.Panel>
                <Tabs.Panel value="tab3">
                    <ThemedParagraph>Selected tab: Tab 3</ThemedParagraph>
                    <ThemedParagraph>
                        (Hippopotomonstrosesquippedaliophobia)
                    </ThemedParagraph>
                </Tabs.Panel>
                <Tabs.Panel value="tab4">
                    <ThemedParagraph>Selected tab: Tab 4</ThemedParagraph>
                </Tabs.Panel>
                <Tabs.Panel value="tab5">
                    <ThemedParagraph>Selected tab: Tab 5</ThemedParagraph>
                </Tabs.Panel>
            </Tabs.Root>
        </div>
    );
};

VerticalPermalink to this section

const Vertical = () => <Tabs.Root
    orientation="vertical"
    style={{
        minWidth: '320px',
        display: 'grid',
        gridTemplateColumns: '120px 1fr',
        gap: '24px',
    }} />;

With Disabled TabPermalink to this section

const WithDisabledTab = () => <Tabs.Root defaultValue="tab3">{[
        <Tabs.List key="list">
            <Tabs.Tab value="tab1" disabled>
                Tab 1
            </Tabs.Tab>
            <Tabs.Tab value="tab2">Tab 2</Tabs.Tab>
            <Tabs.Tab value="tab3">Tab 3</Tabs.Tab>
        </Tabs.List>,
        <Tabs.Panel value="tab1" key="panel-tab1">
            <ThemedParagraph>Selected tab: Tab 1</ThemedParagraph>
        </Tabs.Panel>,
        <Tabs.Panel value="tab2" key="panel-tab2">
            <ThemedParagraph>Selected tab: Tab 2</ThemedParagraph>
        </Tabs.Panel>,
        <Tabs.Panel value="tab3" key="panel-tab3">
            <ThemedParagraph>Selected tab: Tab 3</ThemedParagraph>
        </Tabs.Panel>,
    ]}</Tabs.Root>;

With Tab Icons And TooltipsPermalink to this section

const WithTabIconsAndTooltips = () => <Tabs.Root>{[
        <Tabs.List key="list">
            { tabWithIconsData.map( ( { value, label, icon: Icon } ) => (
                <Tooltip.Root key={ value }>
                    <Tooltip.Trigger
                        aria-label={ label }
                        render={ <Tabs.Tab value={ value } /> }
                    >
                        { /* TODO: potentially refactor with new Icon component */ }
                        <Icon
                            style={ {
                                width: '20px',
                                height: '20px',
                            } }
                        />
                    </Tooltip.Trigger>
                    <Tooltip.Popup
                        positioner={
                            <Tooltip.Positioner align="center" side="top" />
                        }
                    >
                        { label }
                    </Tooltip.Popup>
                </Tooltip.Root>
            ) ) }
        </Tabs.List>,
        tabWithIconsData.map( ( { value, label } ) => (
            <Tabs.Panel value={ value } key={ value }>
                <ThemedParagraph>Selected tab: { label }</ThemedParagraph>
            </Tabs.Panel>
        ) ),
    ]}</Tabs.Root>;

With Panels Always MountedPermalink to this section

const WithPanelsAlwaysMounted = () => <Tabs.Root>{[
        <Tabs.List key="list">
            <Tabs.Tab value="tab1">Tab 1</Tabs.Tab>
            <Tabs.Tab value="tab2">Tab 2</Tabs.Tab>
            <Tabs.Tab value="tab3">Tab 3</Tabs.Tab>
        </Tabs.List>,
        <Tabs.Panel value="tab1" keepMounted key="panel-tab1">
            <ThemedParagraph>Selected tab: Tab 1</ThemedParagraph>
        </Tabs.Panel>,
        <Tabs.Panel value="tab2" keepMounted key="panel-tab2">
            <ThemedParagraph>Selected tab: Tab 2</ThemedParagraph>
        </Tabs.Panel>,
        <Tabs.Panel value="tab3" keepMounted key="panel-tab3">
            <ThemedParagraph>Selected tab: Tab 3</ThemedParagraph>
        </Tabs.Panel>,
    ]}</Tabs.Root>;

With Non Focusable PanelsPermalink to this section

const WithNonFocusablePanels = () => <Tabs.Root>{[
        <Tabs.List key="list">
            <Tabs.Tab value="tab1">Tab 1</Tabs.Tab>
            <Tabs.Tab value="tab2">Tab 2</Tabs.Tab>
            <Tabs.Tab value="tab3">Tab 3</Tabs.Tab>
        </Tabs.List>,
        <Tabs.Panel value="tab1" tabIndex={ -1 } key="panel-tab1">
            <ThemedParagraph>Selected tab: Tab 1</ThemedParagraph>
            <ThemedParagraph>
                This tabpanel is not focusable, therefore tabbing into it
                will focus its first tabbable child.
            </ThemedParagraph>
            <button>Focus me</button>
        </Tabs.Panel>,
        <Tabs.Panel value="tab2" tabIndex={ -1 } key="panel-tab2">
            <ThemedParagraph>Selected tab: Tab 2</ThemedParagraph>
            <ThemedParagraph>
                This tabpanel is not focusable, therefore tabbing into it
                will focus its first tabbable child.
            </ThemedParagraph>
            <button>Focus me</button>
        </Tabs.Panel>,
        <Tabs.Panel value="tab3" tabIndex={ -1 } key="panel-tab3">
            <ThemedParagraph>Selected tab: Tab 3</ThemedParagraph>
            <ThemedParagraph>
                This tabpanel is not focusable, therefore tabbing into it
                will focus its first tabbable child.
            </ThemedParagraph>
            <button>Focus me</button>
        </Tabs.Panel>,
    ]}</Tabs.Root>;