moduix

Tabs

A set of tab buttons that switch between related panels on the same page.

API Reference

Original primitive API

Behavior, accessibility details, and low-level props are documented by Base UI.

Base UI API

Basic

Review project status, team velocity, workloads and activity highlights in one place.
import {  Tabs,  TabsList,  TabsPanel,  TabsTab,} from "moduix";export function TabsDemo() {  return (    <Tabs defaultValue="overview">      <TabsList>        {items.map((item) => (          <TabsTab key={item.value} value={item.value}>            {item.title}          </TabsTab>        ))}      </TabsList>      {items.map((item) => (        <TabsPanel key={item.value} value={item.value}>          {item.content}        </TabsPanel>      ))}    </Tabs>  );}
const items = [  {    value: "overview",    title: "Overview",    content:      "Review project status, team velocity, workloads and activity highlights in one place.",  },  {    value: "projects",    title: "Projects",    content: "Track active workstreams, owners and milestones across all departments.",  },  {    value: "account",    title: "Account",    content: "Manage personal settings, team settings, notifications and access preferences.",  },];

Full list of component variables available for project-level overrides.

PropertyDefaultDescription
--tabs-bgvar(--color-background)Controls the root background color.
--tabs-border-colorvar(--color-border)Controls the root border color.
--tabs-border-widthvar(--border-width-sm)Controls the root border width.
--tabs-colorvar(--color-foreground)Controls the root text color.
--tabs-focus-ring-colorvar(--color-ring)Controls tab and panel focus ring color.
--tabs-focus-ring-offset0Controls tab focus ring offset.
--tabs-focus-ring-widthvar(--border-width-sm)Controls tab and panel focus ring width.
--tabs-gap0Controls spacing between the tab list and panels.
--tabs-indicator-bgvar(--color-background)Controls the default indicator background.
--tabs-indicator-radiusvar(--radius-sm)Controls the default indicator radius.
--tabs-indicator-size1.75remControls the default indicator thickness.
--tabs-indicator-transitiontranslate 200ms ease, width 200ms easeControls the default indicator movement transition.
--tabs-line-indicator-bgvar(--color-foreground)Controls the line indicator color.
--tabs-line-indicator-radiusvar(--radius-full)Controls the line indicator radius.
--tabs-line-indicator-size2pxControls the line indicator thickness.
--tabs-line-indicator-transitiontranslate 200ms ease, width 200ms easeControls the line indicator movement transition.
--tabs-list-bgvar(--color-muted)Controls the tab list background color.
--tabs-list-border-colorvar(--color-border)Controls the tab list separator color.
--tabs-list-border-widthvar(--tabs-border-width, var(--border-width-sm))Controls the tab list separator width.
--tabs-list-gap0.25remControls spacing between tabs.
--tabs-list-padding0.25remControls the tab list padding.
--tabs-list-padding-x0.25remControls the tab list horizontal padding.
--tabs-list-padding-y0.25remControls the tab list vertical padding.
--tabs-max-widthcalc(100vw - 2rem)Controls the root tabs max width.
--tabs-panel-colorvar(--color-foreground)Controls panel text color.
--tabs-panel-font-sizevar(--text-sm)Controls panel text font size.
--tabs-panel-line-heightvar(--line-height-text-sm)Controls panel text line height.
--tabs-panel-focus-ring-offsetcalc(var(--tabs-focus-ring-width, var(--border-width-sm)) * -1)Controls panel focus ring offset.
--tabs-panel-padding1remControls panel padding.
--tabs-radiusvar(--radius-lg)Controls the root border radius.
--tabs-tab-colorvar(--color-muted-foreground)Controls inactive tab text color.
--tabs-tab-color-activevar(--color-foreground)Controls active tab text color.
--tabs-tab-color-hovervar(--color-foreground)Controls hovered tab text color.
--tabs-tab-content-gap0.5remControls spacing between tab icon and label.
--tabs-tab-disabled-opacityvar(--opacity-disabled)Controls disabled tab opacity.
--tabs-tab-font-sizevar(--text-sm)Controls tab text font size.
--tabs-tab-font-weightvar(--weight-medium)Controls tab text font weight.
--tabs-tab-height2remControls each tab height.
--tabs-tab-icon-size1remControls tab icon size.
--tabs-tab-icon-colorcurrentColorControls tab icon color.
--tabs-tab-line-heightvar(--line-height-text-sm)Controls tab text line height.
--tabs-tab-padding-x0.625remControls each tab horizontal padding.
--tabs-tab-radiusvar(--radius-sm)Controls each tab border radius.
--tabs-tab-transitionvar(--transition-default)Controls tab text color transition.
--tabs-vertical-list-width12remControls the list width in vertical orientation.
--tabs-vertical-min-height14remControls the root min-height in vertical orientation.
--tabs-width32remControls the root tabs width.

