Tabs
Tabs organize content into multiple sections and allow users to navigate between them. The content under the set of tabs should be related and form a coherent unit.
install | yarn add @adobe/react-spectrum |
---|---|
added | 3.11.0 |
usage | import {Item, TabList, TabPanels, Tabs} from '@adobe/react-spectrum' |
Example#
<Tabs aria-label="History of Ancient Rome">
<TabList>
<Item key="FoR">Founding of Rome</Item>
<Item key="MaR">Monarchy and Republic</Item>
<Item key="Emp">Empire</Item>
</TabList>
<TabPanels>
<Item key="FoR">
Arma virumque cano, Troiae qui primus ab oris.
</Item>
<Item key="MaR">
Senatus Populusque Romanus.
</Item>
<Item key="Emp">
Alea jacta est.
</Item>
</TabPanels>
</Tabs>
<Tabs aria-label="History of Ancient Rome">
<TabList>
<Item key="FoR">Founding of Rome</Item>
<Item key="MaR">Monarchy and Republic</Item>
<Item key="Emp">Empire</Item>
</TabList>
<TabPanels>
<Item key="FoR">
Arma virumque cano, Troiae qui primus ab oris.
</Item>
<Item key="MaR">
Senatus Populusque Romanus.
</Item>
<Item key="Emp">
Alea jacta est.
</Item>
</TabPanels>
</Tabs>
<Tabs aria-label="History of Ancient Rome">
<TabList>
<Item key="FoR">
Founding of Rome
</Item>
<Item key="MaR">
Monarchy and
Republic
</Item>
<Item key="Emp">
Empire
</Item>
</TabList>
<TabPanels>
<Item key="FoR">
Arma virumque
cano, Troiae qui
primus ab oris.
</Item>
<Item key="MaR">
Senatus
Populusque
Romanus.
</Item>
<Item key="Emp">
Alea jacta est.
</Item>
</TabPanels>
</Tabs>
Content#
Tabs expects <TabList>
and <TabPanels>
elements as children, however additional elements may wrap them to allow for layout flexibility. TabList and TabPanels follow the Collection Components API, accepting both static and dynamic collections. TabList and TabPanel accept <Item>
elements as children,
each with a key
prop. The key passed to the TabList <Item>
must match its corresponding TabPanel <Item>
.
Static collections, as in the previous example, can be used when the full list of tabs and their contents is known ahead of time.
Dynamic collections, as shown below, can be used when the options come from an external data source such as an API call, or update over time. As seen below, an iterable list of options is passed to the Tabs using the items
prop.
Each item accepts a key prop, which is passed to the onSelectionChange
handler to identify the selected item. Alternatively, if the item objects contain an id
property, as shown in the example below, then this is used automatically and a key
prop is not required.
See the Events section for more detail on selection.
import type {Key} from '@adobe/react-spectrum';
function Example() {
let tabs = [
{
id: 1,
name: 'Founding of Rome',
children: 'Arma virumque cano, Troiae qui primus ab oris.'
},
{
id: 2,
name: 'Monarchy and Republic',
children: 'Senatus Populusque Romanus.'
},
{ id: 3, name: 'Empire', children: 'Alea jacta est.' }
];
type Tab = typeof tabs[0];
let [tabId, setTabId] = React.useState<Key>(1);
return (
<>
<p>Current tab id: {tabId}</p>
<Tabs
aria-label="History of Ancient Rome"
items={tabs}
onSelectionChange={setTabId}
>
<TabList>
{(item: Tab) => (
<Item>
{item.name}
</Item>
)}
</TabList>
<TabPanels>
{(item: Tab) => (
<Item>
{item.children}
</Item>
)}
</TabPanels>
</Tabs>
</>
);
}
import type {Key} from '@adobe/react-spectrum';
function Example() {
let tabs = [
{
id: 1,
name: 'Founding of Rome',
children:
'Arma virumque cano, Troiae qui primus ab oris.'
},
{
id: 2,
name: 'Monarchy and Republic',
children: 'Senatus Populusque Romanus.'
},
{ id: 3, name: 'Empire', children: 'Alea jacta est.' }
];
type Tab = typeof tabs[0];
let [tabId, setTabId] = React.useState<Key>(1);
return (
<>
<p>Current tab id: {tabId}</p>
<Tabs
aria-label="History of Ancient Rome"
items={tabs}
onSelectionChange={setTabId}
>
<TabList>
{(item: Tab) => (
<Item>
{item.name}
</Item>
)}
</TabList>
<TabPanels>
{(item: Tab) => (
<Item>
{item.children}
</Item>
)}
</TabPanels>
</Tabs>
</>
);
}
import type {Key} from '@adobe/react-spectrum';
function Example() {
let tabs = [
{
id: 1,
name:
'Founding of Rome',
children:
'Arma virumque cano, Troiae qui primus ab oris.'
},
{
id: 2,
name:
'Monarchy and Republic',
children:
'Senatus Populusque Romanus.'
},
{
id: 3,
name: 'Empire',
children:
'Alea jacta est.'
}
];
type Tab =
typeof tabs[0];
let [tabId, setTabId] =
React.useState<Key>(
1
);
return (
<>
<p>
Current tab id:
{' '}
{tabId}
</p>
<Tabs
aria-label="History of Ancient Rome"
items={tabs}
onSelectionChange={setTabId}
>
<TabList>
{(
item: Tab
) => (
<Item>
{item.name}
</Item>
)}
</TabList>
<TabPanels>
{(
item: Tab
) => (
<Item>
{item
.children}
</Item>
)}
</TabPanels>
</Tabs>
</>
);
}
Internationalization#
To internationalize Tabs, a localized string should be passed as children to the TabList <Item>
. Any text content within the Tab's panel should also be localized accordingly.
For languages that are read right-to-left (e.g. Hebrew and Arabic), the layout of Tabs is automatically flipped.
Icons in tabs#
Icons can also be used in Tabs
in addition to a text label. Tabs use Slots in order to style Icons and Text together.
<Tabs aria-label="History of Ancient Rome">
<TabList>
<Item key="FoR" textValue="FoR">
<Bookmark />
<Text>Founding of Rome</Text>
</Item>
<Item key="MaR" textValue="MaR">
<Calendar />
<Text>Monarchy and Republic</Text>
</Item>
<Item key="Emp" textValue="Emp">
<Dashboard />
<Text>Empire</Text>
</Item>
</TabList>
<TabPanels>
<Item key="FoR">
Arma virumque cano, Troiae qui primus ab oris.
</Item>
<Item key="MaR">
Senatus Populusque Romanus.
</Item>
<Item key="Emp">
Alea jacta est.
</Item>
</TabPanels>
</Tabs>
<Tabs aria-label="History of Ancient Rome">
<TabList>
<Item key="FoR" textValue="FoR">
<Bookmark />
<Text>Founding of Rome</Text>
</Item>
<Item key="MaR" textValue="MaR">
<Calendar />
<Text>Monarchy and Republic</Text>
</Item>
<Item key="Emp" textValue="Emp">
<Dashboard />
<Text>Empire</Text>
</Item>
</TabList>
<TabPanels>
<Item key="FoR">
Arma virumque cano, Troiae qui primus ab oris.
</Item>
<Item key="MaR">
Senatus Populusque Romanus.
</Item>
<Item key="Emp">
Alea jacta est.
</Item>
</TabPanels>
</Tabs>
<Tabs aria-label="History of Ancient Rome">
<TabList>
<Item
key="FoR"
textValue="FoR"
>
<Bookmark />
<Text>
Founding of
Rome
</Text>
</Item>
<Item
key="MaR"
textValue="MaR"
>
<Calendar />
<Text>
Monarchy and
Republic
</Text>
</Item>
<Item
key="Emp"
textValue="Emp"
>
<Dashboard />
<Text>
Empire
</Text>
</Item>
</TabList>
<TabPanels>
<Item key="FoR">
Arma virumque
cano, Troiae qui
primus ab oris.
</Item>
<Item key="MaR">
Senatus
Populusque
Romanus.
</Item>
<Item key="Emp">
Alea jacta est.
</Item>
</TabPanels>
</Tabs>
Customizing layout#
If you need to add additional structure to Tabs
such as buttons in-line with the TabList
, we support this too.
TabList
and TabPanels
are not required to be the immediate children of Tabs
.
function Example() {
let [tabs, setTabs] = React.useState([
{ name: 'Tab 1', children: 'Tab Body 1' },
{ name: 'Tab 2', children: 'Tab Body 2' },
{ name: 'Tab 3', children: 'Tab Body 3' }
]);
type Tab = typeof tabs[0];
let addTab = () => {
let newTabs = [...tabs];
newTabs.push({
name: `Tab `,
children: `Tab Body
`});
setTabs(newTabs);
};
let removeTab = () => {
if (tabs.length > 1) {
let newTabs = [...tabs];
newTabs.pop();
setTabs(newTabs);
}
};
return (
<Tabs aria-label="Tab example" items={tabs}>
<Flex>
<TabList flex="1 1 auto" minWidth="0px">
{(item: Tab) => (
<Item key={item.name}>
{item.name}
</Item>
)}
</TabList>
<div
style={{
display: 'flex',
flex: '0 0 auto',
borderBottom:
'var(--spectrum-alias-border-size-thick) solid var(--spectrum-global-color-gray-300)'
}}
>
<ActionGroup
disabledKeys={tabs.length === 1 ? ['remove'] : undefined}
onAction={(val) => val === 'add' ? addTab() : removeTab()}
>
<Item key="add">
Add Tab
</Item>
<Item key="remove">
Remove Tab
</Item>
</ActionGroup>
</div>
</Flex>
<TabPanels>
{(item: Tab) => (
<Item key={item.name}>
{item.children}
</Item>
)}
</TabPanels>
</Tabs>
);
}
function Example() {
let [tabs, setTabs] = React.useState([
{ name: 'Tab 1', children: 'Tab Body 1' },
{ name: 'Tab 2', children: 'Tab Body 2' },
{ name: 'Tab 3', children: 'Tab Body 3' }
]);
type Tab = typeof tabs[0];
let addTab = () => {
let newTabs = [...tabs];
newTabs.push({
name: `Tab `,
children: `Tab Body
`});
setTabs(newTabs);
};
let removeTab = () => {
if (tabs.length > 1) {
let newTabs = [...tabs];
newTabs.pop();
setTabs(newTabs);
}
};
return (
<Tabs aria-label="Tab example" items={tabs}>
<Flex>
<TabList flex="1 1 auto" minWidth="0px">
{(item: Tab) => (
<Item key={item.name}>
{item.name}
</Item>
)}
</TabList>
<div
style={{
display: 'flex',
flex: '0 0 auto',
borderBottom:
'var(--spectrum-alias-border-size-thick) solid var(--spectrum-global-color-gray-300)'
}}
>
<ActionGroup
disabledKeys={tabs.length === 1
? ['remove']
: undefined}
onAction={(val) =>
val === 'add' ? addTab() : removeTab()}
>
<Item key="add">
Add Tab
</Item>
<Item key="remove">
Remove Tab
</Item>
</ActionGroup>
</div>
</Flex>
<TabPanels>
{(item: Tab) => (
<Item key={item.name}>
{item.children}
</Item>
)}
</TabPanels>
</Tabs>
);
}
function Example() {
let [tabs, setTabs] =
React.useState([
{
name: 'Tab 1',
children:
'Tab Body 1'
},
{
name: 'Tab 2',
children:
'Tab Body 2'
},
{
name: 'Tab 3',
children:
'Tab Body 3'
}
]);
type Tab =
typeof tabs[0];
let addTab = () => {
let newTabs = [
...tabs
];
newTabs.push({
name: `Tab `,
children:
`Tab Body
`});
setTabs(newTabs);
};
let removeTab = () => {
if (
tabs.length > 1
) {
let newTabs = [
...tabs
];
newTabs.pop();
setTabs(newTabs);
}
};
return (
<Tabs
aria-label="Tab example"
items={tabs}
>
<Flex>
<TabList
flex="1 1 auto"
minWidth="0px"
>
{(
item: Tab
) => (
<Item
key={item
.name}
>
{item.name}
</Item>
)}
</TabList>
<div
style={{
display:
'flex',
flex:
'0 0 auto',
borderBottom:
'var(--spectrum-alias-border-size-thick) solid var(--spectrum-global-color-gray-300)'
}}
>
<ActionGroup
disabledKeys={tabs
.length ===
1
? [
'remove'
]
: undefined}
onAction={(val) =>
val ===
'add'
? addTab()
: removeTab()}
>
<Item key="add">
Add Tab
</Item>
<Item key="remove">
Remove Tab
</Item>
</ActionGroup>
</div>
</Flex>
<TabPanels>
{(item: Tab) => (
<Item
key={item
.name}
>
{item
.children}
</Item>
)}
</TabPanels>
</Tabs>
);
}
Labeling#
Accessibility#
While an aria-label
is not explicitly required for a tab list, Tabs should be labeled using a aria-label
in the absence of an ancestor landmark.
This will prevent screen readers from announcing non-focused tabs, allowing for a more focused experience.
Selection#
Setting a selected tab can be done by using the defaultSelectedKey
or selectedKey
prop. The selected key corresponds to the key
of an item. See Events for more details on selection events.
Additionally, see the react-stately
Selection docs for caveats regarding selection prop typing.
function Example() {
let tabs = [
{ id: 1, name: 'Keyboard Settings', children: 'No keyboard detected.' },
{ id: 2, name: 'Mouse Settings', children: 'No mouse detected.' },
{ id: 3, name: 'Gamepad Settings', children: 'No gamepad detected' }
];
type Tab = typeof tabs[0];
let [tab, setTab] = React.useState<Key>(2);
return (
<Flex gap="size-150" wrap>
<span id="label-2">Settings (uncontrolled)</span>
<Tabs
aria-labelledby="label-2"
items={tabs}
defaultSelectedKey={2}
marginBottom="size-400"
>
<TabList>
{(item: Tab) => (
<Item>
{item.name}
</Item>
)}
</TabList>
<TabPanels>
{(item: Tab) => (
<Item>
{item.children}
</Item>
)}
</TabPanels>
</Tabs>
<span id="label-3">Settings (controlled)</span>
<Tabs
aria-labelledby="label-3"
items={tabs}
selectedKey={tab}
onSelectionChange={setTab}
>
<TabList>
{(item: Tab) => (
<Item>
{item.name}
</Item>
)}
</TabList>
<TabPanels>
{(item: Tab) => (
<Item>
{item.children}
</Item>
)}
</TabPanels>
</Tabs>
</Flex>
);
}
function Example() {
let tabs = [
{
id: 1,
name: 'Keyboard Settings',
children: 'No keyboard detected.'
},
{
id: 2,
name: 'Mouse Settings',
children: 'No mouse detected.'
},
{
id: 3,
name: 'Gamepad Settings',
children: 'No gamepad detected'
}
];
type Tab = typeof tabs[0];
let [tab, setTab] = React.useState<Key>(2);
return (
<Flex gap="size-150" wrap>
<span id="label-2">Settings (uncontrolled)</span>
<Tabs
aria-labelledby="label-2"
items={tabs}
defaultSelectedKey={2}
marginBottom="size-400"
>
<TabList>
{(item: Tab) => (
<Item>
{item.name}
</Item>
)}
</TabList>
<TabPanels>
{(item: Tab) => (
<Item>
{item.children}
</Item>
)}
</TabPanels>
</Tabs>
<span id="label-3">Settings (controlled)</span>
<Tabs
aria-labelledby="label-3"
items={tabs}
selectedKey={tab}
onSelectionChange={setTab}
>
<TabList>
{(item: Tab) => (
<Item>
{item.name}
</Item>
)}
</TabList>
<TabPanels>
{(item: Tab) => (
<Item>
{item.children}
</Item>
)}
</TabPanels>
</Tabs>
</Flex>
);
}
function Example() {
let tabs = [
{
id: 1,
name:
'Keyboard Settings',
children:
'No keyboard detected.'
},
{
id: 2,
name:
'Mouse Settings',
children:
'No mouse detected.'
},
{
id: 3,
name:
'Gamepad Settings',
children:
'No gamepad detected'
}
];
type Tab =
typeof tabs[0];
let [tab, setTab] =
React.useState<Key>(
2
);
return (
<Flex
gap="size-150"
wrap
>
<span id="label-2">
Settings
(uncontrolled)
</span>
<Tabs
aria-labelledby="label-2"
items={tabs}
defaultSelectedKey={2}
marginBottom="size-400"
>
<TabList>
{(
item: Tab
) => (
<Item>
{item.name}
</Item>
)}
</TabList>
<TabPanels>
{(
item: Tab
) => (
<Item>
{item
.children}
</Item>
)}
</TabPanels>
</Tabs>
<span id="label-3">
Settings
(controlled)
</span>
<Tabs
aria-labelledby="label-3"
items={tabs}
selectedKey={tab}
onSelectionChange={setTab}
>
<TabList>
{(
item: Tab
) => (
<Item>
{item.name}
</Item>
)}
</TabList>
<TabPanels>
{(
item: Tab
) => (
<Item>
{item
.children}
</Item>
)}
</TabPanels>
</Tabs>
</Flex>
);
}
Events#
Tabs supports selection via mouse, keyboard, and touch. You can handle all of these via the onSelectionChange
prop. Tabs will pass the selected key
to the onSelectionChange
handler.
The following example uses an onSelectionChange
handler to update the tab selection stored in React state.
function Example() {
let tabs = [
{
name: 'Triassic',
children:
'The Triassic ranges roughly from 252 million to 201 million years ago, preceding the Jurassic Period.'
},
{
name: 'Jurassic',
children:
'The Jurassic ranges from 200 million years to 145 million years ago.'
},
{
name: 'Cretaceous',
children:
'The Cretaceous is the longest period of the Mesozoic, spanning from 145 million to 66 million years ago.'
}
];
type Tab = typeof tabs[0];
let [timePeriod, setTimePeriod] = React.useState<Key>('Triassic');
return (
<>
<p>Selected time period: {timePeriod}</p>
<Tabs
aria-label="Mesozoic time periods"
items={tabs}
selectedKey={timePeriod}
onSelectionChange={setTimePeriod}
>
<TabList>
{(item: Tab) => (
<Item key={item.name}>
{item.name}
</Item>
)}
</TabList>
<TabPanels>
{(item: Tab) => (
<Item key={item.name}>
{item.children}
</Item>
)}
</TabPanels>
</Tabs>
</>
);
}
function Example() {
let tabs = [
{
name: 'Triassic',
children:
'The Triassic ranges roughly from 252 million to 201 million years ago, preceding the Jurassic Period.'
},
{
name: 'Jurassic',
children:
'The Jurassic ranges from 200 million years to 145 million years ago.'
},
{
name: 'Cretaceous',
children:
'The Cretaceous is the longest period of the Mesozoic, spanning from 145 million to 66 million years ago.'
}
];
type Tab = typeof tabs[0];
let [timePeriod, setTimePeriod] = React.useState<Key>(
'Triassic'
);
return (
<>
<p>Selected time period: {timePeriod}</p>
<Tabs
aria-label="Mesozoic time periods"
items={tabs}
selectedKey={timePeriod}
onSelectionChange={setTimePeriod}
>
<TabList>
{(item: Tab) => (
<Item key={item.name}>
{item.name}
</Item>
)}
</TabList>
<TabPanels>
{(item: Tab) => (
<Item key={item.name}>
{item.children}
</Item>
)}
</TabPanels>
</Tabs>
</>
);
}
function Example() {
let tabs = [
{
name: 'Triassic',
children:
'The Triassic ranges roughly from 252 million to 201 million years ago, preceding the Jurassic Period.'
},
{
name: 'Jurassic',
children:
'The Jurassic ranges from 200 million years to 145 million years ago.'
},
{
name: 'Cretaceous',
children:
'The Cretaceous is the longest period of the Mesozoic, spanning from 145 million to 66 million years ago.'
}
];
type Tab =
typeof tabs[0];
let [
timePeriod,
setTimePeriod
] = React.useState<
Key
>('Triassic');
return (
<>
<p>
Selected time
period:{' '}
{timePeriod}
</p>
<Tabs
aria-label="Mesozoic time periods"
items={tabs}
selectedKey={timePeriod}
onSelectionChange={setTimePeriod}
>
<TabList>
{(
item: Tab
) => (
<Item
key={item
.name}
>
{item.name}
</Item>
)}
</TabList>
<TabPanels>
{(
item: Tab
) => (
<Item
key={item
.name}
>
{item
.children}
</Item>
)}
</TabPanels>
</Tabs>
</>
);
}
Keyboard Activation#
By default, pressing the arrow keys while focus is on a Tab will switch selection to the adjacent Tab in that direction, updating the content displayed accordingly. If you would like to prevent selection change
from happening automatically you can set the keyboardActivation
prop to "manual". This will prevent tab selection from changing on arrow key press, requiring a subsequent Enter
or Space
key press to confirm
tab selection.
function Example() {
let tabs = [
{
name: 'Triassic',
children:
'The Triassic ranges roughly from 252 million to 201 million years ago, preceding the Jurassic Period.'
},
{
name: 'Jurassic',
children:
'The Jurassic ranges from 200 million years to 145 million years ago.'
},
{
name: 'Cretaceous',
children:
'The Cretaceous is the longest period of the Mesozoic, spanning from 145 million to 66 million years ago.'
}
];
type Tab = typeof tabs[0];
return (
<Tabs
aria-label="Mesozoic time periods"
items={tabs}
keyboardActivation="manual"
>
<TabList>
{(item: Tab) => (
<Item key={item.name}>
{item.name}
</Item>
)}
</TabList>
<TabPanels>
{(item: Tab) => (
<Item key={item.name}>
{item.children}
</Item>
)}
</TabPanels>
</Tabs>
);
}
function Example() {
let tabs = [
{
name: 'Triassic',
children:
'The Triassic ranges roughly from 252 million to 201 million years ago, preceding the Jurassic Period.'
},
{
name: 'Jurassic',
children:
'The Jurassic ranges from 200 million years to 145 million years ago.'
},
{
name: 'Cretaceous',
children:
'The Cretaceous is the longest period of the Mesozoic, spanning from 145 million to 66 million years ago.'
}
];
type Tab = typeof tabs[0];
return (
<Tabs
aria-label="Mesozoic time periods"
items={tabs}
keyboardActivation="manual"
>
<TabList>
{(item: Tab) => (
<Item key={item.name}>
{item.name}
</Item>
)}
</TabList>
<TabPanels>
{(item: Tab) => (
<Item key={item.name}>
{item.children}
</Item>
)}
</TabPanels>
</Tabs>
);
}
function Example() {
let tabs = [
{
name: 'Triassic',
children:
'The Triassic ranges roughly from 252 million to 201 million years ago, preceding the Jurassic Period.'
},
{
name: 'Jurassic',
children:
'The Jurassic ranges from 200 million years to 145 million years ago.'
},
{
name: 'Cretaceous',
children:
'The Cretaceous is the longest period of the Mesozoic, spanning from 145 million to 66 million years ago.'
}
];
type Tab =
typeof tabs[0];
return (
<Tabs
aria-label="Mesozoic time periods"
items={tabs}
keyboardActivation="manual"
>
<TabList>
{(item: Tab) => (
<Item
key={item
.name}
>
{item.name}
</Item>
)}
</TabList>
<TabPanels>
{(item: Tab) => (
<Item
key={item
.name}
>
{item
.children}
</Item>
)}
</TabPanels>
</Tabs>
);
}
Links#
Tabs may be rendered as links to different routes in your application. This can be achieved by passing the href
prop to the <Item>
component. By default, links perform native browser navigation. However, you'll usually want to synchronize the selected tab with the URL from your client side router. This takes two steps:
- Set up a
RouterProvider
at the root of your app. This will handle link navigation from all React Aria components using your framework or router. See the client side routing guide to learn how to set this up. - Use the
selectedKey
prop to set the selected tab based on the URL, as described above.
This example uses React Router to setup routes for each tab and synchronize the selection with the URL.
import {BrowserRouter, Outlet, Route, Routes, useLocation, useNavigate} from 'react-router-dom';
import {RouterProvider} from '@adobe/react-spectrum';
function AppTabs() {
let { pathname } = useLocation();
return (
<Tabs selectedKey={pathname}>
<TabList aria-label="Tabs">
<Item key="/" href="/">Home</Item>
<Item key="/shared" href="/shared">Shared</Item>
<Item key="/deleted" href="/deleted">Deleted</Item>
</TabList>
<TabPanels>
<Item key="/">
<Outlet />
</Item>
<Item key="/shared">
<Outlet />
</Item>
<Item key="/deleted">
<Outlet />
</Item>
</TabPanels>
</Tabs>
);
}
function App() {
let navigate = useNavigate();
return (
<RouterProvider navigate={navigate}>
<Routes>
<Route path="/" element={<AppTabs />}>
<Route index element={<HomePage />} />
<Route path="/shared" element={<SharedPage />} />
<Route path="/deleted" element={<DeletedPage />} />
</Route>
</Routes>
</RouterProvider>
);
}
<BrowserRouter>
<App />
</BrowserRouter>
import {
BrowserRouter,
Outlet,
Route,
Routes,
useLocation,
useNavigate
} from 'react-router-dom';
import {RouterProvider} from '@adobe/react-spectrum';
function AppTabs() {
let { pathname } = useLocation();
return (
<Tabs selectedKey={pathname}>
<TabList aria-label="Tabs">
<Item key="/" href="/">Home</Item>
<Item key="/shared" href="/shared">Shared</Item>
<Item key="/deleted" href="/deleted">Deleted</Item>
</TabList>
<TabPanels>
<Item key="/">
<Outlet />
</Item>
<Item key="/shared">
<Outlet />
</Item>
<Item key="/deleted">
<Outlet />
</Item>
</TabPanels>
</Tabs>
);
}
function App() {
let navigate = useNavigate();
return (
<RouterProvider navigate={navigate}>
<Routes>
<Route path="/" element={<AppTabs />}>
<Route index element={<HomePage />} />
<Route path="/shared" element={<SharedPage />} />
<Route
path="/deleted"
element={<DeletedPage />}
/>
</Route>
</Routes>
</RouterProvider>
);
}
<BrowserRouter>
<App />
</BrowserRouter>
import {
BrowserRouter,
Outlet,
Route,
Routes,
useLocation,
useNavigate
} from 'react-router-dom';
import {RouterProvider} from '@adobe/react-spectrum';
function AppTabs() {
let { pathname } =
useLocation();
return (
<Tabs
selectedKey={pathname}
>
<TabList aria-label="Tabs">
<Item
key="/"
href="/"
>
Home
</Item>
<Item
key="/shared"
href="/shared"
>
Shared
</Item>
<Item
key="/deleted"
href="/deleted"
>
Deleted
</Item>
</TabList>
<TabPanels>
<Item key="/">
<Outlet />
</Item>
<Item key="/shared">
<Outlet />
</Item>
<Item key="/deleted">
<Outlet />
</Item>
</TabPanels>
</Tabs>
);
}
function App() {
let navigate =
useNavigate();
return (
<RouterProvider
navigate={navigate}
>
<Routes>
<Route
path="/"
element={
<AppTabs />
}
>
<Route
index
element={
<HomePage />
}
/>
<Route
path="/shared"
element={
<SharedPage />
}
/>
<Route
path="/deleted"
element={
<DeletedPage />
}
/>
</Route>
</Routes>
</RouterProvider>
);
}
<BrowserRouter>
<App />
</BrowserRouter>
Props#
Tabs Props#
Name | Type | Default | Description |
children | ReactNode | — | The children of the <Tabs> element. Should include <TabList> and <TabPanels> elements. |
items | Iterable<object> | — | The item objects for each tab, for dynamic collections. |
disabledKeys | Iterable<Key> | — | The keys of the tabs that are disabled. These tabs cannot be selected, focused, or otherwise interacted with. |
isDisabled | boolean | — | Whether the Tabs are disabled. |
isQuiet | boolean | — | Whether the tabs are displayed in a quiet style. |
isEmphasized | boolean | — | Whether the tabs are displayed in an emphasized style. |
density | 'compact' | 'regular' | — | The amount of space between the tabs. |
keyboardActivation | 'automatic' | 'manual' | 'automatic' | Whether tabs are activated automatically on focus or manually. |
orientation | Orientation | 'horizontal' | The orientation of the tabs. |
disallowEmptySelection | boolean | — | Whether the collection allows empty selection. |
selectedKey | Key | null | — | The currently selected key in the collection (controlled). |
defaultSelectedKey | Key | — | The initial selected key in the collection (uncontrolled). |
Events
Name | Type | Description |
onSelectionChange | (
(key: Key
)) => void | Handler that is called when the selection changes. |
Layout
Name | Type | Description |
flex | Responsive<string
| number
| boolean> | When used in a flex layout, specifies how the element will grow or shrink to fit the space available. See MDN. |
flexGrow | Responsive<number> | When used in a flex layout, specifies how the element will grow to fit the space available. See MDN. |
flexShrink | Responsive<number> | When used in a flex layout, specifies how the element will shrink to fit the space available. See MDN. |
flexBasis | Responsive<number | string> | When used in a flex layout, specifies the initial main size of the element. See MDN. |
alignSelf | Responsive<'auto'
| 'normal'
| 'start'
| 'end'
| 'center'
| 'flex-start'
| 'flex-end'
| 'self-start'
| 'self-end'
| 'stretch'> | Overrides the alignItems property of a flex or grid container. See MDN. |
justifySelf | Responsive<'auto'
| 'normal'
| 'start'
| 'end'
| 'flex-start'
| 'flex-end'
| 'self-start'
| 'self-end'
| 'center'
| 'left'
| 'right'
| 'stretch'> | Specifies how the element is justified inside a flex or grid container. See MDN. |
order | Responsive<number> | The layout order for the element within a flex or grid container. See MDN. |
gridArea | Responsive<string> | When used in a grid layout, specifies the named grid area that the element should be placed in within the grid. See MDN. |
gridColumn | Responsive<string> | When used in a grid layout, specifies the column the element should be placed in within the grid. See MDN. |
gridRow | Responsive<string> | When used in a grid layout, specifies the row the element should be placed in within the grid. See MDN. |
gridColumnStart | Responsive<string> | When used in a grid layout, specifies the starting column to span within the grid. See MDN. |
gridColumnEnd | Responsive<string> | When used in a grid layout, specifies the ending column to span within the grid. See MDN. |
gridRowStart | Responsive<string> | When used in a grid layout, specifies the starting row to span within the grid. See MDN. |
gridRowEnd | Responsive<string> | When used in a grid layout, specifies the ending row to span within the grid. See MDN. |
Spacing
Name | Type | Description |
margin | Responsive<DimensionValue> | The margin for all four sides of the element. See MDN. |
marginTop | Responsive<DimensionValue> | The margin for the top side of the element. See MDN. |
marginBottom | Responsive<DimensionValue> | The margin for the bottom side of the element. See MDN. |
marginStart | Responsive<DimensionValue> | The margin for the logical start side of the element, depending on layout direction. See MDN. |
marginEnd | Responsive<DimensionValue> | The margin for the logical end side of an element, depending on layout direction. See MDN. |
marginX | Responsive<DimensionValue> | The margin for both the left and right sides of the element. See MDN. |
marginY | Responsive<DimensionValue> | The margin for both the top and bottom sides of the element. See MDN. |
Sizing
Name | Type | Description |
width | Responsive<DimensionValue> | The width of the element. See MDN. |
minWidth | Responsive<DimensionValue> | The minimum width of the element. See MDN. |
maxWidth | Responsive<DimensionValue> | The maximum width of the element. See MDN. |
height | Responsive<DimensionValue> | The height of the element. See MDN. |
minHeight | Responsive<DimensionValue> | The minimum height of the element. See MDN. |
maxHeight | Responsive<DimensionValue> | The maximum height of the element. See MDN. |
Positioning
Name | Type | Description |
position | Responsive<'static'
| 'relative'
| 'absolute'
| 'fixed'
| 'sticky'> | Specifies how the element is positioned. See MDN. |
top | Responsive<DimensionValue> | The top position for the element. See MDN. |
bottom | Responsive<DimensionValue> | The bottom position for the element. See MDN. |
left | Responsive<DimensionValue> | The left position for the element. See MDN. Consider using start instead for RTL support. |
right | Responsive<DimensionValue> | The right position for the element. See MDN. Consider using start instead for RTL support. |
start | Responsive<DimensionValue> | The logical start position for the element, depending on layout direction. See MDN. |
end | Responsive<DimensionValue> | The logical end position for the element, depending on layout direction. See MDN. |
zIndex | Responsive<number> | The stacking order for the element. See MDN. |
isHidden | Responsive<boolean> | Hides the element. |
Accessibility
Name | Type | Description |
id | string | The element's unique identifier. See MDN. |
aria-label | string | Defines a string value that labels the current element. |
aria-labelledby | string | Identifies the element (or elements) that labels the current element. |
aria-describedby | string | Identifies the element (or elements) that describes the object. |
aria-details | string | Identifies the element (or elements) that provide a detailed, extended description for the object. |
Advanced
Name | Type | Description |
UNSAFE_className | string | Sets the CSS className for the element. Only use as a last resort. Use style props instead. |
UNSAFE_style | CSSProperties | Sets inline style for the element. Only use as a last resort. Use style props instead. |
TabList Props#
TabPanels Props#
Name | Type | Description |
children | CollectionChildren<object> | The contents of each tab. Item keys should match the key of the corresponding <Item> within the <TabList> element. |
Layout
Name | Type | Description |
flex | Responsive<string
| number
| boolean> | When used in a flex layout, specifies how the element will grow or shrink to fit the space available. See MDN. |
flexGrow | Responsive<number> | When used in a flex layout, specifies how the element will grow to fit the space available. See MDN. |
flexShrink | Responsive<number> | When used in a flex layout, specifies how the element will shrink to fit the space available. See MDN. |
flexBasis | Responsive<number | string> | When used in a flex layout, specifies the initial main size of the element. See MDN. |
alignSelf | Responsive<'auto'
| 'normal'
| 'start'
| 'end'
| 'center'
| 'flex-start'
| 'flex-end'
| 'self-start'
| 'self-end'
| 'stretch'> | Overrides the alignItems property of a flex or grid container. See MDN. |
justifySelf | Responsive<'auto'
| 'normal'
| 'start'
| 'end'
| 'flex-start'
| 'flex-end'
| 'self-start'
| 'self-end'
| 'center'
| 'left'
| 'right'
| 'stretch'> | Specifies how the element is justified inside a flex or grid container. See MDN. |
order | Responsive<number> | The layout order for the element within a flex or grid container. See MDN. |
gridArea | Responsive<string> | When used in a grid layout, specifies the named grid area that the element should be placed in within the grid. See MDN. |
gridColumn | Responsive<string> | When used in a grid layout, specifies the column the element should be placed in within the grid. See MDN. |
gridRow | Responsive<string> | When used in a grid layout, specifies the row the element should be placed in within the grid. See MDN. |
gridColumnStart | Responsive<string> | When used in a grid layout, specifies the starting column to span within the grid. See MDN. |
gridColumnEnd | Responsive<string> | When used in a grid layout, specifies the ending column to span within the grid. See MDN. |
gridRowStart | Responsive<string> | When used in a grid layout, specifies the starting row to span within the grid. See MDN. |
gridRowEnd | Responsive<string> | When used in a grid layout, specifies the ending row to span within the grid. See MDN. |
Spacing
Name | Type | Description |
margin | Responsive<DimensionValue> | The margin for all four sides of the element. See MDN. |
marginTop | Responsive<DimensionValue> | The margin for the top side of the element. See MDN. |
marginBottom | Responsive<DimensionValue> | The margin for the bottom side of the element. See MDN. |
marginStart | Responsive<DimensionValue> | The margin for the logical start side of the element, depending on layout direction. See MDN. |
marginEnd | Responsive<DimensionValue> | The margin for the logical end side of an element, depending on layout direction. See MDN. |
marginX | Responsive<DimensionValue> | The margin for both the left and right sides of the element. See MDN. |
marginY | Responsive<DimensionValue> | The margin for both the top and bottom sides of the element. See MDN. |
Sizing
Name | Type | Description |
width | Responsive<DimensionValue> | The width of the element. See MDN. |
minWidth | Responsive<DimensionValue> | The minimum width of the element. See MDN. |
maxWidth | Responsive<DimensionValue> | The maximum width of the element. See MDN. |
height | Responsive<DimensionValue> | The height of the element. See MDN. |
minHeight | Responsive<DimensionValue> | The minimum height of the element. See MDN. |
maxHeight | Responsive<DimensionValue> | The maximum height of the element. See MDN. |
Positioning
Name | Type | Description |
position | Responsive<'static'
| 'relative'
| 'absolute'
| 'fixed'
| 'sticky'> | Specifies how the element is positioned. See MDN. |
top | Responsive<DimensionValue> | The top position for the element. See MDN. |
bottom | Responsive<DimensionValue> | The bottom position for the element. See MDN. |
left | Responsive<DimensionValue> | The left position for the element. See MDN. Consider using start instead for RTL support. |
right | Responsive<DimensionValue> | The right position for the element. See MDN. Consider using start instead for RTL support. |
start | Responsive<DimensionValue> | The logical start position for the element, depending on layout direction. See MDN. |
end | Responsive<DimensionValue> | The logical end position for the element, depending on layout direction. See MDN. |
zIndex | Responsive<number> | The stacking order for the element. See MDN. |
isHidden | Responsive<boolean> | Hides the element. |
Advanced
Name | Type | Description |
UNSAFE_className | string | Sets the CSS className for the element. Only use as a last resort. Use style props instead. |
UNSAFE_style | CSSProperties | Sets inline style for the element. Only use as a last resort. Use style props instead. |
Visual options#
Density#
<Tabs aria-label="Chat log density example" density="compact">
<TabList>
<Item key="item1">
John Doe
</Item>
<Item key="item2">
Jane Doe
</Item>
<Item key="item3">
Joe Bloggs
</Item>
</TabList>
<TabPanels>
<Item key="item1">
There is no prior chat history with John Doe.
</Item>
<Item key="item2">
There is no prior chat history with Jane Doe.
</Item>
<Item key="item3">
There is no prior chat history with Joe Bloggs.
</Item>
</TabPanels>
</Tabs>
<Tabs
aria-label="Chat log density example"
density="compact"
>
<TabList>
<Item key="item1">
John Doe
</Item>
<Item key="item2">
Jane Doe
</Item>
<Item key="item3">
Joe Bloggs
</Item>
</TabList>
<TabPanels>
<Item key="item1">
There is no prior chat history with John Doe.
</Item>
<Item key="item2">
There is no prior chat history with Jane Doe.
</Item>
<Item key="item3">
There is no prior chat history with Joe Bloggs.
</Item>
</TabPanels>
</Tabs>
<Tabs
aria-label="Chat log density example"
density="compact"
>
<TabList>
<Item key="item1">
John Doe
</Item>
<Item key="item2">
Jane Doe
</Item>
<Item key="item3">
Joe Bloggs
</Item>
</TabList>
<TabPanels>
<Item key="item1">
There is no prior
chat history with
John Doe.
</Item>
<Item key="item2">
There is no prior
chat history with
Jane Doe.
</Item>
<Item key="item3">
There is no prior
chat history with
Joe Bloggs.
</Item>
</TabPanels>
</Tabs>
Quiet#
<Tabs aria-label="Chat log quiet example" isQuiet>
<TabList>
<Item key="item1">
John Doe
</Item>
<Item key="item2">
Jane Doe
</Item>
<Item key="item3">
Joe Bloggs
</Item>
</TabList>
<TabPanels>
<Item key="item1">
There is no prior chat history with John Doe.
</Item>
<Item key="item2">
There is no prior chat history with Jane Doe.
</Item>
<Item key="item3">
There is no prior chat history with Joe Bloggs.
</Item>
</TabPanels>
</Tabs>
<Tabs aria-label="Chat log quiet example" isQuiet>
<TabList>
<Item key="item1">
John Doe
</Item>
<Item key="item2">
Jane Doe
</Item>
<Item key="item3">
Joe Bloggs
</Item>
</TabList>
<TabPanels>
<Item key="item1">
There is no prior chat history with John Doe.
</Item>
<Item key="item2">
There is no prior chat history with Jane Doe.
</Item>
<Item key="item3">
There is no prior chat history with Joe Bloggs.
</Item>
</TabPanels>
</Tabs>
<Tabs
aria-label="Chat log quiet example"
isQuiet
>
<TabList>
<Item key="item1">
John Doe
</Item>
<Item key="item2">
Jane Doe
</Item>
<Item key="item3">
Joe Bloggs
</Item>
</TabList>
<TabPanels>
<Item key="item1">
There is no prior
chat history with
John Doe.
</Item>
<Item key="item2">
There is no prior
chat history with
Jane Doe.
</Item>
<Item key="item3">
There is no prior
chat history with
Joe Bloggs.
</Item>
</TabPanels>
</Tabs>
Disabled#
<Flex direction="column" rowGap="size-150">
<Tabs
aria-label="Chat log single tab disabled example"
disabledKeys={['item2']}
>
<TabList>
<Item key="item1">
John Doe
</Item>
<Item key="item2">
Jane Doe
</Item>
<Item key="item3">
Joe Bloggs
</Item>
</TabList>
<TabPanels>
<Item key="item1">
There is no prior chat history with John Doe.
</Item>
<Item key="item2">
There is no prior chat history with Jane Doe.
</Item>
<Item key="item3">
There is no prior chat history with Joe Bloggs.
</Item>
</TabPanels>
</Tabs>
<Tabs aria-label="Chat log all tabs disabled example" isDisabled>
<TabList>
<Item key="item1">
John Doe
</Item>
<Item key="item2">
Jane Doe
</Item>
<Item key="item3">
Joe Bloggs
</Item>
</TabList>
<TabPanels>
<Item key="item1">
There is no prior chat history with John Doe.
</Item>
<Item key="item2">
There is no prior chat history with Jane Doe.
</Item>
<Item key="item3">
There is no prior chat history with Joe Bloggs.
</Item>
</TabPanels>
</Tabs>
</Flex>
<Flex direction="column" rowGap="size-150">
<Tabs
aria-label="Chat log single tab disabled example"
disabledKeys={['item2']}
>
<TabList>
<Item key="item1">
John Doe
</Item>
<Item key="item2">
Jane Doe
</Item>
<Item key="item3">
Joe Bloggs
</Item>
</TabList>
<TabPanels>
<Item key="item1">
There is no prior chat history with John Doe.
</Item>
<Item key="item2">
There is no prior chat history with Jane Doe.
</Item>
<Item key="item3">
There is no prior chat history with Joe Bloggs.
</Item>
</TabPanels>
</Tabs>
<Tabs
aria-label="Chat log all tabs disabled example"
isDisabled
>
<TabList>
<Item key="item1">
John Doe
</Item>
<Item key="item2">
Jane Doe
</Item>
<Item key="item3">
Joe Bloggs
</Item>
</TabList>
<TabPanels>
<Item key="item1">
There is no prior chat history with John Doe.
</Item>
<Item key="item2">
There is no prior chat history with Jane Doe.
</Item>
<Item key="item3">
There is no prior chat history with Joe Bloggs.
</Item>
</TabPanels>
</Tabs>
</Flex>
<Flex
direction="column"
rowGap="size-150"
>
<Tabs
aria-label="Chat log single tab disabled example"
disabledKeys={[
'item2'
]}
>
<TabList>
<Item key="item1">
John Doe
</Item>
<Item key="item2">
Jane Doe
</Item>
<Item key="item3">
Joe Bloggs
</Item>
</TabList>
<TabPanels>
<Item key="item1">
There is no
prior chat
history with
John Doe.
</Item>
<Item key="item2">
There is no
prior chat
history with
Jane Doe.
</Item>
<Item key="item3">
There is no
prior chat
history with
Joe Bloggs.
</Item>
</TabPanels>
</Tabs>
<Tabs
aria-label="Chat log all tabs disabled example"
isDisabled
>
<TabList>
<Item key="item1">
John Doe
</Item>
<Item key="item2">
Jane Doe
</Item>
<Item key="item3">
Joe Bloggs
</Item>
</TabList>
<TabPanels>
<Item key="item1">
There is no
prior chat
history with
John Doe.
</Item>
<Item key="item2">
There is no
prior chat
history with
Jane Doe.
</Item>
<Item key="item3">
There is no
prior chat
history with
Joe Bloggs.
</Item>
</TabPanels>
</Tabs>
</Flex>
Orientation#
By default, tabs are horizontally oriented. The orientation
prop can be set to vertical
to change this. This does not affect keyboard navigation.
<Tabs aria-label="Chat log orientation example" orientation="vertical">
<TabList>
<Item key="item1">
John Doe
</Item>
<Item key="item2">
Jane Doe
</Item>
<Item key="item3">
Joe Bloggs
</Item>
</TabList>
<TabPanels>
<Item key="item1">
There is no prior chat history with John Doe.
</Item>
<Item key="item2">
There is no prior chat history with Jane Doe.
</Item>
<Item key="item3">
There is no prior chat history with Joe Bloggs.
</Item>
</TabPanels>
</Tabs>
<Tabs
aria-label="Chat log orientation example"
orientation="vertical"
>
<TabList>
<Item key="item1">
John Doe
</Item>
<Item key="item2">
Jane Doe
</Item>
<Item key="item3">
Joe Bloggs
</Item>
</TabList>
<TabPanels>
<Item key="item1">
There is no prior chat history with John Doe.
</Item>
<Item key="item2">
There is no prior chat history with Jane Doe.
</Item>
<Item key="item3">
There is no prior chat history with Joe Bloggs.
</Item>
</TabPanels>
</Tabs>
<Tabs
aria-label="Chat log orientation example"
orientation="vertical"
>
<TabList>
<Item key="item1">
John Doe
</Item>
<Item key="item2">
Jane Doe
</Item>
<Item key="item3">
Joe Bloggs
</Item>
</TabList>
<TabPanels>
<Item key="item1">
There is no prior
chat history with
John Doe.
</Item>
<Item key="item2">
There is no prior
chat history with
Jane Doe.
</Item>
<Item key="item3">
There is no prior
chat history with
Joe Bloggs.
</Item>
</TabPanels>
</Tabs>
Collapse (overflow behavior)#
If there isn't enough horizontal room to render every tab on a single line, the component will collapse all tabs into a Picker
. Note that this does not apply to vertical Tabs.
Try the example below to see the above behavior.
function Example() {
let [collapse, setCollapse] = React.useState(false);
return (
<>
<div
style={{
width: collapse ? '150px' : '300px',
marginBottom: '50px',
height: '150px',
maxWidth: '100%'
}}
>
<Tabs aria-label="Chat log collapse example">
<TabList>
<Item key="item1">
John Doe
</Item>
<Item key="item2">
Jane Doe
</Item>
<Item key="item3">
Joe Bloggs
</Item>
</TabList>
<TabPanels>
<Item key="item1">
There is no prior chat history with John Doe.
</Item>
<Item key="item2">
There is no prior chat history with Jane Doe.
</Item>
<Item key="item3">
There is no prior chat history with Joe Bloggs.
</Item>
</TabPanels>
</Tabs>
</div>
<Button
variant="primary"
onPress={() => setCollapse((collapse) => !collapse)}
>
Toggle tab container size.
</Button>
</>
);
}
function Example() {
let [collapse, setCollapse] = React.useState(false);
return (
<>
<div
style={{
width: collapse ? '150px' : '300px',
marginBottom: '50px',
height: '150px',
maxWidth: '100%'
}}
>
<Tabs aria-label="Chat log collapse example">
<TabList>
<Item key="item1">
John Doe
</Item>
<Item key="item2">
Jane Doe
</Item>
<Item key="item3">
Joe Bloggs
</Item>
</TabList>
<TabPanels>
<Item key="item1">
There is no prior chat history with John Doe.
</Item>
<Item key="item2">
There is no prior chat history with Jane Doe.
</Item>
<Item key="item3">
There is no prior chat history with Joe
Bloggs.
</Item>
</TabPanels>
</Tabs>
</div>
<Button
variant="primary"
onPress={() => setCollapse((collapse) => !collapse)}
>
Toggle tab container size.
</Button>
</>
);
}
function Example() {
let [
collapse,
setCollapse
] = React.useState(
false
);
return (
<>
<div
style={{
width: collapse
? '150px'
: '300px',
marginBottom:
'50px',
height:
'150px',
maxWidth:
'100%'
}}
>
<Tabs aria-label="Chat log collapse example">
<TabList>
<Item key="item1">
John Doe
</Item>
<Item key="item2">
Jane Doe
</Item>
<Item key="item3">
Joe Bloggs
</Item>
</TabList>
<TabPanels>
<Item key="item1">
There is no
prior chat
history
with John
Doe.
</Item>
<Item key="item2">
There is no
prior chat
history
with Jane
Doe.
</Item>
<Item key="item3">
There is no
prior chat
history
with Joe
Bloggs.
</Item>
</TabPanels>
</Tabs>
</div>
<Button
variant="primary"
onPress={() =>
setCollapse((
collapse
) =>
!collapse
)}
>
Toggle tab
container size.
</Button>
</>
);
}
Emphasized#
<Tabs aria-label="Chat log emphasized example" isEmphasized>
<TabList>
<Item key="item1">
John Doe
</Item>
<Item key="item2">
Jane Doe
</Item>
<Item key="item3">
Joe Bloggs
</Item>
</TabList>
<TabPanels>
<Item key="item1">
There is no prior chat history with John Doe.
</Item>
<Item key="item2">
There is no prior chat history with Jane Doe.
</Item>
<Item key="item3">
There is no prior chat history with Joe Bloggs.
</Item>
</TabPanels>
</Tabs>
<Tabs aria-label="Chat log emphasized example" isEmphasized>
<TabList>
<Item key="item1">
John Doe
</Item>
<Item key="item2">
Jane Doe
</Item>
<Item key="item3">
Joe Bloggs
</Item>
</TabList>
<TabPanels>
<Item key="item1">
There is no prior chat history with John Doe.
</Item>
<Item key="item2">
There is no prior chat history with Jane Doe.
</Item>
<Item key="item3">
There is no prior chat history with Joe Bloggs.
</Item>
</TabPanels>
</Tabs>
<Tabs
aria-label="Chat log emphasized example"
isEmphasized
>
<TabList>
<Item key="item1">
John Doe
</Item>
<Item key="item2">
Jane Doe
</Item>
<Item key="item3">
Joe Bloggs
</Item>
</TabList>
<TabPanels>
<Item key="item1">
There is no prior
chat history with
John Doe.
</Item>
<Item key="item2">
There is no prior
chat history with
Jane Doe.
</Item>
<Item key="item3">
There is no prior
chat history with
Joe Bloggs.
</Item>
</TabPanels>
</Tabs>
Testing#
Test utils alpha#
Tabs features automatic tab collapse behavior and may need specific mocks to test said behavior. Please also refer to React Spectrum's test suite if you run into any issues with your tests.
@react-spectrum/test-utils
offers common tabs interaction utilities which you may find helpful when writing tests. See here for more information on how to setup these utilities
in your tests. Below is the full definition of the tabs tester and a sample of how you could use it in your test suite.
// Tabs.test.ts
import {render} from '@testing-library/react';
import {theme} from '@react-spectrum/theme-default';
import {User} from '@react-spectrum/test-utils';
let testUtilUser = new User({ interactionType: 'mouse' });
// Other setup, be sure to check out the suggested mocks mentioned above in https://react-spectrum.adobe.com/react-spectrum/Tabs.html#testing
it('Tabs can change selection via keyboard', async function () {
// Render your test component/app and initialize the listbox tester
let { getByTestId } = render(
<Provider theme={defaultTheme}>
<Tabs data-testid="test-tabs">
...
</Tabs>
</Provider>
);
let tabsTester = testUtilUser.createTester('Tabs', {
root: getByTestId('test-tabs'),
interactionType: 'keyboard'
});
let tabs = tabsTester.tabs;
expect(tabsTester.selectedTab).toBe(tabs[0]);
await tabsTester.triggerTab({ tab: 1 });
expect(tabsTester.selectedTab).toBe(tabs[1]);
});
// Tabs.test.ts
import {render} from '@testing-library/react';
import {theme} from '@react-spectrum/theme-default';
import {User} from '@react-spectrum/test-utils';
let testUtilUser = new User({ interactionType: 'mouse' });
// Other setup, be sure to check out the suggested mocks mentioned above in https://react-spectrum.adobe.com/react-spectrum/Tabs.html#testing
it('Tabs can change selection via keyboard', async function () {
// Render your test component/app and initialize the listbox tester
let { getByTestId } = render(
<Provider theme={defaultTheme}>
<Tabs data-testid="test-tabs">
...
</Tabs>
</Provider>
);
let tabsTester = testUtilUser.createTester('Tabs', {
root: getByTestId('test-tabs'),
interactionType: 'keyboard'
});
let tabs = tabsTester.tabs;
expect(tabsTester.selectedTab).toBe(tabs[0]);
await tabsTester.triggerTab({ tab: 1 });
expect(tabsTester.selectedTab).toBe(tabs[1]);
});
// Tabs.test.ts
import {render} from '@testing-library/react';
import {theme} from '@react-spectrum/theme-default';
import {User} from '@react-spectrum/test-utils';
let testUtilUser =
new User({
interactionType:
'mouse'
});
// Other setup, be sure to check out the suggested mocks mentioned above in https://react-spectrum.adobe.com/react-spectrum/Tabs.html#testing
it('Tabs can change selection via keyboard', async function () {
// Render your test component/app and initialize the listbox tester
let { getByTestId } =
render(
<Provider
theme={defaultTheme}
>
<Tabs data-testid="test-tabs">
...
</Tabs>
</Provider>
);
let tabsTester =
testUtilUser
.createTester(
'Tabs',
{
root:
getByTestId(
'test-tabs'
),
interactionType:
'keyboard'
}
);
let tabs =
tabsTester.tabs;
expect(
tabsTester
.selectedTab
).toBe(tabs[0]);
await tabsTester
.triggerTab({
tab: 1
});
expect(
tabsTester
.selectedTab
).toBe(tabs[1]);
});
Properties
Name | Type | Description |
tablist | HTMLElement | Returns the tablist. |
tabpanels | HTMLElement[] | Returns the tabpanels. |
tabs | HTMLElement[] | Returns the tabs in the tablist. |
selectedTab | HTMLElement | null | Returns the currently selected tab in the tablist if any. |
activeTabpanel | HTMLElement | null | Returns the currently active tabpanel if any. |
Methods
Method | Description |
constructor(
(opts: TabsTesterOpts
)): void | |
setInteractionType(
(type: UserOpts['interactionType']
)): void | Set the interaction type used by the tabs tester. |
findTab(
(opts: {}
)): HTMLElement | Returns a tab matching the specified index or text content. |
triggerTab(
(opts: TriggerTabOptions
)): Promise<void> | Triggers the specified tab. Defaults to using the interaction type set on the tabs tester. |