Skip to main content
react-md

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:

Tab 1 Content
Tab 2 Content
Tab 3 Content
"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>
    </>
  );
}

Press Enter to start editing.

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>
    </>
  );
}

Press Enter to start editing.

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>
    </>
  );
}

Press Enter to start editing.

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>
    </>
  );
}

Press Enter to start editing.

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>
    </>
  );
}

Press Enter to start editing.

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;
}

Press Enter to start editing.

Vertical Tabs

Tabs can be rendered vertically instead of horizontally by enabling the vertical prop on the TabList component.

Tab 1 Content
Tab 2 Content
Tab 3 Content
"use client";

import { Box } from "@react-md/core/box/Box";
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";

import styles from "./VerticalTabsExample.module.scss";

export default function VerticalTabsExample(): ReactElement {
  const { getTabProps, getTabListProps, getTabPanelProps, getTabPanelsProps } =
    useTabs();

  return (
    <Box grid fullWidth disablePadding className={styles.container}>
      <TabList {...getTabListProps()} vertical>
        <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>
    </Box>
  );
}

Press Enter to start editing.

.container {
  grid-template-columns: min-content 1fr;
}

Press Enter to start editing.

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;

Press Enter to start editing.

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;

Press Enter to start editing.

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.

Tab 1 Content
Tab 2 Content
Tab 3 Content
"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>
    </>
  );
}

Press Enter to start editing.

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:

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}`);

Press Enter to start editing.

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}`);

Press Enter to start editing.

Vertical Scrollable Tabs

The scroll buttons and scrollbar are available for vertical tabs as well.

"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";

import styles from "./VerticalScrollableTabsExample.module.scss";

export default function VerticalScrollableTabsExample(): ReactElement {
  const { getTabProps, getTabListProps } = useTabs({ vertical: true });

  const scrollbar = false;

  return (
    <TabList
      {...getTabListProps()}
      scrollbar={scrollbar}
      scrollButtons
      className={styles.container}
    >
      {tabs.map((name, i) => (
        <Tab key={name} {...getTabProps(i)}>
          {name}
        </Tab>
      ))}
    </TabList>
  );
}

const tabs = Array.from({ length: 20 }, (_, i) => `Tab ${i + 1}`);

Press Enter to start editing.

.container {
  max-height: 20rem;
  max-width: 8rem;
}

Press Enter to start editing.

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;

Press Enter to start editing.

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:

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.

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.

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.

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.

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.

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.

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.

"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 { Typography } from "@react-md/core/typography/Typography";
import { cnb } from "cnbuilder";
import { type ReactElement } from "react";

import styles from "./StaticPanelHeightExample.module.scss";

export default function StaticPanelHeightExample(): ReactElement {
  const { getTabProps, getTabListProps, getTabPanelProps, getTabPanelsProps } =
    useTabs();

  const STYLE_CONTAINER = true;
  const containerClassName = cnb(STYLE_CONTAINER && styles.container);
  const slideClassName = cnb(!STYLE_CONTAINER && styles.slide);
  return (
    <>
      <TabList {...getTabListProps()}>
        <Tab {...getTabProps(0)}>Tab 1</Tab>
        <Tab {...getTabProps(1)}>Tab 2</Tab>
        <Tab {...getTabProps(2)}>Tab 3</Tab>
      </TabList>
      <SlideContainer {...getTabPanelsProps()} className={containerClassName}>
        <Slide {...getTabPanelProps(0)} className={slideClassName}>
          <Tab1Content />
        </Slide>
        <Slide {...getTabPanelProps(1)} className={slideClassName}>
          <Tab2Content />
        </Slide>
        <Slide {...getTabPanelProps(2)} className={slideClassName}>
          <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>
    </>
  );
}

Press Enter to start editing.

.container {
  height: 35rem;
}

.slide {
  height: 35rem;
  overflow: auto;
}

Press Enter to start editing.

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.

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.

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.

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.

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.

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.

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.

"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>
    </>
  );
}

Press Enter to start editing.

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.

Tab 1 Content
Tab 2 Content
Tab 3 Content
"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>
  );
}

Press Enter to start editing.

Accessibility

The tab components and hooks implement the tabs pattern.

Keyboard Movement

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>
  );
}

Press Enter to start editing.