Interactive variables scoped for docs preview without changing size scale tokens.

PropertyValueDefaultDescription
--tabs-bgvar(--color-background)Controls root background color.
--tabs-border-colorvar(--color-border)Controls root border color.
--tabs-border-widthvar(--border-width-sm)Controls root border width.
--tabs-focus-ring-colorvar(--color-ring)Controls tab and panel focus ring color.
--tabs-list-bgvar(--color-muted)Controls tab list background color.
--tabs-panel-colorvar(--color-foreground)Controls panel text color.
--tabs-radiusvar(--radius-lg)Controls root border radius.
--tabs-tab-colorvar(--color-muted-foreground)Controls inactive tab text color.
--tabs-tab-color-activevar(--color-foreground)Controls active tab text color.
--tabs-tab-color-hovervar(--color-foreground)Controls hovered tab text color.
--tabs-indicator-bgvar(--color-background)Controls indicator background.

Anatomy

Tabs is composed from a root state machine, one tab list, tab buttons, and matching panels. TabsList renders the moving indicator internally by default, so consumers do not need to place a service Indicator part in the public composition.

Tabs
├─ TabsList
│  ├─ TabsTab[value]
│  │  └─ label or TabsTabContent
│  │     ├─ TabsTabIcon
│  │     └─ TabsTabLabel
│  └─ indicator
└─ TabsPanel[value]
   └─ content
<Tabs defaultValue="overview">
  <TabsList>
    <TabsTab value="overview">Overview</TabsTab>
    <TabsTab value="projects">Projects</TabsTab>
  </TabsList>
  <TabsPanel value="overview">Overview content</TabsPanel>
  <TabsPanel value="projects">Projects content</TabsPanel>
</Tabs>
PartRole
TabsRoot state machine. Controls selected value, orientation, variant, and controlled/uncontrolled state.
TabsListGroups tab buttons and owns keyboard list behavior. It renders the active indicator internally.
TabsTabInteractive tab button. Its value must match one TabsPanel value.
TabsTabContentOptional wrapper for compound tab labels, usually icon plus text.
TabsTabIconOptional icon wrapper. Use it with your application icons or the icons exported by moduix.
TabsTabLabelOptional text wrapper used with TabsTabContent.
TabsPanelContent region displayed when its matching tab is active. Use keepMounted when hidden DOM must remain.
indicatorInternal service slot that follows the active tab. Style it through TabsList classNames.indicator.

Composition

Use Tabs for state props such as defaultValue, value, onValueChange, orientation, and variant. Base UI props pass through to the matching visible parts: TabsList supports activateOnFocus and loopFocus, TabsTab supports disabled, render, and nativeButton, and TabsPanel supports keepMounted.

Visible parts accept className. The internal indicator is not part of the required composition: style it with TabsList classNames.indicator, disable it with withIndicator={false}, or pass non-class Base UI indicator props through TabsList slotProps.indicator.

import { Tabs, TabsList, TabsPanel, TabsTab } from 'moduix';
import styles from './custom-indicator-tabs-demo.module.css';

export function CustomIndicatorTabsDemo() {
  return (
    <Tabs defaultValue="profile" className={styles.root}>
      <TabsList
        className={styles.list}
        classNames={{ indicator: styles.indicator }}
        slotProps={{ indicator: { renderBeforeHydration: true } }}
      >
        <TabsTab value="profile" className={styles.tab}>
          Profile
        </TabsTab>
        <TabsTab value="security" className={styles.tab}>
          Security
        </TabsTab>
      </TabsList>
      <TabsPanel value="profile" className={styles.panel}>
        Profile settings
      </TabsPanel>
      <TabsPanel value="security" className={styles.panel}>
        Security settings
      </TabsPanel>
    </Tabs>
  );
}

