Tabs
Easily create accessible, fully customizable tab interfaces, with robust focus management and keyboard navigation support.
To get started, install Headless UI via npm:
npm install @headlessui/react
Tabs are built using the Tab.Group
, Tab.List
, Tab
, Tab.Panels
, and Tab.Panel
components. By default the first tab is selected, and clicking on any tab or selecting it with the keyboard will activate the corresponding panel.
import { Tab } from '@headlessui/react' function MyTabs() { return ( <Tab.Group> <Tab.List> <Tab>Tab 1</Tab> <Tab>Tab 2</Tab> <Tab>Tab 3</Tab> </Tab.List> <Tab.Panels> <Tab.Panel>Content 1</Tab.Panel> <Tab.Panel>Content 2</Tab.Panel> <Tab.Panel>Content 3</Tab.Panel> </Tab.Panels> </Tab.Group> ) }
Headless UI keeps track of a lot of state about each component, like which tab option is currently checked, whether a popover is open or closed, or which item in a menu is currently active via the keyboard.
But because the components are headless and completely unstyled out of the box, you can't see this information in your UI until you provide the styles you want for each state yourself.
Each component exposes information about its current state via render props that you can use to conditionally apply different styles or render different content.
For example, the Tab
component exposes a selected
state, which tells you if the tab is currently selected.
import { Fragment } from 'react' import { Tab } from '@headlessui/react' function MyTabs() { return ( <Tab.Group> <Tab.List> <Tab as={Fragment}>
{({ selected }) => (/* Use the `selected` state to conditionally style the selected tab. */ <button className={selected ? 'bg-blue-500 text-white' : 'bg-white text-black'} > Tab 1 </button> )} </Tab> {/* ... */} </Tab.List> <Tab.Panels> <Tab.Panel>Content 1</Tab.Panel> {/* ... */} </Tab.Panels> </Tab.Group> ) }
For the complete render prop API for each component, see the component API documentation.
Each component also exposes information about its current state via a data-headlessui-state
attribute that you can use to conditionally apply different styles.
When any of the states in the render prop API are true
, they will be listed in this attribute as space-separated strings so you can target them with a CSS attribute selector in the form [attr~=value]
.
For example, here's what the Tab.Group
component with some child Tab
components renders when the second tab is selected
:
<!-- Rendered `Tab.Group` --> <div> <button data-headlessui-state="">Tab 1</button> <button data-headlessui-state="selected">Tab 2</button> <button data-headlessui-state="">Tab 3</button> </div> <div> <div data-headlessui-state="">Content 1</div> <div data-headlessui-state="selected">Content 2</div> <div data-headlessui-state="">Content 3</div> </div>
If you are using Tailwind CSS, you can use the @headlessui/tailwindcss plugin to target this attribute with modifiers like ui-open:*
:
import { Tab } from '@headlessui/react' function MyTabs() { return ( <Tab.Group> <Tab.List>
<Tab className="ui-selected:bg-blue-500 ui-selected:text-white ui-not-selected:bg-white ui-not-selected:text-black">Tab 1 </Tab> {/* ... */} </Tab.List> <Tab.Panels> <Tab.Panel>Content 1</Tab.Panel> {/* ... */} </Tab.Panels> </Tab.Group> ) }
To disable a tab, use the disabled
prop on the Tab
component. Disabled tabs cannot be selected with the mouse, and are also skipped when navigating the tab list using the keyboard.
import { Tab } from '@headlessui/react' function MyTabs() { return ( <Tab.Group> <Tab.List> <Tab>Tab 1</Tab>
<Tab disabled>Tab 2</Tab><Tab>Tab 3</Tab> </Tab.List> <Tab.Panels> <Tab.Panel>Content 1</Tab.Panel> <Tab.Panel>Content 2</Tab.Panel> <Tab.Panel>Content 3</Tab.Panel> </Tab.Panels> </Tab.Group> ) }
By default, tabs are automatically selected as the user navigates through them using the arrow keys.
If you'd rather not change the current tab until the user presses Enter
or Space
, use the manual
prop on the Tab.Group
component. This can be helpful if selecting a tab performs an expensive operation and you don't want to run it unnecessarily.
import { Tab } from '@headlessui/react' function MyTabs() { return (
<Tab.Group manual><Tab.List> <Tab>Tab 1</Tab> <Tab>Tab 2</Tab> <Tab>Tab 3</Tab> </Tab.List> <Tab.Panels> <Tab.Panel>Content 1</Tab.Panel> <Tab.Panel>Content 2</Tab.Panel> <Tab.Panel>Content 3</Tab.Panel> </Tab.Panels> </Tab.Group> ) }
The manual
prop has no impact on mouse interactions — tabs will still be selected as soon as they are clicked.
If you've styled your Tab.List
to appear vertically, use the vertical
prop to enable navigating with the up and down arrow keys instead of left and right, and to update the aria-orientation
attribute for assistive technologies.
import { Tab } from '@headlessui/react' function MyTabs() { return (
<Tab.Group vertical><Tab.List> <Tab>Tab 1</Tab> <Tab>Tab 2</Tab> <Tab>Tab 3</Tab> </Tab.List> <Tab.Panels> <Tab.Panel>Content 1</Tab.Panel> <Tab.Panel>Content 2</Tab.Panel> <Tab.Panel>Content 3</Tab.Panel> </Tab.Panels> </Tab.Group> ) }
To change which tab is selected by default, use the defaultIndex={number}
prop on the Tab.Group
component.
import { Tab } from '@headlessui/react' function MyTabs() { return (
<Tab.Group defaultIndex={1}><Tab.List> <Tab>Tab 1</Tab>{/* Selects this tab by default */}<Tab>Tab 2</Tab><Tab>Tab 3</Tab> </Tab.List> <Tab.Panels> <Tab.Panel>Content 1</Tab.Panel>{/* Displays this panel by default */}<Tab.Panel>Content 2</Tab.Panel><Tab.Panel>Content 3</Tab.Panel> </Tab.Panels> </Tab.Group> ) }
If you happen to provide an index that is out of bounds, then the last non-disabled tab will be selected on initial render. (For example, <Tab.Group defaultIndex={5}
in the example above would render the third panel as selected.)
To run a function whenever the selected tab changes, use the onChange
prop on the Tab.Group
component.
import { Tab } from '@headlessui/react' function MyTabs() { return ( <Tab.Group
onChange={(index) => {console.log('Changed selected tab to:', index)}}> <Tab.List> <Tab>Tab 1</Tab> <Tab>Tab 2</Tab> <Tab>Tab 3</Tab> </Tab.List> <Tab.Panels> <Tab.Panel>Content 1</Tab.Panel> <Tab.Panel>Content 2</Tab.Panel> <Tab.Panel>Content 3</Tab.Panel> </Tab.Panels> </Tab.Group> ) }
The tabs component can also be used as a controlled component. To do this, provide the selectedIndex
and manage the state yourself.
import { useState } from 'react' import { Tab } from '@headlessui/react' function MyTabs() {
const [selectedIndex, setSelectedIndex] = useState(0)return (<Tab.Group selectedIndex={selectedIndex} onChange={setSelectedIndex}><Tab.List> <Tab>Tab 1</Tab> <Tab>Tab 2</Tab> <Tab>Tab 3</Tab> </Tab.List> <Tab.Panels> <Tab.Panel>Content 1</Tab.Panel> <Tab.Panel>Content 2</Tab.Panel> <Tab.Panel>Content 3</Tab.Panel> </Tab.Panels> </Tab.Group> ) }
Clicking a Tab
will select that tab and display the corresponding Tab.Panel
.
All interactions apply when a Tab
component is focused.
Command | Description |
ArrowLeft and ArrowRight | Selects the previous/next non-disabled tab. |
ArrowUp and ArrowDownwhen | Selects the previous/next non-disabled tab. |
Home or PageUp | Selects the first non-disabled tab. |
End or PageDown | Selects the last non-disabled tab. |
Enter or Space when | Activates the selected tab. |
All relevant ARIA attributes are automatically managed.
For a full reference on all accessibility features implemented in Tabs
, see the ARIA spec on Tabs.
The main Tab.Group component.
Prop | Default | Description |
as | Fragment | String | Component The element or component the |
defaultIndex | 0 | Number The default selected index |
selectedIndex | — | number The selected index if you want to use the Tabs component as a controlled component. |
onChange | — | (index: number) => void A function called whenever the active tab changes. |
vertical | false | Boolean When true, the orientation of the |
manual | false | Boolean When true, the user can only display a panel via the keyboard by first
navigating to it using the arrow keys, and then by pressing |
Render Prop | Description |
selectedIndex |
The currently selected index. |
Prop | Default | Description |
as | div | String | Component The element or component the |
Render Prop | Description |
selectedIndex |
The currently selected index. |
Prop | Default | Description |
as | button | String | Component The element or component the |
disabled | false | Boolean Whether or not the |
Render Prop | Description |
selected |
Whether or not the |
Prop | Default | Description |
as | div | String | Component The element or component the |
Render Prop | Description |
selectedIndex |
The currently selected index. |
Prop | Default | Description |
as | div | String | Component The element or component the |
static | false | Boolean Whether the element should ignore the selected index. Note: |
unmount | true | Boolean Whether the element should be unmounted or hidden based on the selected index. Note: |
Render Prop | Description |
selected |
Whether or not the |
If you're interested in predesigned component examples using Headless UI and Tailwind CSS, check out Tailwind UI — a collection of beautifully designed and expertly crafted components built by us.
It's a great way to support our work on open-source projects like this and makes it possible for us to improve them and keep them well-maintained.