Tabs make it easy to explore and switch between different views.
Tabs organize and allow navigation between groups of content that are related and at the same level of hierarchy.
Tabs are implemented using a collection of related components:
<Tab />
- the tab element itself. Clicking on a tab displays its corresponding panel.<Tabs />
- the container that houses the tabs. Responsible for handling focus and keyboard navigation between tabs.{{ādemoā: āBasicTabs.jsā}}
import Tabs from '@mui/material/Tabs';
import Tab from '@mui/material/Tab';
@mui/lab
offers utility components that inject props to implement accessible tabs
following WAI-ARIA Authoring Practices:
<TabList />
- the container that houses the tabs. Responsible for handling focus and keyboard navigation between tabs.<TabPanel />
- the card that hosts the content associated with a tab.<TabContext />
- the top-level component that wraps the Tab List and Tab Panel components.{{ādemoā: āLabTabs.jsā}}
Long labels will automatically wrap on tabs. If the label is too long for the tab, it will overflow, and the text will not be visible.
{{ādemoā: āTabsWrappedLabel.jsā}}
{{ādemoā: āColorTabs.jsā}}
A tab can be disabled by setting the disabled
prop.
{{ādemoā: āDisabledTabs.jsā}}
Fixed tabs should be used with a limited number of tabs, and when a consistent placement will aid muscle memory.
The variant="fullWidth"
prop should be used for smaller views.
import * as React from 'react';
import { useTheme } from '@mui/material/styles';
import AppBar from '@mui/material/AppBar';
import Tabs from '@mui/material/Tabs';
import Tab from '@mui/material/Tab';
import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box';
interface TabPanelProps {
children?: React.ReactNode;
dir?: string;
index: number;
value: number;
}
function TabPanel(props: TabPanelProps) {
const { children, value, index, ...other } = props;
return (
<div
role="tabpanel"
hidden={value !== index}
id={`full-width-tabpanel-${index}`}
aria-labelledby={`full-width-tab-${index}`}
{...other}
>
{value === index && (
<Box sx={{ p: 3 }}>
<Typography>{children}</Typography>
</Box>
)}
</div>
);
}
function a11yProps(index: number) {
return {
id: `full-width-tab-${index}`,
'aria-controls': `full-width-tabpanel-${index}`,
};
}
export default function FullWidthTabs() {
const theme = useTheme();
const [value, setValue] = React.useState(0);
const handleChange = (event: React.SyntheticEvent, newValue: number) => {
setValue(newValue);
};
return (
<Box sx={{ bgcolor: 'background.paper', width: 500 }}>
<AppBar position="static">
<Tabs
value={value}
onChange={handleChange}
indicatorColor="secondary"
textColor="inherit"
variant="fullWidth"
aria-label="full width tabs example"
>
<Tab label="Item One" {...a11yProps(0)} />
<Tab label="Item Two" {...a11yProps(1)} />
<Tab label="Item Three" {...a11yProps(2)} />
</Tabs>
</AppBar>
<TabPanel value={value} index={0} dir={theme.direction}>
Item One
</TabPanel>
<TabPanel value={value} index={1} dir={theme.direction}>
Item Two
</TabPanel>
<TabPanel value={value} index={2} dir={theme.direction}>
Item Three
</TabPanel>
</Box>
);
}
The centered
prop should be used for larger views.
import * as React from 'react';
import Box from '@mui/material/Box';
import Tabs from '@mui/material/Tabs';
import Tab from '@mui/material/Tab';
export default function CenteredTabs() {
const [value, setValue] = React.useState(0);
const handleChange = (event: React.SyntheticEvent, newValue: number) => {
setValue(newValue);
};
return (
<Box sx={{ width: '100%', bgcolor: 'background.paper' }}>
<Tabs value={value} onChange={handleChange} centered>
<Tab label="Item One" />
<Tab label="Item Two" />
<Tab label="Item Three" />
</Tabs>
</Box>
);
}
Use the variant="scrollable"
and scrollButtons="auto"
props to display left and right scroll buttons on desktop that are hidden on mobile:
import * as React from 'react';
import Tabs from '@mui/material/Tabs';
import Tab from '@mui/material/Tab';
import Box from '@mui/material/Box';
export default function ScrollableTabsButtonAuto() {
const [value, setValue] = React.useState(0);
const handleChange = (event: React.SyntheticEvent, newValue: number) => {
setValue(newValue);
};
return (
<Box sx={{ maxWidth: { xs: 320, sm: 480 }, bgcolor: 'background.paper' }}>
<Tabs
value={value}
onChange={handleChange}
variant="scrollable"
scrollButtons="auto"
aria-label="scrollable auto tabs example"
>
<Tab label="Item One" />
<Tab label="Item Two" />
<Tab label="Item Three" />
<Tab label="Item Four" />
<Tab label="Item Five" />
<Tab label="Item Six" />
<Tab label="Item Seven" />
</Tabs>
</Box>
);
}
Apply scrollButtons={true}
and the allowScrollButtonsMobile
prop to display the left and right scroll buttons on all viewports:
import * as React from 'react';
import Tabs from '@mui/material/Tabs';
import Tab from '@mui/material/Tab';
import Box from '@mui/material/Box';
export default function ScrollableTabsButtonForce() {
const [value, setValue] = React.useState(0);
const handleChange = (event: React.SyntheticEvent, newValue: number) => {
setValue(newValue);
};
return (
<Box sx={{ maxWidth: { xs: 320, sm: 480 }, bgcolor: 'background.paper' }}>
<Tabs
value={value}
onChange={handleChange}
variant="scrollable"
scrollButtons
allowScrollButtonsMobile
aria-label="scrollable force tabs example"
>
<Tab label="Item One" />
<Tab label="Item Two" />
<Tab label="Item Three" />
<Tab label="Item Four" />
<Tab label="Item Five" />
<Tab label="Item Six" />
<Tab label="Item Seven" />
</Tabs>
</Box>
);
}
If you want to make sure the buttons are always visible, you should customize the opacity.
.MuiTabs-scrollButtons.Mui-disabled {
opacity: 0.3;
}
import * as React from 'react';
import Box from '@mui/material/Box';
import Tabs, { tabsClasses } from '@mui/material/Tabs';
import Tab from '@mui/material/Tab';
export default function ScrollableTabsButtonVisible() {
const [value, setValue] = React.useState(0);
const handleChange = (event: React.SyntheticEvent, newValue: number) => {
setValue(newValue);
};
return (
<Box
sx={{
flexGrow: 1,
maxWidth: { xs: 320, sm: 480 },
bgcolor: 'background.paper',
}}
>
<Tabs
value={value}
onChange={handleChange}
variant="scrollable"
scrollButtons
aria-label="visible arrows tabs example"
sx={{
[`& .${tabsClasses.scrollButtons}`]: {
'&.Mui-disabled': { opacity: 0.3 },
},
}}
>
<Tab label="Item One" />
<Tab label="Item Two" />
<Tab label="Item Three" />
<Tab label="Item Four" />
<Tab label="Item Five" />
<Tab label="Item Six" />
<Tab label="Item Seven" />
</Tabs>
</Box>
);
}
Left and right scroll buttons are never be presented with scrollButtons={false}
.
All scrolling must be initiated through user agent scrolling mechanisms (for example left/right swipe, shift mouse wheel, etc.)
import * as React from 'react';
import Tabs from '@mui/material/Tabs';
import Tab from '@mui/material/Tab';
import Box from '@mui/material/Box';
export default function ScrollableTabsButtonPrevent() {
const [value, setValue] = React.useState(0);
const handleChange = (event: React.SyntheticEvent, newValue: number) => {
setValue(newValue);
};
return (
<Box sx={{ maxWidth: { xs: 320, sm: 480 }, bgcolor: 'background.paper' }}>
<Tabs
value={value}
onChange={handleChange}
variant="scrollable"
scrollButtons={false}
aria-label="scrollable prevent tabs example"
>
<Tab label="Item One" />
<Tab label="Item Two" />
<Tab label="Item Three" />
<Tab label="Item Four" />
<Tab label="Item Five" />
<Tab label="Item Six" />
<Tab label="Item Seven" />
</Tabs>
</Box>
);
}
Here is an example of customizing the component. You can learn more about this in the overrides documentation page.
{{ādemoā: āCustomizedTabs.jsā}}
šØ If you are looking for inspiration, you can check MUI Treasuryās customization examples.
To make vertical tabs instead of default horizontal ones, there is orientation="vertical"
:
import * as React from 'react';
import Tabs from '@mui/material/Tabs';
import Tab from '@mui/material/Tab';
import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box';
interface TabPanelProps {
children?: React.ReactNode;
index: number;
value: number;
}
function TabPanel(props: TabPanelProps) {
const { children, value, index, ...other } = props;
return (
<div
role="tabpanel"
hidden={value !== index}
id={`vertical-tabpanel-${index}`}
aria-labelledby={`vertical-tab-${index}`}
{...other}
>
{value === index && (
<Box sx={{ p: 3 }}>
<Typography>{children}</Typography>
</Box>
)}
</div>
);
}
function a11yProps(index: number) {
return {
id: `vertical-tab-${index}`,
'aria-controls': `vertical-tabpanel-${index}`,
};
}
export default function VerticalTabs() {
const [value, setValue] = React.useState(0);
const handleChange = (event: React.SyntheticEvent, newValue: number) => {
setValue(newValue);
};
return (
<Box
sx={{ flexGrow: 1, bgcolor: 'background.paper', display: 'flex', height: 224 }}
>
<Tabs
orientation="vertical"
variant="scrollable"
value={value}
onChange={handleChange}
aria-label="Vertical tabs example"
sx={{ borderRight: 1, borderColor: 'divider' }}
>
<Tab label="Item One" {...a11yProps(0)} />
<Tab label="Item Two" {...a11yProps(1)} />
<Tab label="Item Three" {...a11yProps(2)} />
<Tab label="Item Four" {...a11yProps(3)} />
<Tab label="Item Five" {...a11yProps(4)} />
<Tab label="Item Six" {...a11yProps(5)} />
<Tab label="Item Seven" {...a11yProps(6)} />
</Tabs>
<TabPanel value={value} index={0}>
Item One
</TabPanel>
<TabPanel value={value} index={1}>
Item Two
</TabPanel>
<TabPanel value={value} index={2}>
Item Three
</TabPanel>
<TabPanel value={value} index={3}>
Item Four
</TabPanel>
<TabPanel value={value} index={4}>
Item Five
</TabPanel>
<TabPanel value={value} index={5}>
Item Six
</TabPanel>
<TabPanel value={value} index={6}>
Item Seven
</TabPanel>
</Box>
);
}
Note that you can restore the scrollbar with visibleScrollbar
.
By default, tabs use a button
element, but you can provide your custom tag or component. Hereās an example of implementing tabbed navigation:
{{ādemoā: āNavTabs.jsā}}
One frequent use case is to perform navigation on the client only, without an HTTP round-trip to the server.
The Tab
component provides the component
prop to handle this use case.
Here is a more detailed guide.
Tab labels may be either all icons or all text.
{{ādemoā: āIconTabs.jsā}}
{{ādemoā: āIconLabelTabs.jsā}}
By default, the icon is positioned at the top
of a tab. Other supported positions are start
, end
, bottom
.
{{ādemoā: āIconPositionTabs.jsā}}
(WAI-ARIA: https://www.w3.org/WAI/ARIA/apg/patterns/tabs/)
The following steps are needed in order to provide necessary information for assistive technologies:
Tabs
via aria-label
or aria-labelledby
.Tab
s need to be connected to their
corresponding [role="tabpanel"]
by setting the correct id
, aria-controls
and aria-labelledby
.An example for the current implementation can be found in the demos on this page. Weāve also published an experimental API in @mui/lab
that does not require
extra work.
The components implement keyboard navigation using the āmanual activationā behavior.
If you want to switch to the āselection automatically follows focusā behavior you have to pass selectionFollowsFocus
to the Tabs
component.
The WAI-ARIA authoring practices have a detailed guide on how to decide when to make selection automatically follow focus.
The following two demos only differ in their keyboard navigation behavior. Focus a tab and navigate with arrow keys to notice the difference, for example Arrow Left.
/* Tabs where selection follows focus */
<Tabs selectionFollowsFocus />
import * as React from 'react';
import Tabs from '@mui/material/Tabs';
import Tab from '@mui/material/Tab';
import Box from '@mui/material/Box';
export default function AccessibleTabs1() {
const [value, setValue] = React.useState(0);
const handleChange = (event: React.SyntheticEvent, newValue: number) => {
setValue(newValue);
};
return (
<Box sx={{ width: '100%' }}>
<Tabs
onChange={handleChange}
value={value}
aria-label="Tabs where selection follows focus"
selectionFollowsFocus
>
<Tab label="Item One" />
<Tab label="Item Two" />
<Tab label="Item Three" />
</Tabs>
</Box>
);
}
/* Tabs where each tab needs to be selected manually */
<Tabs />
import * as React from 'react';
import Tabs from '@mui/material/Tabs';
import Tab from '@mui/material/Tab';
import Box from '@mui/material/Box';
export default function AccessibleTabs2() {
const [value, setValue] = React.useState(0);
const handleChange = (event: React.SyntheticEvent, newValue: number) => {
setValue(newValue);
};
return (
<Box sx={{ width: '100%' }}>
<Tabs
onChange={handleChange}
value={value}
aria-label="Tabs where each tab needs to be selected manually"
>
<Tab label="Item One" />
<Tab label="Item Two" />
<Tab label="Item Three" />
</Tabs>
</Box>
);
}