Examples

Vertical

Use orientation="vertical" when the tab list should sit next to the active panel.

Review project status, team velocity, workloads and activity highlights in one place.
import {  Tabs,  TabsList,  TabsPanel,  TabsTab,} from "moduix";export function VerticalTabsDemo() {  return (    <Tabs defaultValue="overview" orientation="vertical">      <TabsList>        {items.map((item) => (          <TabsTab key={item.value} value={item.value}>            {item.title}          </TabsTab>        ))}      </TabsList>      {items.map((item) => (        <TabsPanel key={item.value} value={item.value}>          {item.content}        </TabsPanel>      ))}    </Tabs>  );}
const items = [  {    value: "overview",    title: "Overview",    content:      "Review project status, team velocity, workloads and activity highlights in one place.",  },  {    value: "projects",    title: "Projects",    content: "Track active workstreams, owners and milestones across all departments.",  },  {    value: "account",    title: "Account",    content: "Manage personal settings, team settings, notifications and access preferences.",  },];

Activate On Focus

Pass activateOnFocus to TabsList when arrow-key focus should immediately select a tab.

Review project status, team velocity, workloads and activity highlights in one place.
import {  Tabs,  TabsList,  TabsPanel,  TabsTab,} from "moduix";export function ActivateOnFocusTabsDemo() {  return (    <Tabs defaultValue="overview">      <TabsList activateOnFocus>        {items.map((item) => (          <TabsTab key={item.value} value={item.value}>            {item.title}          </TabsTab>        ))}      </TabsList>      {items.map((item) => (        <TabsPanel key={item.value} value={item.value}>          {item.content}        </TabsPanel>      ))}    </Tabs>  );}
const items = [  {    value: "overview",    title: "Overview",    content:      "Review project status, team velocity, workloads and activity highlights in one place.",  },  {    value: "projects",    title: "Projects",    content: "Track active workstreams, owners and milestones across all departments.",  },  {    value: "account",    title: "Account",    content: "Manage personal settings, team settings, notifications and access preferences.",  },];

Line

Use variant="line" for a compact indicator that sits along the active tab edge.

Review project status, team velocity, workloads and activity highlights in one place.
import {  Tabs,  TabsList,  TabsPanel,  TabsTab,} from "moduix";export function LineTabsDemo() {  return (    <Tabs defaultValue="overview" variant="line">      <TabsList>        {items.map((item) => (          <TabsTab key={item.value} value={item.value}>            {item.title}          </TabsTab>        ))}      </TabsList>      {items.map((item) => (        <TabsPanel key={item.value} value={item.value}>          {item.content}        </TabsPanel>      ))}    </Tabs>  );}
const items = [  {    value: "overview",    title: "Overview",    content:      "Review project status, team velocity, workloads and activity highlights in one place.",  },  {    value: "projects",    title: "Projects",    content: "Track active workstreams, owners and milestones across all departments.",  },  {    value: "account",    title: "Account",    content: "Manage personal settings, team settings, notifications and access preferences.",  },];

Without Indicator

Set withIndicator={false} on TabsList when the active tab should be shown only through tab state styles.

Review project status, team velocity, workloads and activity highlights in one place.
import {  Tabs,  TabsList,  TabsPanel,  TabsTab,} from "moduix";export function WithoutIndicatorTabsDemo() {  return (    <Tabs defaultValue="overview">      <TabsList withIndicator={false}>        {items.map((item) => (          <TabsTab key={item.value} value={item.value}>            {item.title}          </TabsTab>        ))}      </TabsList>      {items.map((item) => (        <TabsPanel key={item.value} value={item.value}>          {item.content}        </TabsPanel>      ))}    </Tabs>  );}
const items = [  {    value: "overview",    title: "Overview",    content:      "Review project status, team velocity, workloads and activity highlights in one place.",  },  {    value: "projects",    title: "Projects",    content: "Track active workstreams, owners and milestones across all departments.",  },  {    value: "account",    title: "Account",    content: "Manage personal settings, team settings, notifications and access preferences.",  },];

