Tabs

An accessible tabs component that provides keyboard interactions and ARIA attributes described in the WAI-ARIA Tabs Design Pattern.

Import#

import { Tabs, TabList, TabPanels, Tab, TabPanel } from '@chakra-ui/react'
  • Tabs: Provides context and state for all components
  • TabList: Wrapper for the Tab components
  • Tab: element that serves as a label for one of the tab panels and can be activated to display that panel.
  • TabPanels: Wrapper for the TabPanel components
  • TabPanel: element that contains the content associated with a tab

Usage#

You can render any element within Tabs, but TabList should only have Tab as children, and TabPanels should have TabPanel as children.

Tabs expects TabList and TabPanels as children. The order doesn't matter, you can have TabList at the top, at the bottom, or both.

<Tabs>
<TabList>
<Tab>One</Tab>
<Tab>Two</Tab>
<Tab>Three</Tab>
</TabList>
<TabPanels>
<TabPanel>
<p>one!</p>
</TabPanel>
<TabPanel>
<p>two!</p>
</TabPanel>
<TabPanel>
<p>three!</p>
</TabPanel>
</TabPanels>
</Tabs>

Tab variants and color#

Tabs come in 6 different variants to style the tabs: line,enclosed, enclosed-colored, soft-rounded, solid-rounded

<Tabs variant='enclosed'>
<TabList>
<Tab>One</Tab>
<Tab>Two</Tab>
</TabList>
<TabPanels>
<TabPanel>
<p>one!</p>
</TabPanel>
<TabPanel>
<p>two!</p>
</TabPanel>
</TabPanels>
</Tabs>

You can also change the color scheme for any specific variant by passing the colorScheme.

The value of colorScheme must exist in the theme object, and must be a key in theme.colors that has the 50 - 900 color values.

<Tabs variant='soft-rounded' colorScheme='green'>
<TabList>
<Tab>Tab 1</Tab>
<Tab>Tab 2</Tab>
</TabList>
<TabPanels>
<TabPanel>
<p>one!</p>
</TabPanel>
<TabPanel>
<p>two!</p>
</TabPanel>
</TabPanels>
</Tabs>

Tab sizes#

You can change the size of the tab by passing size prop. We support 3 sizes sm, md, lg

<Tabs size='md' variant='enclosed'>
<TabList>
<Tab>One</Tab>
<Tab>Two</Tab>
</TabList>
<TabPanels>
<TabPanel>
<p>one!</p>
</TabPanel>
<TabPanel>
<p>two!</p>
</TabPanel>
</TabPanels>
</Tabs>

Changing the tabs alignment#

You can change the alignment of the TabList by passing align prop. We support 3 sizes start, center, end.

<Tabs align='end' variant='enclosed'>
<TabList>
<Tab>One</Tab>
<Tab>Two</Tab>
</TabList>
<TabPanels>
<TabPanel>
<p>one!</p>
</TabPanel>
<TabPanel>
<p>two!</p>
</TabPanel>
</TabPanels>
</Tabs>

Fitted Tabs#

Stretch the tab list to fit the container by passing isFitted prop.

<Tabs isFitted variant='enclosed'>
<TabList mb='1em'>
<Tab>One</Tab>
<Tab>Two</Tab>
</TabList>
<TabPanels>
<TabPanel>
<p>one!</p>
</TabPanel>
<TabPanel>
<p>two!</p>
</TabPanel>
</TabPanels>
</Tabs>

Styling the tab states via props#

In event you need to create custom styles for the tabs. Simply set the variant to unstyled, and use the _selected, _hover, _active style props.

<Tabs variant='unstyled'>
<TabList>
<Tab _selected={{ color: 'white', bg: 'blue.500' }}>Tab 1</Tab>
<Tab _selected={{ color: 'white', bg: 'green.400' }}>Tab 2</Tab>
</TabList>
<TabPanels>
<TabPanel>
<p>one!</p>
</TabPanel>
<TabPanel>
<p>two!</p>
</TabPanel>
</TabPanels>
</Tabs>

Tabs onChange#

The onChange callback returns the active tab's index whenever the user changes tabs. If you intend to control the tabs programmatically, use this with the index prop.

function Example() {
const colors = useColorModeValue(
['red.50', 'teal.50', 'blue.50'],
['red.900', 'teal.900', 'blue.900'],
)
const [tabIndex, setTabIndex] = React.useState(0)
const bg = colors[tabIndex]
return (
<Tabs onChange={(index) => setTabIndex(index)} bg={bg}>
<TabList>
<Tab>Red</Tab>
<Tab>Teal</Tab>
<Tab>Blue</Tab>
</TabList>
<TabPanels p='2rem'>
<TabPanel>The Primary Colors</TabPanel>
<TabPanel>Are 1, 2, 3</TabPanel>
<TabPanel>Red, yellow and blue.</TabPanel>
</TabPanels>
</Tabs>
)
}

