Tab
Accessible Tab
component that follows the WAI-ARIA Tabs Pattern. It's a component that, when activated, displays a TabPanel
.
#Installation
npm install reakit
Learn more in Get started.
#Usage
import { useTabState, Tab, TabList, TabPanel } from "reakit/Tab"; function Example() { const tab = useTabState(); return ( <> <TabList {...tab} aria-label="My tabs"> <Tab {...tab}>Tab 1</Tab> <Tab {...tab} disabled> Tab 2 </Tab> <Tab {...tab}>Tab 3</Tab> </TabList> <TabPanel {...tab}>Tab 1</TabPanel> <TabPanel {...tab}>Tab 2</TabPanel> <TabPanel {...tab}>Tab 3</TabPanel> </> ); }
#Default selected tab
You can set the default selected tab by passing an id
to selectedId
on useTabState
.
import { useTabState, Tab, TabList, TabPanel } from "reakit/Tab"; function Example() { const tab = useTabState({ selectedId: "tab3" }); return ( <> <TabList {...tab} aria-label="My tabs"> <Tab {...tab}>Tab 1</Tab> <Tab {...tab} disabled> Tab 2 </Tab> <Tab {...tab} id="tab3"> Tab 3 </Tab> </TabList> <TabPanel {...tab}>Tab 1</TabPanel> <TabPanel {...tab}>Tab 2</TabPanel> <TabPanel {...tab}>Tab 3</TabPanel> </> ); }
Note that Tab
automatically generates an id
if you don't specify one. In this example, an id
is explicitly assigned for the third tab, so that the same value can be specified as the selectedId
. No id
is assigned for the other tabs. The automatically generated id
s are fine, since we don't need to reference them.
#No selected tab
By default, the first tab will be selected, but you can unset it by passing null
to selectedId
on useTabState
.
import { useTabState, Tab, TabList, TabPanel } from "reakit/Tab"; function Example() { const tab = useTabState({ selectedId: null }); return ( <> <TabList {...tab} aria-label="My tabs"> <Tab {...tab}>Tab 1</Tab> <Tab {...tab}>Tab 2</Tab> <Tab {...tab}>Tab 3</Tab> </TabList> <TabPanel {...tab}>Tab 1</TabPanel> <TabPanel {...tab}>Tab 2</TabPanel> <TabPanel {...tab}>Tab 3</TabPanel> </> ); }
#Manual activation
By default, a Tab
is selected when it gets focused, which reveals its corresponding TabPanel
. This behavior can be changed by setting manual
to true
on useTabState
.
import { useTabState, Tab, TabList, TabPanel } from "reakit/Tab"; function Example() { const tab = useTabState({ manual: true }); return ( <> <TabList {...tab} aria-label="My tabs"> <Tab {...tab}>Tab 1</Tab> <Tab {...tab}>Tab 2</Tab> <Tab {...tab}>Tab 3</Tab> </TabList> <TabPanel {...tab}>Tab 1</TabPanel> <TabPanel {...tab}>Tab 2</TabPanel> <TabPanel {...tab}>Tab 3</TabPanel> </> ); }
#Detecting the selected tab
On each render, selectedId
from useTabState
specifies which tab is currently selected. You can check it and alter your render accordingly. For example, since only the panel for the selected tab is visible, you can avoid rendering the contents of the other ones as an optimization.
import { useTabState, Tab, TabList, TabPanel } from "reakit/Tab"; function Example() { const tab = useTabState(); return ( <> <TabList {...tab} aria-label="My tabs"> <Tab {...tab} id="tab1"> Tab 1 </Tab> <Tab {...tab} id="tab2"> Tab 2 </Tab> <Tab {...tab} id="tab3"> Tab 3 </Tab> </TabList> <TabPanel {...tab}>{tab.selectedId === "tab1" && "Tab 1"}</TabPanel> <TabPanel {...tab}>{tab.selectedId === "tab2" && "Tab 2"}</TabPanel> <TabPanel {...tab}>{tab.selectedId === "tab3" && "Tab 3"}</TabPanel> </> ); }
#Non-tabbable tab panels
By default, TabPanel
s are tabbable. You can disable this by passing a tabIndex
prop to the TabPanel
: either -1
to make it not tababble but still focusable, or undefined
to make it neither tabbable nor focusable.
import { useTabState, Tab, TabList, TabPanel } from "reakit/Tab"; import { Button } from "reakit/Button"; function Example() { const tab = useTabState(); return ( <> <TabList {...tab} aria-label="My tabs"> <Tab {...tab}>Tab 1</Tab> <Tab {...tab}>Tab 2</Tab> <Tab {...tab}>Tab 3</Tab> </TabList> <TabPanel {...tab}>Tab 1</TabPanel> <TabPanel {...tab} tabIndex={undefined}> <Button>Button</Button> </TabPanel> <TabPanel {...tab}>Tab 3</TabPanel> </> ); }
You should only do this if the contents of the TabPanel
are tabbable.
#Vertical tabs
You can control the orientation of the tabs by setting orientation
on useTabState
. Since it composes from CompositeItem, explicitly defining an orientation will change how arrow key navigation works. If it's set to vertical
, only ↑ and ↓ will work.
import { useTabState, Tab, TabList, TabPanel } from "reakit/Tab"; function Example() { const tab = useTabState({ orientation: "vertical" }); return ( <div style={{ display: "flex" }}> <TabList {...tab} aria-label="My tabs"> <Tab {...tab}>Tab 1</Tab> <Tab {...tab}>Tab 2</Tab> <Tab {...tab}>Tab 3</Tab> </TabList> <TabPanel {...tab}>Tab 1</TabPanel> <TabPanel {...tab}>Tab 2</TabPanel> <TabPanel {...tab}>Tab 3</TabPanel> </div> ); }
#Abstracting
Like all other Reakit components, you can leverage the low level API to create your own customized API and make it less verbose, for example, by using React Context underneath.
import React from "react"; import { useTabState, Tab as BaseTab, TabList as BaseTabList, TabPanel as BaseTabPanel, } from "reakit/Tab"; const TabsContext = React.createContext(); function Tabs({ children, ...initialState }) { const tab = useTabState(initialState); const value = React.useMemo(() => tab, Object.values(tab)); return <TabsContext.Provider value={value}>{children}</TabsContext.Provider>; } function Tab(props) { const tab = React.useContext(TabsContext); return <BaseTab {...tab} {...props} />; } function TabList(props) { const tab = React.useContext(TabsContext); return <BaseTabList {...tab} {...props} />; } function TabPanel(props) { const tab = React.useContext(TabsContext); return <BaseTabPanel {...tab} {...props} />; } function Example() { return ( <Tabs selectedId="tab3"> <TabList aria-label="My tabs"> <Tab>Tab 1</Tab> <Tab>Tab 2</Tab> <Tab id="tab3">Tab 3</Tab> </TabList> <TabPanel>Tab 1</TabPanel> <TabPanel>Tab 2</TabPanel> <TabPanel>Tab 3</TabPanel> </Tabs> ); }
#Accessibility
Tab
has roletab
.Tab
hasaria-controls
referring to its associatedTabPanel
.- The selected
Tab
hasaria-selected
set totrue
and all otherTab
s have it set tofalse
. Tab
extends the accessibility features of CompositeItem.TabList
has roletablist
.TabList
hasaria-orientation
set tovertical
orhorizontal
based on the value of theorientation
option.TabList
extends the accessibility features of Composite.TabPanel
has roletabpanel
.TabPanel
hasaria-labelledby
referring to its associatedTab
.TabPanel
extends the accessibility features of DisclosureContent.
Learn more in Accessibility.
#Composition
Tab
uses CompositeItem.TabList
uses Composite.TabPanel
uses DisclosureContent.
Learn more in Composition.
#Props
#useTabState
-
baseId
string
ID that will serve as a base for all the items IDs.
-
unstable_virtual
boolean
If enabled, the composite element will act as an aria-activedescendant container instead of roving tabindex. DOM focus will remain on the composite while its items receive virtual focus.
-
rtl
boolean
Determines how
next
andprevious
functions will behave. Ifrtl
is set totrue
, they will be inverted. This only affects the composite widget behavior. You still need to setdir="rtl"
on HTML/CSS. -
orientation
"horizontal" | "vertical" | undefined
Defines the orientation of the composite widget. If the composite has a single row or column (one-dimensional), the
orientation
value determines which arrow keys can be used to move focus:undefined
: all arrow keys work.horizontal
: only left and right arrow keys work.vertical
: only up and down arrow keys work.
It doesn't have any effect on two-dimensional composites.
-
currentId
string | null | undefined
The current focused item
id
.undefined
will automatically focus the first enabled composite item.null
will focus the base composite element and users will be able to navigate out of it using arrow keys.- If
currentId
is initially set tonull
, the base composite element itself will have focus and users will be able to navigate to it using arrow keys.
-
loop
boolean | "horizontal" | "vertical"
On one-dimensional composites:
true
loops from the last item to the first item and vice-versa.horizontal
loops only iforientation
ishorizontal
or not set.vertical
loops only iforientation
isvertical
or not set.- If
currentId
is initially set tonull
, the composite element will be focused in between the last and first items.
On two-dimensional composites:
true
loops from the last row/column item to the first item in the same row/column and vice-versa. If it's the last item in the last row, it moves to the first item in the first row and vice-versa.horizontal
loops only from the last row item to the first item in the same row.vertical
loops only from the last column item to the first item in the column row.- If
currentId
is initially set tonull
, vertical loop will have no effect as moving down from the last row or up from the first row will focus the composite element. - If
wrap
matches the value ofloop
, it'll wrap between the last item in the last row or column and the first item in the first row or column and vice-versa.
-
wrap
boolean | "horizontal" | "vertical"
Has effect only on two-dimensional composites. If enabled, moving to the next item from the last one in a row or column will focus the first item in the next row or column and vice-versa.
true
wraps between rows and columns.horizontal
wraps only between rows.vertical
wraps only between columns.- If
loop
matches the value ofwrap
, it'll wrap between the last item in the last row or column and the first item in the first row or column and vice-versa.
-
shift
boolean
Has effect only on two-dimensional composites. If enabled, moving up or down when there's no next item or the next item is disabled will shift to the item right before it.
-
selectedId
string | null | undefined
The current selected tab's
id
. -
manual
boolean
Whether the tab selection should be manual.
#Tab
-
disabled
boolean | undefined
Same as the HTML attribute.
-
focusable
boolean | undefined
When an element is
disabled
, it may still befocusable
. It works similarly toreadOnly
on form elements. In this case, onlyaria-disabled
will be set. -
id
string | undefined
Same as the HTML attribute.
19 state props
These props are returned by the state hook. You can spread them into this component (
{...state}
) or pass them separately. You can also provide these props from your own state logic.
-
baseId
string
ID that will serve as a base for all the items IDs.
-
unstable_virtual
boolean
If enabled, the composite element will act as an aria-activedescendant container instead of roving tabindex. DOM focus will remain on the composite while its items receive virtual focus.
-
orientation
"horizontal" | "vertical" | undefined
Defines the orientation of the composite widget. If the composite has a single row or column (one-dimensional), the
orientation
value determines which arrow keys can be used to move focus:undefined
: all arrow keys work.horizontal
: only left and right arrow keys work.vertical
: only up and down arrow keys work.
It doesn't have any effect on two-dimensional composites.
-
unstable_moves
number
Stores the number of moves that have been performed by calling
move
,next
,previous
,up
,down
,first
orlast
. -
currentId
string | null | undefined
The current focused item
id
.undefined
will automatically focus the first enabled composite item.null
will focus the base composite element and users will be able to navigate out of it using arrow keys.- If
currentId
is initially set tonull
, the base composite element itself will have focus and users will be able to navigate to it using arrow keys.
-
items
Item[]
Lists all the composite items with their
id
, DOMref
,disabled
state andgroupId
if any. This state is automatically updated whenregisterItem
andunregisterItem
are called. -
setCurrentId
(value: SetStateAction<string | null | undefine...
Sets
currentId
. This is different fromcomposite.move
as this only updates thecurrentId
state without moving focus. When the composite widget gets focused by the user, the item referred by thecurrentId
state will get focus. -
registerItem
(item: Item) => void
Registers a composite item.
-
unregisterItem
(id: string) => void
Unregisters a composite item.
-
next
(unstable_allTheWay?: boolean | undefined) => void
Moves focus to the next item.
-
previous
(unstable_allTheWay?: boolean | undefined) => void
Moves focus to the previous item.
-
up
(unstable_allTheWay?: boolean | undefined) => void
Moves focus to the item above.
-
down
(unstable_allTheWay?: boolean | undefined) => void
Moves focus to the item below.
-
first
() => void
Moves focus to the first item.
-
last
() => void
Moves focus to the last item.
-
manual
boolean
Whether the tab selection should be manual.
-
selectedId
string | null | undefined
The current selected tab's
id
. -
panels
Item[]
Lists all the panels.
-
select
(id: string | null) => void
Moves into and selects a tab by its
id
.
#TabList
-
disabled
boolean | undefined
Same as the HTML attribute.
-
focusable
boolean | undefined
When an element is
disabled
, it may still befocusable
. It works similarly toreadOnly
on form elements. In this case, onlyaria-disabled
will be set.
12 state props
These props are returned by the state hook. You can spread them into this component (
{...state}
) or pass them separately. You can also provide these props from your own state logic.
-
baseId
string
ID that will serve as a base for all the items IDs.
-
unstable_virtual
boolean
If enabled, the composite element will act as an aria-activedescendant container instead of roving tabindex. DOM focus will remain on the composite while its items receive virtual focus.
-
orientation
"horizontal" | "vertical" | undefined
Defines the orientation of the composite widget. If the composite has a single row or column (one-dimensional), the
orientation
value determines which arrow keys can be used to move focus:undefined
: all arrow keys work.horizontal
: only left and right arrow keys work.vertical
: only up and down arrow keys work.
It doesn't have any effect on two-dimensional composites.
-
currentId
string | null | undefined
The current focused item
id
.undefined
will automatically focus the first enabled composite item.null
will focus the base composite element and users will be able to navigate out of it using arrow keys.- If
currentId
is initially set tonull
, the base composite element itself will have focus and users will be able to navigate to it using arrow keys.
-
wrap
boolean | "horizontal" | "vertical"
Has effect only on two-dimensional composites. If enabled, moving to the next item from the last one in a row or column will focus the first item in the next row or column and vice-versa.
true
wraps between rows and columns.horizontal
wraps only between rows.vertical
wraps only between columns.- If
loop
matches the value ofwrap
, it'll wrap between the last item in the last row or column and the first item in the first row or column and vice-versa.
-
unstable_moves
number
Stores the number of moves that have been performed by calling
move
,next
,previous
,up
,down
,first
orlast
. -
groups
Group[]
Lists all the composite groups with their
id
and DOMref
. This state is automatically updated whenregisterGroup
andunregisterGroup
are called. -
items
Item[]
Lists all the composite items with their
id
, DOMref
,disabled
state andgroupId
if any. This state is automatically updated whenregisterItem
andunregisterItem
are called. -
move
(id: string | null) => void
Moves focus to a given item ID.
-
setCurrentId
(value: SetStateAction<string | null | undefine...
Sets
currentId
. This is different fromcomposite.move
as this only updates thecurrentId
state without moving focus. When the composite widget gets focused by the user, the item referred by thecurrentId
state will get focus. -
first
() => void
Moves focus to the first item.
-
last
() => void
Moves focus to the last item.
#TabPanel
-
id
string | undefined
Same as the HTML attribute.
-
tabId
string | undefined
Tab's id
10 state props
These props are returned by the state hook. You can spread them into this component (
{...state}
) or pass them separately. You can also provide these props from your own state logic.
-
baseId
string
ID that will serve as a base for all the items IDs.
-
visible
boolean
Whether it's visible or not.
-
animating
boolean
Whether it's animating or not.
-
animated
number | boolean
If
true
,animating
will be set totrue
whenvisible
is updated. It'll wait forstopAnimation
to be called or a CSS transition ends. Ifanimated
is set to anumber
,stopAnimation
will be called only after the same number of milliseconds have passed. -
stopAnimation
() => void
Stops animation. It's called automatically if there's a CSS transition.
-
selectedId
string | null | undefined
The current selected tab's
id
. -
items
Item[]
Lists all the composite items with their
id
, DOMref
,disabled
state andgroupId
if any. This state is automatically updated whenregisterItem
andunregisterItem
are called. -
panels
Item[]
Lists all the panels.
-
registerPanel
(item: Item) => void
Registers a tab panel.
-
unregisterPanel
(id: string) => void
Unregisters a tab panel.