Controlled

Control the active tab from React state when the selected panel needs to coordinate with other UI.

Track active workstreams, owners and milestones across all departments and align delivery timelines.
import {  Tabs,  TabsList,  TabsPanel,  TabsTab,  type TabsValue,} from "moduix";import { useState } from "react";export function ControlledTabsDemo() {  const [value, setValue] = useState("projects" as TabsValue);  return (    <Tabs value={value} onValueChange={setValue}>      <TabsList>        {items.map((item) => (          <TabsTab key={item.value} value={item.value}>            {item.title}          </TabsTab>        ))}      </TabsList>      {items.map((item) => (        <TabsPanel key={item.value} value={item.value}>          {item.content}        </TabsPanel>      ))}    </Tabs>  );}
const items = [  {    value: "overview",    title: "Overview",    content:      "Review project status, team velocity, workloads and activity highlights in one place.",  },  {    value: "projects",    title: "Projects",    content: "Track active workstreams, owners and milestones across all departments.",  },  {    value: "account",    title: "Account",    content: "Manage personal settings, team settings, notifications and access preferences.",  },];

Use render with nativeButton={false} when tabs should render as links.

Review project status, team velocity, workloads and activity highlights in one place.
import {  Tabs,  TabsList,  TabsPanel,  TabsTab,} from "moduix";export function LinkTabsDemo() {  return (    <Tabs defaultValue="overview">      <TabsList>        {items.map((item) => (          <TabsTab            key={item.value}            value={item.value}            nativeButton={false}            render={<a href={"#" + item.value} />}          >            {item.title}          </TabsTab>        ))}      </TabsList>      {items.map((item) => (        <TabsPanel key={item.value} value={item.value}>          <span id={item.value}>{item.content}</span>        </TabsPanel>      ))}    </Tabs>  );}
const items = [  {    value: "overview",    title: "Overview",    content:      "Review project status, team velocity, workloads and activity highlights in one place.",  },  {    value: "projects",    title: "Projects",    content: "Track active workstreams, owners and milestones across all departments.",  },  {    value: "account",    title: "Account",    content: "Manage personal settings, team settings, notifications and access preferences.",  },];

Custom Icons

Compose tab content with TabsTabContent, TabsTabIcon, and TabsTabLabel to use icons from your application or icon library.

Review project status, team velocity, workloads and activity highlights in one place.
import {  HandshakeIcon,  MapIcon,  PresentIcon,  Tabs,  TabsList,  TabsPanel,  TabsTab,  TabsTabContent,  TabsTabIcon,  TabsTabLabel,} from "moduix";export function IconTabsDemo() {  return (    <Tabs defaultValue="overview">      <TabsList>        <TabsTab value="overview">          <TabsTabContent>            <TabsTabIcon>              <HandshakeIcon />            </TabsTabIcon>            <TabsTabLabel>Overview</TabsTabLabel>          </TabsTabContent>        </TabsTab>        <TabsTab value="projects">          <TabsTabContent>            <TabsTabIcon>              <PresentIcon />            </TabsTabIcon>            <TabsTabLabel>Projects</TabsTabLabel>          </TabsTabContent>        </TabsTab>        <TabsTab value="account">          <TabsTabContent>            <TabsTabIcon>              <MapIcon />            </TabsTabIcon>            <TabsTabLabel>Account</TabsTabLabel>          </TabsTabContent>        </TabsTab>      </TabsList>      {items.map((item) => (        <TabsPanel key={item.value} value={item.value}>          {item.content}        </TabsPanel>      ))}    </Tabs>  );}
const items = [  {    value: "overview",    content:      "Review project status, team velocity, workloads and activity highlights in one place.",  },  {    value: "projects",    content: "Track active workstreams, owners and milestones across all departments.",  },  {    value: "account",    content: "Manage personal settings, team settings, notifications and access preferences.",  },];

Disabled Tab

