# Tabs (/docs/tabs)





## API Reference [#api-reference]

<BaseUIReference href="https://base-ui.com/react/components/tabs" />

## Basic [#basic]

<Preview cssProperties="tabsPlaygroundCssProperties">
  <TabsExample />

  <Preview.Code>
    {`
          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>
            );
          }
        `}
  </Preview.Code>

  <Preview.Data>
    {`
          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.",
            },
          ];
        `}
  </Preview.Data>

  <Preview.CSSProperties>
    {(context) => <TabsCssPropertiesPanel {...context} />}
  </Preview.CSSProperties>

  <Preview.CSSPlayground>
    {(context) => <TabsCssPlaygroundPanel {...context} />}
  </Preview.CSSPlayground>
</Preview>

## Anatomy [#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.

```text
Tabs
├─ TabsList
│  ├─ TabsTab[value]
│  │  └─ label or TabsTabContent
│  │     ├─ TabsTabIcon
│  │     └─ TabsTabLabel
│  └─ indicator
└─ TabsPanel[value]
   └─ content
```

```tsx
<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>
```

| Part             | Role                                                                                                     |
| ---------------- | -------------------------------------------------------------------------------------------------------- |
| `Tabs`           | Root state machine. Controls selected value, orientation, variant, and controlled/uncontrolled state.    |
| `TabsList`       | Groups tab buttons and owns keyboard list behavior. It renders the active indicator internally.          |
| `TabsTab`        | Interactive tab button. Its `value` must match one `TabsPanel` value.                                    |
| `TabsTabContent` | Optional wrapper for compound tab labels, usually icon plus text.                                        |
| `TabsTabIcon`    | Optional icon wrapper. Use it with your application icons or the icons exported by `moduix`.             |
| `TabsTabLabel`   | Optional text wrapper used with `TabsTabContent`.                                                        |
| `TabsPanel`      | Content region displayed when its matching tab is active. Use `keepMounted` when hidden DOM must remain. |
| `indicator`      | Internal service slot that follows the active tab. Style it through `TabsList classNames.indicator`.     |

## Composition [#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`.

```tsx
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 [#examples]

### Vertical [#vertical]

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

<Preview>
  <VerticalTabsExample />

  <Preview.Code>
    {`
          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>
            );
          }
        `}
  </Preview.Code>

  <Preview.Data>
    {`
          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.",
            },
          ];
        `}
  </Preview.Data>
</Preview>

### Activate On Focus [#activate-on-focus]

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

<Preview>
  <ActivateOnFocusTabsExample />

  <Preview.Code>
    {`
          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>
            );
          }
        `}
  </Preview.Code>

  <Preview.Data>
    {`
          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.",
            },
          ];
        `}
  </Preview.Data>
</Preview>

### Line [#line]

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

<Preview>
  <LineTabsExample />

  <Preview.Code>
    {`
          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>
            );
          }
        `}
  </Preview.Code>

  <Preview.Data>
    {`
          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.",
            },
          ];
        `}
  </Preview.Data>
</Preview>

### Without Indicator [#without-indicator]

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

<Preview>
  <WithoutIndicatorTabsExample />

  <Preview.Code>
    {`
          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>
            );
          }
        `}
  </Preview.Code>

  <Preview.Data>
    {`
          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.",
            },
          ];
        `}
  </Preview.Data>
</Preview>

### Controlled [#controlled]

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

<Preview>
  <ControlledTabsExample />

  <Preview.Code>
    {`
          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>
            );
          }
        `}
  </Preview.Code>

  <Preview.Data>
    {`
          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.",
            },
          ];
        `}
  </Preview.Data>
</Preview>

### Links [#links]

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

<Preview>
  <LinkTabsExample />

  <Preview.Code>
    {`
          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>
            );
          }
        `}
  </Preview.Code>

  <Preview.Data>
    {`
          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.",
            },
          ];
        `}
  </Preview.Data>
</Preview>

### Custom Icons [#custom-icons]

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

<Preview>
  <IconTabsExample />

  <Preview.Code>
    {`
          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>
            );
          }
        `}
  </Preview.Code>

  <Preview.Data>
    {`
          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.",
            },
          ];
        `}
  </Preview.Data>
</Preview>

### Disabled Tab [#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.

<Preview>
  <DisabledTabTabsExample />

  <Preview.Code>
    {`
          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>
            );
          }
        `}
  </Preview.Code>

  <Preview.Data>
    {`
          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.",
            },
          ];
        `}
  </Preview.Data>
</Preview>

### Custom Styles [#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.

<Preview>
  <InlineInputsTabsExample />

  <Preview.Code>
    {`
          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>
            );
          }
        `}
  </Preview.Code>

  <Preview.CSS>
    {`
          .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);
          }
        `}
  </Preview.CSS>
</Preview>
