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 ids 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, TabPanels 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
Tabhas roletab.Tabhasaria-controlsreferring to its associatedTabPanel.- The selected
Tabhasaria-selectedset totrueand all otherTabs have it set tofalse. Tabextends the accessibility features of CompositeItem.TabListhas roletablist.TabListhasaria-orientationset toverticalorhorizontalbased on the value of theorientationoption.TabListextends the accessibility features of Composite.TabPanelhas roletabpanel.TabPanelhasaria-labelledbyreferring to its associatedTab.TabPanelextends the accessibility features of DisclosureContent.
Learn more in Accessibility.
#Composition
Tabuses CompositeItem.TabListuses Composite.TabPaneluses DisclosureContent.
Learn more in Composition.
#Props
#useTabState
-
baseIdstringID that will serve as a base for all the items IDs.
-
unstable_virtualbooleanIf 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.
-
rtlbooleanDetermines how
nextandpreviousfunctions will behave. Ifrtlis 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" | undefinedDefines the orientation of the composite widget. If the composite has a single row or column (one-dimensional), the
orientationvalue 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.
-
currentIdstring | null | undefinedThe current focused item
id.undefinedwill automatically focus the first enabled composite item.nullwill focus the base composite element and users will be able to navigate out of it using arrow keys.- If
currentIdis initially set tonull, the base composite element itself will have focus and users will be able to navigate to it using arrow keys.
-
loopboolean | "horizontal" | "vertical"On one-dimensional composites:
trueloops from the last item to the first item and vice-versa.horizontalloops only iforientationishorizontalor not set.verticalloops only iforientationisverticalor not set.- If
currentIdis initially set tonull, the composite element will be focused in between the last and first items.
On two-dimensional composites:
trueloops 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.horizontalloops only from the last row item to the first item in the same row.verticalloops only from the last column item to the first item in the column row.- If
currentIdis 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
wrapmatches 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.
-
wrapboolean | "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.
truewraps between rows and columns.horizontalwraps only between rows.verticalwraps only between columns.- If
loopmatches 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.
-
shiftbooleanHas 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.
-
selectedIdstring | null | undefinedThe current selected tab's
id. -
manualbooleanWhether the tab selection should be manual.
#Tab
-
disabledboolean | undefinedSame as the HTML attribute.
-
focusableboolean | undefinedWhen an element is
disabled, it may still befocusable. It works similarly toreadOnlyon form elements. In this case, onlyaria-disabledwill be set. -
idstring | undefinedSame 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.
-
baseIdstringID that will serve as a base for all the items IDs.
-
unstable_virtualbooleanIf 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" | undefinedDefines the orientation of the composite widget. If the composite has a single row or column (one-dimensional), the
orientationvalue 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_movesnumberStores the number of moves that have been performed by calling
move,next,previous,up,down,firstorlast. -
currentIdstring | null | undefinedThe current focused item
id.undefinedwill automatically focus the first enabled composite item.nullwill focus the base composite element and users will be able to navigate out of it using arrow keys.- If
currentIdis initially set tonull, the base composite element itself will have focus and users will be able to navigate to it using arrow keys.
-
itemsItem[]Lists all the composite items with their
id, DOMref,disabledstate andgroupIdif any. This state is automatically updated whenregisterItemandunregisterItemare called. -
setCurrentId(value: SetStateAction<string | null | undefine...Sets
currentId. This is different fromcomposite.moveas this only updates thecurrentIdstate without moving focus. When the composite widget gets focused by the user, the item referred by thecurrentIdstate will get focus. -
registerItem(item: Item) => voidRegisters a composite item.
-
unregisterItem(id: string) => voidUnregisters a composite item.
-
next(unstable_allTheWay?: boolean | undefined) => voidMoves focus to the next item.
-
previous(unstable_allTheWay?: boolean | undefined) => voidMoves focus to the previous item.
-
up(unstable_allTheWay?: boolean | undefined) => voidMoves focus to the item above.
-
down(unstable_allTheWay?: boolean | undefined) => voidMoves focus to the item below.
-
first() => voidMoves focus to the first item.
-
last() => voidMoves focus to the last item.
-
manualbooleanWhether the tab selection should be manual.
-
selectedIdstring | null | undefinedThe current selected tab's
id. -
panelsItem[]Lists all the panels.
-
select(id: string | null) => voidMoves into and selects a tab by its
id.
#TabList
-
disabledboolean | undefinedSame as the HTML attribute.
-
focusableboolean | undefinedWhen an element is
disabled, it may still befocusable. It works similarly toreadOnlyon form elements. In this case, onlyaria-disabledwill 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.
-
baseIdstringID that will serve as a base for all the items IDs.
-
unstable_virtualbooleanIf 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" | undefinedDefines the orientation of the composite widget. If the composite has a single row or column (one-dimensional), the
orientationvalue 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.
-
currentIdstring | null | undefinedThe current focused item
id.undefinedwill automatically focus the first enabled composite item.nullwill focus the base composite element and users will be able to navigate out of it using arrow keys.- If
currentIdis initially set tonull, the base composite element itself will have focus and users will be able to navigate to it using arrow keys.
-
wrapboolean | "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.
truewraps between rows and columns.horizontalwraps only between rows.verticalwraps only between columns.- If
loopmatches 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_movesnumberStores the number of moves that have been performed by calling
move,next,previous,up,down,firstorlast. -
groupsGroup[]Lists all the composite groups with their
idand DOMref. This state is automatically updated whenregisterGroupandunregisterGroupare called. -
itemsItem[]Lists all the composite items with their
id, DOMref,disabledstate andgroupIdif any. This state is automatically updated whenregisterItemandunregisterItemare called. -
move(id: string | null) => voidMoves focus to a given item ID.
-
setCurrentId(value: SetStateAction<string | null | undefine...Sets
currentId. This is different fromcomposite.moveas this only updates thecurrentIdstate without moving focus. When the composite widget gets focused by the user, the item referred by thecurrentIdstate will get focus. -
first() => voidMoves focus to the first item.
-
last() => voidMoves focus to the last item.
#TabPanel
-
idstring | undefinedSame as the HTML attribute.
-
tabIdstring | undefinedTab'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.
-
baseIdstringID that will serve as a base for all the items IDs.
-
visiblebooleanWhether it's visible or not.
-
animatingbooleanWhether it's animating or not.
-
animatednumber | booleanIf
true,animatingwill be set totruewhenvisibleis updated. It'll wait forstopAnimationto be called or a CSS transition ends. Ifanimatedis set to anumber,stopAnimationwill be called only after the same number of milliseconds have passed. -
stopAnimation() => voidStops animation. It's called automatically if there's a CSS transition.
-
selectedIdstring | null | undefinedThe current selected tab's
id. -
itemsItem[]Lists all the composite items with their
id, DOMref,disabledstate andgroupIdif any. This state is automatically updated whenregisterItemandunregisterItemare called. -
panelsItem[]Lists all the panels.
-
registerPanel(item: Item) => voidRegisters a tab panel.
-
unregisterPanel(id: string) => voidUnregisters a tab panel.