Disable tabs that are visible but not available yet. Add keepMounted to panels when hidden panel DOM must stay mounted for forms, measurements, or preserved local state.

Review project status, team velocity, workloads and activity highlights in one place.
import {  Tabs,  TabsList,  TabsPanel,  TabsTab,} from "moduix";export function DisabledTabTabsDemo() {  return (    <Tabs defaultValue="overview">      <TabsList>        <TabsTab value="overview">Overview</TabsTab>        <TabsTab value="projects" disabled>          Projects        </TabsTab>        <TabsTab value="account">Account</TabsTab>      </TabsList>      {items.map((item) => (        <TabsPanel key={item.value} value={item.value} keepMounted>          {item.content}        </TabsPanel>      ))}    </Tabs>  );}
const items = [  {    value: "overview",    title: "Overview",    content:      "Review project status, team velocity, workloads and activity highlights in one place.",  },  {    value: "projects",    title: "Projects",    content: "Track active workstreams, owners and milestones across all departments.",  },  {    value: "account",    title: "Account",    content: "Manage personal settings, team settings, notifications and access preferences.",  },];

Custom Styles

Use className, classNames.indicator, and CSS variables to reshape layout and visuals when the default Tabs style is too strong for the target composition.

import {  Tabs,  TabsList,  TabsPanel,  TabsTab,} from "moduix";import styles from "./inline-inputs-tabs-demo.module.css";export function InlineInputsTabsDemo() {  return (    <Tabs defaultValue="name" className={styles.inlineRoot}>      <TabsList        className={styles.inlineList}        classNames={{ indicator: styles.inlineIndicator }}        slotProps={{ indicator: { renderBeforeHydration: true } }}      >        <TabsTab value="name" className={styles.inlineTab}>          Name        </TabsTab>        <TabsTab value="email" className={styles.inlineTab}>          Email        </TabsTab>      </TabsList>      <TabsPanel value="name" className={styles.inlinePanel}>        <input className={styles.inlineInput} placeholder="Full name" aria-label="Full name" />      </TabsPanel>      <TabsPanel value="email" className={styles.inlinePanel}>        <input className={styles.inlineInput} placeholder="Email" aria-label="Email" />      </TabsPanel>    </Tabs>  );}
.inlineRoot {  --tabs-bg: transparent;  --tabs-border-color: transparent;  --tabs-border-width: 0;  --tabs-gap: var(--spacing-4);  --tabs-list-bg: transparent;  --tabs-list-border-width: 0;  --tabs-list-padding: 0;  --tabs-list-padding-x: 0;  --tabs-list-padding-y: 0;  display: flex;  flex-direction: row;  width: min(36rem, calc(100vw - 2rem));  align-items: center;  justify-content: space-between;  gap: var(--tabs-gap);  color: var(--color-foreground);}.inlineList {  position: relative;  display: flex;  align-items: center;  gap: var(--spacing-1);  flex-shrink: 0;}.inlineTab {  height: var(--size-md);  margin: 0;  appearance: none;  border: 0;  border-radius: var(--radius-sm);  padding-inline: var(--spacing-3);  background: transparent;  color: var(--color-muted-foreground);  font: inherit;  font-size: var(--text-sm);  line-height: var(--line-height-text-sm);  cursor: pointer;  &[data-active] {    color: var(--color-foreground);  }}.inlineIndicator {  position: absolute;  top: auto;  left: 0;  bottom: 0;  width: var(--active-tab-width);  height: var(--border-width-md);  border-radius: var(--radius-full);  background: var(--color-foreground);  translate: var(--active-tab-left) 0;  transition:    translate 200ms ease,    width 200ms ease;}.inlinePanel {  flex: 1;  min-width: 0;  &[hidden]:not([hidden='until-found']) {    display: none;  }}.inlineInput {  box-sizing: border-box;  width: 100%;  height: var(--size-md);  border: var(--border-width-sm) solid var(--color-border);  border-radius: var(--radius-sm);  padding-inline: var(--spacing-2);  background: var(--color-background);  color: var(--color-foreground);  font: inherit;  font-size: var(--text-sm);}

On this page