Make a tab initially active#

If you want a tab to be initially active, simply pass the defaultIndex prop and set it to the index of that tab.

<Tabs defaultIndex={1}>
<TabPanels>
<TabPanel>
<Image
boxSize='200px'
fit='cover'
src='https://resizing.flixster.com/wTgvsiM8vNLhCcCH-6ovV8n5z5U=/300x300/v1.bjsyMDkxMzI5O2o7MTgyMDQ7MTIwMDsxMjAwOzkwMA'
/>
</TabPanel>
<TabPanel>
<Image
boxSize='200px'
fit='cover'
src='https://vignette.wikia.nocookie.net/naruto/images/2/21/Sasuke_Part_1.png/revision/latest?cb=20170716092103'
/>
</TabPanel>
</TabPanels>
<TabList>
<Tab>Naruto</Tab>
<Tab>Sasuke</Tab>
</TabList>
</Tabs>

Make a Tab disabled#

When a Tab is disabled, it is skipped during keyboard navigation and it is not clickable.

function Example() {
return (
<Tabs>
<TabList>
<Tab>One</Tab>
<Tab isDisabled>Two</Tab>
<Tab>Three</Tab>
</TabList>
<TabPanels>
<TabPanel>1</TabPanel>
<TabPanel>2</TabPanel>
<TabPanel>3</TabPanel>
</TabPanels>
</Tabs>
)
}

Tabs with manual activation#

By default, Tabs are activated automatically. This means when you use the arrow keys to change tabs, the tab is activated and focused.

The content of a TabPanel should ideally be preloaded. However, if switching to a tab panel causes a network request and possibly a page refresh, there might be some noticeable latency and this might affect the experience for keyboard and screen reader users.

In this scenario, you should use a manually activated tab, it moves focus without activating the tabs. With focus on a specific tab, users can activate a tab by pressing Space or Enter.

<Tabs isManual variant='enclosed'>
<TabList>
<Tab>One</Tab>
<Tab>Two</Tab>
</TabList>
<TabPanels>
<TabPanel>
<p>one!</p>
</TabPanel>
<TabPanel>
<p>two!</p>
</TabPanel>
</TabPanels>
</Tabs>

Lazily mounting tab panels#

By default, the Tabs component renders all tabs content to the DOM, meaning that invisible tabs are still rendered but are hidden by styles.

If you want to defer rendering of each tab until that tab is selected, you can use the isLazy prop. This is useful if your tabs require heavy performance, or make network calls on mount that should only happen when the component is displayed.

<Tabs isLazy>
<TabList>
<Tab>One</Tab>
<Tab>Two</Tab>
</TabList>
<TabPanels>
{/* initially mounted */}
<TabPanel>
<p>one!</p>
</TabPanel>
{/* initially not mounted */}
<TabPanel>
<p>two!</p>
</TabPanel>
</TabPanels>
</Tabs>

Controlled Tabs#

Like form inputs, a tab's state can be controlled. Make sure to include an onChange as well, or else the tabs will not be interactive.

function ControlledExample() {
const [tabIndex, setTabIndex] = React.useState(0)
const handleSliderChange = (event) => {
setTabIndex(parseInt(event.target.value, 10))
}
const handleTabsChange = (index) => {
setTabIndex(index)
}
return (
<Box>
<input
type='range'
min='0'
max='2'
value={tabIndex}
onChange={handleSliderChange}
/>
<Tabs index={tabIndex} onChange={handleTabsChange}>
<TabList>
<Tab>One</Tab>
<Tab>Two</Tab>
<Tab>Three</Tab>
</TabList>
<TabPanels>
<TabPanel>
<p>Click the tabs or pull the slider around</p>
</TabPanel>
<TabPanel>
<p>Yeah yeah. What's up?</p>
</TabPanel>
<TabPanel>
<p>Oh, hello there.</p>
</TabPanel>
</TabPanels>
</Tabs>
</Box>
)
}

Creating custom tab components#

Because TabList needs to know the order of the children, we use cloneElement to pass state internally. Your custom Tab component must use React.forwardRef.

