useTabList
Provides the behavior and accessibility implementation for a tab list. Tabs organize content into multiple sections and allow users to navigate between them.
install | yarn add @react-aria/tabs |
---|---|
version | 3.1.4 |
usage | import {useTabList, useTab, useTabPanel} from '@react-aria/tabs' |
API#
useTabList<T>(
props: AriaTabListProps<T>,
state: TabListState<T>,
ref: RefObject<HTMLElement>
): TabListAria
useTab<T>(
props: AriaTabProps,
state: TabListState<T>,
ref: RefObject<HTMLElement>
): TabAria
useTabPanel<T>(
props: AriaTabPanelProps,
state: TabListState<T>,
ref: RefObject<HTMLElement>
): TabPanelAria
Features#
Tabs provide a list of tabs that a user can select from to switch between multiple tab panels. useTabList
, useTab
, and useTabPanel
can be used to implement these in an accessible way.
- Support for mouse, touch, and keyboard interactions on tabs
- Support for LTR and RTL keyboard navigation
- Support for disabled tabs
- Follows the tabs ARIA pattern, semantically linking tabs and their associated tab panels
- Focus management for tab panels without any focusable children
Anatomy#
Tabs consist of a tab list with one or more visually separated tabs. Each tab has associated content, and only the selected tab's content is shown.
Each tab can be clicked, tapped, or navigated to via arrow keys. Depending on the keyboardActivation
prop, the tab can be selected by receiving keyboard focus, or it can be selected with the Enter key.
useTabList
returns props to spread onto the tab list container:
Name | Type | Description |
tabListProps | HTMLAttributes<HTMLElement> | Props for the tablist container. |
useTab
returns props to be spread onto each individual tab:
Name | Type | Description |
tabProps | HTMLAttributes<HTMLElement> | Props for the tab element. |
useTabPanel
returns props to spread onto the container for the tab content:
Name | Type | Description |
tabPanelProps | HTMLAttributes<HTMLElement> | Props for the tab panel element. |
State is managed by the useTabListState
hook in @react-stately/tabs
. The state object should be passed as an option to useTabList
, useTab
,
and useTabPanel
.
Example#
This example displays a basic list of tabs. The currently selected tab receives a tabIndex
of 0 while the rest are set to -1 ensuring that the whole tablist is a single tab stop. The selected tab has a different style so it's obvious which one is currently selected. useTab
and useTabPanel
handle associating the tabs and tab panels for assistive technology. The currently selected tab panel is rendered below the list of tabs. The key
prop on the TabPanel
element is important to ensure that DOM state (e.g. text field contents) is not shared between unrelated tabs.
import {Item} from '@react-stately/collections';
import {useTab, useTabList, useTabPanel} from '@react-aria/tabs';
import {useTabListState} from '@react-stately/tabs';
function Tabs(props) {
let state = useTabListState(props);
let ref = React.useRef();
let { tabListProps } = useTabList(props, state, ref);
return (
<div style={{ height: '150px' }}>
<div
{...tabListProps}
ref={ref}
style={{ display: 'flex', borderBottom: '1px solid grey' }}
>
{[...state.collection].map((item) => (
<Tab key={item.key} item={item} state={state} />
))}
</div>
<TabPanel key={state.selectedItem?.key} state={state} />
</div>
);
}
function Tab({ item, state }) {
let { key, rendered } = item;
let ref = React.useRef();
let { tabProps } = useTab({ key }, state, ref);
let isSelected = state.selectedKey === key;
let isDisabled = state.disabledKeys.has(key);
return (
<div
{...tabProps}
ref={ref}
style={{
padding: '10px',
borderBottom: isSelected ? '3px solid var(--blue)' : undefined,
opacity: isDisabled ? '0.5' : undefined
}}
>
{rendered}
</div>
);
}
function TabPanel({ state, ...props }) {
let ref = React.useRef();
let { tabPanelProps } = useTabPanel(props, state, ref);
return (
<div {...tabPanelProps} ref={ref} style={{ padding: '10px' }}>
{state.selectedItem?.props.children}
</div>
);
}
<Tabs aria-label="History of Ancient Rome" disabledKeys={['Emp']}>
<Item key="FoR" title="Founding of Rome">
Arma virumque cano, Troiae qui primus ab oris.
</Item>
<Item key="MaR" title="Monarchy and Republic">
Senatus Populusque Romanus.
</Item>
<Item key="Emp" title="Empire">Alea jacta est.</Item>
</Tabs>
import {Item} from '@react-stately/collections';
import {
useTab,
useTabList,
useTabPanel
} from '@react-aria/tabs';
import {useTabListState} from '@react-stately/tabs';
function Tabs(props) {
let state = useTabListState(props);
let ref = React.useRef();
let { tabListProps } = useTabList(props, state, ref);
return (
<div style={{ height: '150px' }}>
<div
{...tabListProps}
ref={ref}
style={{
display: 'flex',
borderBottom: '1px solid grey'
}}
>
{[...state.collection].map((item) => (
<Tab key={item.key} item={item} state={state} />
))}
</div>
<TabPanel
key={state.selectedItem?.key}
state={state}
/>
</div>
);
}
function Tab({ item, state }) {
let { key, rendered } = item;
let ref = React.useRef();
let { tabProps } = useTab({ key }, state, ref);
let isSelected = state.selectedKey === key;
let isDisabled = state.disabledKeys.has(key);
return (
<div
{...tabProps}
ref={ref}
style={{
padding: '10px',
borderBottom: isSelected
? '3px solid var(--blue)'
: undefined,
opacity: isDisabled ? '0.5' : undefined
}}
>
{rendered}
</div>
);
}
function TabPanel({ state, ...props }) {
let ref = React.useRef();
let { tabPanelProps } = useTabPanel(props, state, ref);
return (
<div
{...tabPanelProps}
ref={ref}
style={{ padding: '10px' }}
>
{state.selectedItem?.props.children}
</div>
);
}
<Tabs
aria-label="History of Ancient Rome"
disabledKeys={['Emp']}
>
<Item key="FoR" title="Founding of Rome">
Arma virumque cano, Troiae qui primus ab oris.
</Item>
<Item key="MaR" title="Monarchy and Republic">
Senatus Populusque Romanus.
</Item>
<Item key="Emp" title="Empire">Alea jacta est.</Item>
</Tabs>
import {Item} from '@react-stately/collections';
import {
useTab,
useTabList,
useTabPanel
} from '@react-aria/tabs';
import {useTabListState} from '@react-stately/tabs';
function Tabs(props) {
let state =
useTabListState(
props
);
let ref = React
.useRef();
let { tabListProps } =
useTabList(
props,
state,
ref
);
return (
<div
style={{
height: '150px'
}}
>
<div
{...tabListProps}
ref={ref}
style={{
display:
'flex',
borderBottom:
'1px solid grey'
}}
>
{[
...state
.collection
].map((item) => (
<Tab
key={item
.key}
item={item}
state={state}
/>
))}
</div>
<TabPanel
key={state
.selectedItem
?.key}
state={state}
/>
</div>
);
}
function Tab(
{ item, state }
) {
let { key, rendered } =
item;
let ref = React
.useRef();
let { tabProps } =
useTab(
{ key },
state,
ref
);
let isSelected =
state.selectedKey ===
key;
let isDisabled = state
.disabledKeys.has(
key
);
return (
<div
{...tabProps}
ref={ref}
style={{
padding: '10px',
borderBottom:
isSelected
? '3px solid var(--blue)'
: undefined,
opacity:
isDisabled
? '0.5'
: undefined
}}
>
{rendered}
</div>
);
}
function TabPanel(
{ state, ...props }
) {
let ref = React
.useRef();
let { tabPanelProps } =
useTabPanel(
props,
state,
ref
);
return (
<div
{...tabPanelProps}
ref={ref}
style={{
padding: '10px'
}}
>
{state.selectedItem
?.props.children}
</div>
);
}
<Tabs
aria-label="History of Ancient Rome"
disabledKeys={[
'Emp'
]}
>
<Item
key="FoR"
title="Founding of Rome"
>
Arma virumque cano,
Troiae qui primus
ab oris.
</Item>
<Item
key="MaR"
title="Monarchy and Republic"
>
Senatus Populusque
Romanus.
</Item>
<Item
key="Emp"
title="Empire"
>
Alea jacta est.
</Item>
</Tabs>
With focusable content#
When the tab panel doesn't contain any focusable content, the entire panel is given a tabIndex=0
so that the content can be navigated to with the keyboard. When the tab panel contains focusable content, such as a textfield, then the tabIndex
is omitted because the content itself can receive focus.
This example uses the same Tabs
component from above. Try navigating from the tabs to the content for each panel using the keyboard.
<Tabs aria-label="Notes app">
<Item key="item1" title="Jane Doe">
<label>Leave a note for Jane: <input type="text" /></label>
</Item>
<Item key="item2" title="John Doe">Senatus Populusque Romanus.</Item>
<Item key="item3" title="Joe Bloggs">Alea jacta est.</Item>
</Tabs>
<Tabs aria-label="Notes app">
<Item key="item1" title="Jane Doe">
<label>
Leave a note for Jane: <input type="text" />
</label>
</Item>
<Item key="item2" title="John Doe">
Senatus Populusque Romanus.
</Item>
<Item key="item3" title="Joe Bloggs">
Alea jacta est.
</Item>
</Tabs>
<Tabs aria-label="Notes app">
<Item
key="item1"
title="Jane Doe"
>
<label>
Leave a note for
Jane:{' '}
<input type="text" />
</label>
</Item>
<Item
key="item2"
title="John Doe"
>
Senatus Populusque
Romanus.
</Item>
<Item
key="item3"
title="Joe Bloggs"
>
Alea jacta est.
</Item>
</Tabs>
Internationalization#
useTabList
handles some aspects of internationalization automatically. For example, keyboard navigation is automatically mirrored for right-to-left languages. You are responsible for localizing all tab labels and content.
RTL#
In right-to-left languages, the tablist should be mirrored. The first tab is furthest right and the last tab is furthest left. Ensure that your CSS accounts for this.