Tabs
Tabs organize content across different screens, data sets, and other interactions.
Simple Example
Tabs can be created by using the following components and hooks:
useTabs
- Manages the state for the active tab content and provides additional accessibility props required for a tab widgetTabList
- Contains all the Tab components- `Tab - A button that should show specific content while active
SliderContainer
/Slide
- Used to animate the active tab content into view
"use client";
import { Tab } from "@react-md/core/tabs/Tab";
import { TabList } from "@react-md/core/tabs/TabList";
import { useTabs } from "@react-md/core/tabs/useTabs";
import { Slide } from "@react-md/core/transition/Slide";
import { SlideContainer } from "@react-md/core/transition/SlideContainer";
import { type ReactElement } from "react";
export default function SimpleExample(): ReactElement {
const { getTabProps, getTabListProps, getTabPanelProps, getTabPanelsProps } =
useTabs();
return (
<>
<TabList {...getTabListProps()}>
<Tab {...getTabProps(0)}>Tab 1</Tab>
<Tab {...getTabProps(1)}>Tab 2</Tab>
<Tab {...getTabProps(2)}>Tab 3</Tab>
</TabList>
<SlideContainer {...getTabPanelsProps()}>
<Slide {...getTabPanelProps(0)}>Tab 1 Content</Slide>
<Slide {...getTabPanelProps(1)}>Tab 2 Content</Slide>
<Slide {...getTabPanelProps(2)}>Tab 3 Content</Slide>
</SlideContainer>
</>
);
}
With Icons
The Tab component can also render an icon to the left, right, above, or below
the content by using the icon
, iconAfter
, and stacked
props.
"use client";
import { Tab } from "@react-md/core/tabs/Tab";
import { TabList } from "@react-md/core/tabs/TabList";
import { useTabs } from "@react-md/core/tabs/useTabs";
import FavoriteIcon from "@react-md/material-icons/FavoriteIcon";
import SocialDistanceOutlinedIcon from "@react-md/material-icons/SocialDistanceOutlinedIcon";
import { type ReactElement } from "react";
export default function WithIconsExample(): ReactElement {
const { getTabListProps, getTabProps } = useTabs();
return (
<>
<TabList {...getTabListProps()}>
<Tab icon={<FavoriteIcon />} {...getTabProps(0)}>
Tab 1
</Tab>
<Tab
icon={<SocialDistanceOutlinedIcon />}
iconAfter
{...getTabProps(1)}
>
Tab 2
</Tab>
</TabList>
<TabList {...getTabListProps()}>
<Tab icon={<FavoriteIcon />} stacked {...getTabProps(0)}>
Tab 1
</Tab>
<Tab
icon={<SocialDistanceOutlinedIcon />}
iconAfter
stacked
{...getTabProps(1)}
>
Tab 2
</Tab>
</TabList>
</>
);
}
Controlling the Active Tab
If the active tab index needs to be controlled externally, the activeTab
and
setActiveTab
returned values can be used.
The current activeIndex is 0
"use client";
import { AppBar } from "@react-md/core/app-bar/AppBar";
import { Button } from "@react-md/core/button/Button";
import { Tab } from "@react-md/core/tabs/Tab";
import { TabList } from "@react-md/core/tabs/TabList";
import { useTabs } from "@react-md/core/tabs/useTabs";
import { Typography } from "@react-md/core/typography/Typography";
import { type ReactElement } from "react";
export default function ControllingTheActiveTabExample(): ReactElement {
const { getTabProps, getTabListProps, activeTab, setActiveTab } = useTabs();
return (
<>
<AppBar height="auto" theme="surface">
<TabList {...getTabListProps()}>
{Array.from({ length: 4 }, (_, i) => (
<Tab key={i} {...getTabProps(i)}>
{`Tab ${i + 1}`}
</Tab>
))}
</TabList>
</AppBar>
<Typography>{`The current activeIndex is ${activeTab}`}</Typography>
<Button
onClick={() => {
setActiveTab(0);
}}
>
Set 0
</Button>
<Button
onClick={() => {
setActiveTab(1);
}}
>
Set 1
</Button>
</>
);
}
Custom Active Tab Index
For more complex flows, the activeTab
can be controlled by providing the
activeTab
and setActiveTab
options to the useTabs
hook.
The current activeIndex is 0
"use client";
import { AppBar } from "@react-md/core/app-bar/AppBar";
import { Button } from "@react-md/core/button/Button";
import { Tab } from "@react-md/core/tabs/Tab";
import { TabList } from "@react-md/core/tabs/TabList";
import { useTabs } from "@react-md/core/tabs/useTabs";
import { Typography } from "@react-md/core/typography/Typography";
import { type ReactElement, useState } from "react";
export default function CustomActiveTabIndexExample(): ReactElement {
const [activeTab, setActiveTab] = useState(0);
const { getTabProps, getTabListProps } = useTabs({
activeTab,
setActiveTab,
});
return (
<>
<AppBar height="auto" theme="surface">
<TabList {...getTabListProps()}>
{Array.from({ length: 4 }, (_, i) => (
<Tab key={i} {...getTabProps(i)}>
{`Tab ${i + 1}`}
</Tab>
))}
</TabList>
</AppBar>
<Typography>{`The current activeIndex is ${activeTab}`}</Typography>
<Button
onClick={() => {
setActiveTab(0);
}}
>
Set 0
</Button>
<Button
onClick={() => {
setActiveTab(1);
}}
>
Set 1
</Button>
</>
);
}
Custom Active Tab
If the active tab should be derived from a string value instead of an index, an ordered list of available tab values need to be provided to the hook so the keyboard movement behavior can work correctly.
The current activeTab is tab-1
"use client";
import { AppBar } from "@react-md/core/app-bar/AppBar";
import { Button } from "@react-md/core/button/Button";
import { Tab } from "@react-md/core/tabs/Tab";
import { TabList } from "@react-md/core/tabs/TabList";
import { useTabs } from "@react-md/core/tabs/useTabs";
import { Typography } from "@react-md/core/typography/Typography";
import { type ReactElement, useState } from "react";
const tabs = ["tab-1", "tab-2", "tab-3", "tab-4"];
export default function CustomActiveTabExample(): ReactElement {
const [activeTab, setActiveTab] = useState(tabs[0]);
const { getTabProps, getTabListProps } = useTabs({
tabs,
activeTab,
setActiveTab,
});
return (
<>
<AppBar height="auto" theme="surface">
<TabList {...getTabListProps()}>
{tabs.map((tab, i) => (
<Tab key={i} {...getTabProps(tab)}>
{`Tab ${i + 1}`}
</Tab>
))}
</TabList>
</AppBar>
<Typography>{`The current activeTab is ${activeTab}`}</Typography>
<Button
onClick={() => {
setActiveTab("tab-1");
}}
>
Set 1
</Button>
<Button
onClick={() => {
setActiveTab("tab-2");
}}
>
Set 2
</Button>
</>
);
}
Link Tab Example
Tabs can also be rendered as links by setting the as
prop to "a"
or a custom
link component.
This example shows how the active tab could be derived from the current
window.location.hash
, but most real world examples would be based on
pathname
or search parameters.
"use client";
import { Tab } from "@react-md/core/tabs/Tab";
import { TabList } from "@react-md/core/tabs/TabList";
import { useTabs } from "@react-md/core/tabs/useTabs";
import { type ReactElement, useEffect, useState } from "react";
export default function LinkTabExample(): ReactElement {
const activeTab = useActiveTab();
const { getTabListProps, getTabProps } = useTabs({
tabs,
activeTab,
setActiveTab: noop,
});
return (
<>
<TabList {...getTabListProps()}>
{tabs.map((tab) => (
<Tab key={tab} {...getTabProps(tab)} as="a" href={`#${tab}`}>
{tab}
</Tab>
))}
</TabList>
</>
);
}
const tabs = ["tab-1", "tab-2", "tab-3"] as const;
type ValidTab = (typeof tabs)[number];
const noop = (): void => {
// do nothing
};
function isValidTab(hash: string): hash is ValidTab {
return tabs.includes(hash as ValidTab);
}
function useActiveTab(): ValidTab {
const [activeTab, setActiveTab] = useState<ValidTab>(tabs[0]);
useEffect(() => {
const updateHash = (): void => {
const hash = decodeURIComponent(window.location.hash.substring(1));
const nextActiveTab = isValidTab(hash) ? hash : tabs[0];
setActiveTab(nextActiveTab);
};
updateHash();
window.addEventListener("hashchange", updateHash);
return () => {
window.removeEventListener("hashchange", updateHash);
};
}, []);
return activeTab;
}
Vertical Tabs
Tabs can be rendered vertically instead of horizontally by enabling the
vertical
prop on the TabList
component.
Disable Active Tab Transition
The active tab indicator transition can be disabled by setting
disableTransition
to true
on the TabList
component and enabling the
activeIndicator
prop on each Tab.
All tab transitions (tab indicator and tab panel) can be disabled by
passing disableTransition: true
to the useTabs
hook.
"use client";
import { Tab } from "@react-md/core/tabs/Tab";
import { TabList } from "@react-md/core/tabs/TabList";
import { useTabs } from "@react-md/core/tabs/useTabs";
import { type ReactElement } from "react";
function DisableActiveTabTransitionExample(): ReactElement {
const { getTabProps, getTabListProps } = useTabs({ disableTransition: true });
return (
<div>
<TabList {...getTabListProps()}>
<Tab {...getTabProps(0)}>Tab 1</Tab>
<Tab {...getTabProps(1)}>Tab 2</Tab>
<Tab {...getTabProps(2)}>Tab 3</Tab>
</TabList>
</div>
);
}
// This behaves the same as the component above but does not disable the
// transitions for the tab panels (if they were defined). Try setting `VERBOSE`
// to `true`
function DisableActiveTabTransitionExample2(): ReactElement {
const { getTabProps, getTabListProps } = useTabs();
return (
<div>
<TabList {...getTabListProps()} disableTransition>
<Tab {...getTabProps(0)} activeIndicator>
Tab 1
</Tab>
<Tab {...getTabProps(1)} activeIndicator>
Tab 2
</Tab>
<Tab {...getTabProps(2)} activeIndicator>
Tab 3
</Tab>
</TabList>
</div>
);
}
const VERBOSE = false;
export default VERBOSE
? DisableActiveTabTransitionExample
: DisableActiveTabTransitionExample2;
Disable Vertical Active Tab Transition
If the tabs are rendered vertically, the verticalActiveIndicator
prop must
also be enabled on each Tab for the correct styles to apply.
"use client";
import { Tab } from "@react-md/core/tabs/Tab";
import { TabList } from "@react-md/core/tabs/TabList";
import { useTabs } from "@react-md/core/tabs/useTabs";
import { type ReactElement } from "react";
function DisableVerticalActiveTabTransitionExample(): ReactElement {
const { getTabProps, getTabListProps } = useTabs();
return (
<div>
<TabList {...getTabListProps()} vertical disableTransition>
<Tab {...getTabProps(0)} activeIndicator verticalActiveIndicator>
Tab 1
</Tab>
<Tab {...getTabProps(1)} activeIndicator verticalActiveIndicator>
Tab 2
</Tab>
<Tab {...getTabProps(2)} activeIndicator verticalActiveIndicator>
Tab 3
</Tab>
</TabList>
</div>
);
}
function DisableVerticalActiveTabTransitionExample2(): ReactElement {
const { getTabProps, getTabListProps } = useTabs({
vertical: true,
disableTransition: true,
});
return (
<div>
<TabList {...getTabListProps()}>
<Tab {...getTabProps(0)}>Tab 1</Tab>
<Tab {...getTabProps(1)}>Tab 2</Tab>
<Tab {...getTabProps(2)}>Tab 3</Tab>
</TabList>
</div>
);
}
const VERBOSE = true;
export default VERBOSE
? DisableVerticalActiveTabTransitionExample
: DisableVerticalActiveTabTransitionExample2;
Disable Tab Panel Transition
If the active tab panel should no longer animate while becoming active,
the Slide
could be updated to include timeout={0}
. An alternative would be
to use SimpleTabPanels
and SimpleTabPanel
instead of SlideContainer
and
Slide
. These two components are just simple wrappers around a <div>
that
only apply the required props and ref
.
"use client";
import { SimpleTabPanel } from "@react-md/core/tabs/SimpleTabPanel";
import { SimpleTabPanels } from "@react-md/core/tabs/SimpleTabPanels";
import { Tab } from "@react-md/core/tabs/Tab";
import { TabList } from "@react-md/core/tabs/TabList";
import { useTabs } from "@react-md/core/tabs/useTabs";
import { type ReactElement } from "react";
export default function DisableTabPanelTransitionExample(): ReactElement {
const { getTabProps, getTabListProps, getTabPanelProps, getTabPanelsProps } =
useTabs({ disableTransition: true });
return (
<>
<TabList {...getTabListProps()}>
<Tab {...getTabProps(0)}>Tab 1</Tab>
<Tab {...getTabProps(1)}>Tab 2</Tab>
<Tab {...getTabProps(2)}>Tab 3</Tab>
</TabList>
<SimpleTabPanels {...getTabPanelsProps()}>
<SimpleTabPanel {...getTabPanelProps(0)}>Tab 1 Content</SimpleTabPanel>
<SimpleTabPanel {...getTabPanelProps(1)}>Tab 2 Content</SimpleTabPanel>
<SimpleTabPanel {...getTabPanelProps(2)}>Tab 3 Content</SimpleTabPanel>
</SimpleTabPanels>
</>
);
}
Scrollable Tabs
When there are overflow issues due to rendering a lot of tabs, the overflown
tabs will be hidden by default. The user can scroll the hidden tabs into view
by hovering the TabList
and either:
Shift + Mouse Wheel
on Windows- Two-finger scroll on Mac
- Touch scroll on touch devices
- Keyboard behavior
Since most users might not know how to scroll horizontally, scroll buttons can be enabled instead that will scroll 1/10th the distance on each click.
"use client";
import { Tab } from "@react-md/core/tabs/Tab";
import { TabList } from "@react-md/core/tabs/TabList";
import { useTabs } from "@react-md/core/tabs/useTabs";
import { type ReactElement } from "react";
export default function ScrollableTabsExample(): ReactElement {
const { getTabProps, getTabListProps } = useTabs();
return (
<>
<TabList {...getTabListProps()} scrollButtons>
{tabs.map((name, i) => (
<Tab key={name} {...getTabProps(i)}>
{name}
</Tab>
))}
</TabList>
</>
);
}
const tabs = Array.from({ length: 20 }, (_, i) => `Tab ${i + 1}`);
Scrollable Tabs with Scrollbar
A scrollbar could also be shown with or without the scroll buttons by enabling
the scrollbar
prop.
"use client";
import { Tab } from "@react-md/core/tabs/Tab";
import { TabList } from "@react-md/core/tabs/TabList";
import { useTabs } from "@react-md/core/tabs/useTabs";
import { type ReactElement } from "react";
export default function ScrollableTabsWithScrollbarExample(): ReactElement {
const { getTabProps, getTabListProps } = useTabs();
return (
<>
<TabList {...getTabListProps()} scrollButtons scrollbar>
{tabs.map((name, i) => (
<Tab key={name} {...getTabProps(i)}>
{name}
</Tab>
))}
</TabList>
</>
);
}
const tabs = Array.from({ length: 20 }, (_, i) => `Tab ${i + 1}`);
Vertical Scrollable Tabs
The scroll buttons and scrollbar are available for vertical tabs as well.
Custom Scroll Behavior
The scroll distance can be configured by providing the getScrollToOptions
to
the TabList
component.
"use client";
import { Tab } from "@react-md/core/tabs/Tab";
// import { getTabListScrollToOptions } from "@react-md/core/tabs/getTabListScrollToOptions";
import { TabList } from "@react-md/core/tabs/TabList";
import { useTabs } from "@react-md/core/tabs/useTabs";
import { type ReactElement } from "react";
export default function CustomScrollBehaviorExample(): ReactElement {
const { getTabProps, getTabListProps } = useTabs({ disableTransition });
return (
<>
<TabList
{...getTabListProps()}
scrollButtons
getScrollToOptions={(options) => {
// the default implementation
// return getTabListScrollToOptions(options);
// or define custom behavior
// Disclaimer: this is the default implementation
const { isRTL, animate, vertical, increment, container } = options;
const { scrollLeft, scrollTop, scrollWidth, scrollHeight } =
container;
const currentScroll = vertical ? scrollTop : scrollLeft;
const scrollDistance = vertical ? scrollHeight : scrollWidth;
const amount = (scrollDistance / 10) * (increment ? 1 : -1);
const distance =
currentScroll + amount * (vertical || !isRTL ? 1 : -1);
return {
left: vertical ? undefined : distance,
top: vertical ? distance : undefined,
behavior: animate ? "smooth" : "instant",
};
}}
>
{tabs.map((name, i) => (
<Tab key={name} {...getTabProps(i)}>
{name}
</Tab>
))}
</TabList>
</>
);
}
const tabs = Array.from({ length: 20 }, (_, i) => `Tab ${i + 1}`);
const disableTransition = true;
Static Panel Height
When the tab panel content height is unknown, it can cause the entire layout to shift as different tab panels become active. There are a few ways to handle it:
- Find the max tab panel height and set the height to that value
- Set a static height through CSS on the tab panels container component
- Set a max height through CSS on the tab panels container component
This example will showcase how to set a static height through CSS through
either the SlideContainer
or by setting it on each Slide
.
When the height
is set on the SlideContainer
, it will gain the
scrollbar causing it to dynamically appear when large content becomes
visible. This will cause a slight layout shift when it disappears but it
allows all slides to maintain the same padding on the container element.
When the height
is set on the Slide
, overflow: auto
must also be set so
the scrollbar only appears on that single Slide
.
Use Max Tab Panel Height
This example will use the useMaxTabPanelHeight
hook provided through
react-md
to calculate the max tab panel height and set the height to that
value.
This will not work if conditionally rendering the active tab panel
by enabling the temporary
prop on the Slide
or manually.
"use client";
import { Tab } from "@react-md/core/tabs/Tab";
import { TabList } from "@react-md/core/tabs/TabList";
import { useMaxTabPanelHeight } from "@react-md/core/tabs/useMaxTabPanelHeight";
import { useTabs } from "@react-md/core/tabs/useTabs";
import { Slide } from "@react-md/core/transition/Slide";
import { SlideContainer } from "@react-md/core/transition/SlideContainer";
import { Typography } from "@react-md/core/typography/Typography";
import { type ReactElement } from "react";
export default function MaxtabPanelHeightExample(): ReactElement {
const { getTabProps, getTabListProps, getTabPanelProps, getTabPanelsProps } =
useTabs();
const { getMaxTabPanelHeightProps } = useMaxTabPanelHeight({
getTabPanelsProps,
// optional default height
defaultHeight: "30rem",
// if you need to merge a ref, can provide it here
// ref,
// if you need custom inline style, can provide it here or as the
// `getMaxTabPanelHeightProps` argument
// style,
});
return (
<>
<TabList {...getTabListProps()}>
<Tab {...getTabProps(0)}>Tab 1</Tab>
<Tab {...getTabProps(1)}>Tab 2</Tab>
<Tab {...getTabProps(2)}>Tab 3</Tab>
</TabList>
<SlideContainer {...getMaxTabPanelHeightProps()}>
<Slide {...getTabPanelProps(0)}>
<Tab1Content />
</Slide>
<Slide {...getTabPanelProps(1)}>
<Tab2Content />
</Slide>
<Slide {...getTabPanelProps(2)}>
<Tab3Content />
</Slide>
</SlideContainer>
</>
);
}
function Tab1Content(): ReactElement {
return (
<>
<Typography>
Duis tincidunt justo ut magna ullamcorper euismod. Aliquam ultricies,
libero a mollis convallis, neque libero rutrum dolor, at blandit justo
turpis at ex. Pellentesque ullamcorper urna in tempor feugiat. Praesent
eget ligula eget tortor mattis suscipit. Aenean volutpat nisi ac
vestibulum congue. Nulla in tellus quis mauris facilisis tempor.
Pellentesque malesuada at tellus sed bibendum. Donec ultricies vitae
dolor a sodales. Sed non justo ac nibh euismod pulvinar. Duis vel risus
in risus luctus pulvinar et nec mauris. Aliquam blandit tellus nunc, sit
amet feugiat diam facilisis vitae. Aliquam nisi elit, scelerisque et
luctus a, mollis eu ex. Pellentesque eget fringilla metus. Ut molestie
commodo odio finibus consectetur.
</Typography>
<Typography>
Pellentesque molestie ipsum ac felis bibendum vehicula. Aliquam accumsan
augue ac sapien iaculis fringilla. Sed feugiat est vitae ornare
consectetur. Nulla lacinia quam nisl, ut mollis lacus placerat at.
Vivamus non consectetur sapien. Ut ac mollis ante, id ultricies mi.
Mauris tincidunt at erat sed vestibulum. Curabitur eget tellus venenatis
sem tincidunt molestie. Morbi tellus sem, accumsan non vestibulum
fermentum, tristique ac magna. Curabitur congue consectetur nibh quis
placerat. Integer ultrices orci nisi, vel ornare mauris fringilla at.
</Typography>
</>
);
}
function Tab2Content(): ReactElement {
return (
<>
<Typography>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec feugiat
diam et mauris dapibus bibendum. Praesent vehicula maximus egestas.
Mauris lacinia lectus elit, quis placerat massa egestas sit amet. Sed
massa ex, commodo sed orci nec, accumsan sagittis sem. Curabitur
malesuada urna sit amet leo vehicula lobortis. Pellentesque habitant
morbi tristique senectus et netus et malesuada fames ac turpis egestas.
Integer fringilla tincidunt nunc, in efficitur nibh placerat quis.
Vivamus a est quis nunc vestibulum facilisis. Ut ut massa a ante
ultricies imperdiet. Orci varius natoque penatibus et magnis dis
parturient montes, nascetur ridiculus mus. Ut viverra volutpat ex, eu
scelerisque tortor interdum non. Ut cursus mi id turpis tristique, sed
fringilla urna fermentum. Suspendisse potenti. Suspendisse hendrerit
scelerisque porttitor. Duis nisi dolor, ultrices quis fermentum ac,
interdum eu nisl.
</Typography>
<Typography>
Donec nec ultricies tellus. Suspendisse potenti. Cras condimentum, arcu
tempus ornare aliquet, purus elit bibendum justo, eu pretium mauris leo
ac ligula. Sed imperdiet odio in vulputate vestibulum. Integer vitae
commodo elit. Phasellus gravida eros at dolor varius, in tempor metus
tincidunt. Nulla dictum nec mi in dignissim. Duis non mauris a turpis
posuere tempor ut vel eros. Donec volutpat velit sapien, et varius velit
viverra sit amet. Aenean id magna sit amet velit rutrum porta in a
tortor. Integer vel feugiat tortor. Nullam posuere, nulla ut viverra
dignissim, eros quam mattis erat, eget pretium felis erat non mi. Proin
varius est id pretium volutpat. Aliquam erat volutpat.
</Typography>
<Typography>
Mauris porta mauris et feugiat blandit. Sed placerat non mauris at
ultrices. Fusce malesuada sem ut nulla malesuada aliquam. Pellentesque
fermentum aliquam lorem, at semper ex facilisis ac. Fusce laoreet odio
vitae sem dictum, at sagittis erat imperdiet. Nullam lacinia tortor at
diam tempus malesuada. Aliquam vitae euismod mi. Integer ac enim commodo
felis sollicitudin condimentum id at ex. Mauris lobortis est nunc, et
scelerisque eros malesuada vel.
</Typography>
</>
);
}
function Tab3Content(): ReactElement {
return (
<>
<Typography>
Sed ultricies nibh ut leo dapibus, eget volutpat lectus auctor. Etiam
egestas urna nec neque posuere, eget feugiat lacus aliquet. Aliquam nunc
nulla, faucibus at libero et, dapibus vulputate nisi. Cras sit amet
sagittis tortor. Donec nec velit nulla. Mauris dictum vel ipsum quis
bibendum. Aenean elementum nisi urna, vel finibus lacus iaculis ut.
</Typography>
</>
);
}
Custom Tab Panels
The SlideContainer
and Slide
components are not required for rendering the
different tab panels, but it will make it easier to use since most of the
required functionality has already been defined.
This example showcases how to create custom tab panels without the SlideContainer
and Slide
components.
This is pretty much the source code for the SimpleTabPanels
and
SimpleTabPanel
.
"use client";
import { Tab } from "@react-md/core/tabs/Tab";
import { TabList } from "@react-md/core/tabs/TabList";
import {
type ProvidedTabPanelProps,
type ProvidedTabPanelsProps,
useTabs,
} from "@react-md/core/tabs/useTabs";
import { DISPLAY_NONE_CLASS } from "@react-md/core/utils/isElementVisible";
import { cnb } from "cnbuilder";
import { type ReactElement, type ReactNode, forwardRef } from "react";
export default function CustomTabPanelsExample(): ReactElement {
const { getTabProps, getTabListProps, getTabPanelProps, getTabPanelsProps } =
useTabs();
return (
<>
<TabList {...getTabListProps()}>
<Tab {...getTabProps(0)}>Tab 1</Tab>
<Tab {...getTabProps(1)}>Tab 2</Tab>
<Tab {...getTabProps(2)}>Tab 3</Tab>
</TabList>
<TabPanels {...getTabPanelsProps()}>
<TabPanel {...getTabPanelProps(0)}>Tab 1 Content</TabPanel>
<TabPanel {...getTabPanelProps(1)}>Tab 2 Content</TabPanel>
<TabPanel {...getTabPanelProps(2)}>Tab 3 Content</TabPanel>
</TabPanels>
</>
);
}
interface TabPanelsProps
extends Omit<ProvidedTabPanelsProps<HTMLDivElement>, "ref"> {
children: ReactNode;
}
// must forward ref so that switching tabs scrolls to the top
const TabPanels = forwardRef<HTMLDivElement, TabPanelsProps>(
function TabPanels(props, ref) {
const { children } = props;
return <div ref={ref}>{children}</div>;
},
);
interface TabPanelProps extends ProvidedTabPanelProps {
children: ReactNode;
}
function TabPanel(props: TabPanelProps): ReactElement {
const { active, children, ...remaining } = props;
return (
<div {...remaining} className={cnb(!active && DISPLAY_NONE_CLASS)}>
{children}
</div>
);
}
Accessibility
The tab components and hooks implement the tabs pattern.
Keyboard Movement
- Using Tab and Shift + Tab will only focus the active tab
- Once a tab has been focused, ArrowLeft and ArrowRight can be used to
focus another tab
- Use ArrowUp and ArrowDown for vertical tabs
- Pressing Enter or Space will select the focused tab
- Home - Move focus to the first tab
- End - Move focus to the last tab
Activation Mode
When navigating between tabs with a keyboard, tabs must be activated manually by
pressing Space or Enter. An alternative approach is to automatically select
a tab once focused with a keyboard by setting activationMode="automatic"
.
"use client";
import { Tab } from "@react-md/core/tabs/Tab";
import { TabList } from "@react-md/core/tabs/TabList";
import { useTabs } from "@react-md/core/tabs/useTabs";
import { type ReactElement } from "react";
export default function ActivationModeExample(): ReactElement {
const { getTabProps, getTabListProps } = useTabs();
return (
<TabList
{...getTabListProps()}
activationMode="automatic"
// it's recommended to disable the transition for the active indicator and
// enable active indicators on each tab when tabs are selected immediately
disableTransition
>
<Tab {...getTabProps(0)} activeIndicator>
Tab 1
</Tab>
<Tab {...getTabProps(1)} activeIndicator>
Tab 2
</Tab>
<Tab {...getTabProps(2)} activeIndicator>
Tab 3
</Tab>
</TabList>
);
}