function CustomTabs() {
const CustomTab = React.forwardRef((props, ref) => {
// 1. Reuse the `useTab` hook
const tabProps = useTab({ ...props, ref })
const isSelected = !!tabProps['aria-selected']
// 2. Hook into the Tabs `size`, `variant`, props
const styles = useMultiStyleConfig('Tabs', tabProps)
return (
<Button __css={styles.tab} {...tabProps}>
<Box as='span' mr='2'>
{isSelected ? '😎' : '😐'}
</Box>
{tabProps.children}
</Button>
)
})
return (
<Tabs>
<TabList>
<CustomTab>One</CustomTab>
<CustomTab>Two</CustomTab>
</TabList>
<TabPanels>
<TabPanel>1</TabPanel>
<TabPanel>2</TabPanel>
</TabPanels>
</Tabs>
)
}

DataTabs#

If you'd like to drive your tabs with an array instead of using the granular components, you can create your own DataTabs component.

function Example() {
// 1. Create the component
function DataTabs({ data }) {
return (
<Tabs>
<TabList>
{data.map((tab, index) => (
<Tab key={index}>{tab.label}</Tab>
))}
</TabList>
<TabPanels>
{data.map((tab, index) => (
<TabPanel p={4} key={index}>
{tab.content}
</TabPanel>
))}
</TabPanels>
</Tabs>
)
}
// 2. Create an array of data
const tabData = [
{
label: 'Nigerian Jollof',
content: 'Perhaps the greatest dish ever invented.',
},
{
label: 'Pounded Yam & Egusi',
content:
'Perhaps the surest dish ever invented but fills the stomach more than rice.',
},
]
// 3. Pass the props and chill!
return <DataTabs data={tabData} />
}

Accessibility#

Keyboard#

KeyAction
ArrowLeftMoves focus to the next tab
ArrowRightMoves focus to the previous tab
TabWhen focus moves into the tab list, places focus on the active tab element
Space or EnterActivates the tab if it was not activated automatically on focus
HomeMoves focus to the first tab
EndMoves focus to the last tab

ARIA roles#

ComponentAriaUsage
Tabrole="tab"Indicates that it is a tab
aria-selectedSet to true a tab is selected and all other Tabs have it set to false
aria-controlsSet to the id of its associated TabPanel
TabListidThe id of the TabPanel that's referenced by its associated Tab
aria-orientationSet to vertical or horizontal based on the value of the orientation prop
role="tablist"Indicates that it is a tablist
TabPanelrole="tabpanel"Indicates that it is a tabpanel
aria-labelledbySet to the id of the Tab that labels the TabPanel

Props#

Tabs Props#

Tabs composes Box so you call pass all Box related props.

align

Description

The alignment of the tabs

Type
"center" | "end" | "start"

colorScheme

Type
"whiteAlpha" | "blackAlpha" | "gray" | "red" | "orange" | "yellow" | "green" | "teal" | "blue" | "cyan" | "purple" | "pink" | "linkedin" | "facebook" | "messenger" | "whatsapp" | "twitter" | "telegram"
Default
"blue"

defaultIndex

Description

The initial index of the selected tab (in uncontrolled mode)

Type
number

direction

Description

The writing mode direction. - When in RTL, the left and right navigation is flipped

Type
"ltr" | "rtl"

id

Description

The id of the tab

Type
string

index

Description

The index of the selected tab (in controlled mode)

Type
number

isFitted

Description

If true, tabs will stretch to width of the tablist.

Type
boolean

isLazy

Description

Performance 🚀: If true, rendering of the tab panel's will be deferred until it is selected.

Type
boolean

isManual

Description

If true, the tabs will be manually activated and display its panel by pressing Space or Enter. If false, the tabs will be automatically activated and their panel is displayed when they receive focus.

Type
boolean

lazyBehavior

Description

Performance 🚀: The lazy behavior of tab panels' content when not active. Only works when `isLazy={true}` - "unmount": The content of inactive tab panels are always unmounted. - "keepMounted": The content of inactive tab panels is initially unmounted, but stays mounted when selected.

Type
LazyBehavior
Default
"unmount"

onChange

Description

Callback when the index (controlled or un-controlled) changes.

Type
((index: number) => void)

orientation

Description

The orientation of the tab list.

Type
"horizontal" | "vertical"

size

Type
"sm" | "md" | "lg"
Default
"md"

variant

Type
"line" | "enclosed" | "enclosed-colored" | "soft-rounded" | "solid-rounded" | "unstyled"
Default
"line"

Tab Props#

id

Type
string

isDisabled

Description

If true, the Tab won't be toggleable

Type
boolean

isSelected

Type
boolean

panelId

Type
string

Proudly made inNigeria by Segun Adebayo

Deployed by Vercel