---
title: Tabs
subtitle: A component for toggling between related panels on the same page.
description: A high-quality, unstyled React tabs component for toggling between related panels on the same page.
---

> FineSoft Components documentation. Independent Stencil port; not the official Base UI website.
>
> React API text and `@base-ui/react` examples are upstream references. FineSoft Stencil examples use `finesoft-components`. Do not treat the two packages as interchangeable.

# Tabs

A high-quality, unstyled React tabs component for toggling between related panels on the same page.

## Demo

### Tailwind

This example shows how to implement the component using Tailwind CSS.

```tsx
/* docs-comparison.tsx */
import ReactTabs from './react-tabs';
import StencilTabs from './stencil-tabs';
import { StencilComparison } from 'docs/src/components/StencilComparison';

export default function ExampleTabs() {
  return <StencilComparison component="tabs" react={<ReactTabs />} stencil={<StencilTabs />} />;
}
```

```tsx
/* react-tabs.tsx */
import { Tabs } from '@base-ui/react/tabs';

const tabClassName =
  'flex h-[calc(2rem+1px)] items-center justify-center bg-transparent px-2 py-0 font-inherit text-sm font-normal leading-5 break-keep whitespace-nowrap text-neutral-600 outline-none select-none hover:text-neutral-950 focus-visible:outline-2 focus-visible:outline-solid focus-visible:-outline-offset-1 focus-visible:outline-neutral-950 dark:focus-visible:outline-white data-active:text-neutral-950 dark:text-neutral-300 dark:hover:text-white dark:data-active:text-white';

const panelClassName =
  'col-start-1 row-start-1 flex w-full items-center justify-center bg-white p-4 text-center text-sm text-neutral-950 outline-none focus-visible:z-1 focus-visible:outline-2 focus-visible:outline-solid focus-visible:-outline-offset-1 focus-visible:outline-neutral-950 dark:focus-visible:outline-white dark:bg-neutral-950 dark:text-white [[hidden]]:hidden';

export default function ExampleTabs() {
  return (
    <Tabs.Root className="w-full max-w-xs" defaultValue="overview">
      <Tabs.List className="relative z-1 -mb-px flex gap-1">
        <Tabs.Tab className={tabClassName} value="overview">
          Overview
        </Tabs.Tab>
        <Tabs.Tab className={tabClassName} value="projects">
          Projects
        </Tabs.Tab>
        <Tabs.Tab className={tabClassName} value="account">
          Account
        </Tabs.Tab>
        <Tabs.Indicator className="absolute top-0 left-0 -z-1 h-full w-(--active-tab-width) translate-x-(--active-tab-left) border-x border-t border-neutral-950 bg-white transition-[translate,width] duration-150 ease-in-out dark:border-white dark:bg-neutral-950" />
      </Tabs.List>
      <div className="grid w-full min-h-32 grid-cols-1 border border-neutral-950 dark:border-white">
        <Tabs.Panel className={panelClassName} value="overview">
          <p>Workspace stats and activity.</p>
        </Tabs.Panel>
        <Tabs.Panel className={panelClassName} value="projects">
          <p>Milestones and deadlines.</p>
        </Tabs.Panel>
        <Tabs.Panel className={panelClassName} value="account">
          <p>Profile and preferences.</p>
        </Tabs.Panel>
      </div>
    </Tabs.Root>
  );
}
```

```tsx
/* stencil-tabs.tsx */
const tabClassName =
  'flex h-[calc(2rem+1px)] items-center justify-center bg-transparent px-2 py-0 font-inherit text-sm font-normal leading-5 break-keep whitespace-nowrap text-neutral-600 outline-none select-none hover:text-neutral-950 focus-visible:outline-2 focus-visible:outline-solid focus-visible:-outline-offset-1 focus-visible:outline-neutral-950 dark:focus-visible:outline-white data-active:text-neutral-950 dark:text-neutral-300 dark:hover:text-white dark:data-active:text-white';

const panelClassName =
  'col-start-1 row-start-1 flex w-full items-center justify-center bg-white p-4 text-center text-sm text-neutral-950 outline-none focus-visible:z-1 focus-visible:outline-2 focus-visible:outline-solid focus-visible:-outline-offset-1 focus-visible:outline-neutral-950 dark:focus-visible:outline-white dark:bg-neutral-950 dark:text-white [[hidden]]:hidden';

export default function StencilTabs() {
  return (
    <fs-tabs-root className="w-full max-w-xs" default-value="overview">
      <fs-tabs-list className="relative z-1 -mb-px flex gap-1">
        <fs-tabs-tab className={tabClassName} value="overview">
          Overview
        </fs-tabs-tab>
        <fs-tabs-tab className={tabClassName} value="projects">
          Projects
        </fs-tabs-tab>
        <fs-tabs-tab className={tabClassName} value="account">
          Account
        </fs-tabs-tab>
        <fs-tabs-indicator className="absolute top-0 left-0 -z-1 h-full w-(--active-tab-width) translate-x-(--active-tab-left) border-x border-t border-neutral-950 bg-white transition-[translate,width] duration-150 ease-in-out dark:border-white dark:bg-neutral-950" />
      </fs-tabs-list>
      <div className="grid w-full min-h-32 grid-cols-1 border border-neutral-950 dark:border-white">
        <fs-tabs-panel className={panelClassName} value="overview">
          <p>Workspace stats and activity.</p>
        </fs-tabs-panel>
        <fs-tabs-panel className={panelClassName} value="projects">
          <p>Milestones and deadlines.</p>
        </fs-tabs-panel>
        <fs-tabs-panel className={panelClassName} value="account">
          <p>Profile and preferences.</p>
        </fs-tabs-panel>
      </div>
    </fs-tabs-root>
  );
}
```

### CSS Modules

This example shows how to implement the component using CSS Modules.

```css
/* index.module.css */
.Root {
  box-sizing: border-box;
  width: 100%;
  max-width: 20rem;
}

.List {
  display: flex;
  position: relative;
  z-index: 1;
  gap: 0.25rem;
  margin-bottom: -1px;
}

.Tab {
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 0;
  margin: 0;
  outline: 0;
  background: none;
  color: oklch(43.9% 0 0deg);
  font-family: inherit;
  font-size: 0.875rem;
  line-height: 1.25rem;
  font-weight: 400;
  -webkit-user-select: none;
  user-select: none;
  white-space: nowrap;
  word-break: keep-all;
  padding-inline: 0.5rem;
  padding-block: 0;
  height: calc(2rem + 1px);

  @media (prefers-color-scheme: dark) {
    color: oklch(87% 0 0deg);
  }

  @media (hover: hover) {
    &:hover {
      color: oklch(14.5% 0 0deg);

      @media (prefers-color-scheme: dark) {
        color: white;
      }
    }
  }

  &[data-active] {
    color: oklch(14.5% 0 0deg);

    @media (prefers-color-scheme: dark) {
      color: white;
    }
  }

  &:focus-visible {
    outline: 2px solid oklch(14.5% 0 0deg);
    outline-offset: -1px;

    @media (prefers-color-scheme: dark) {
      outline-color: white;
    }
  }
}

.Indicator {
  box-sizing: border-box;
  position: absolute;
  z-index: -1;
  left: 0;
  top: 0;
  translate: var(--active-tab-left);
  width: var(--active-tab-width);
  height: 100%;
  transition-property: translate, width;
  transition-duration: 150ms;
  transition-timing-function: ease-in-out;
  border-top: 1px solid oklch(14.5% 0 0deg);
  border-inline: 1px solid oklch(14.5% 0 0deg);
  background-color: white;

  @media (prefers-color-scheme: dark) {
    border-top: 1px solid white;
    border-inline: 1px solid white;
    background-color: oklch(14.5% 0 0deg);
  }
}

.PanelViewport {
  box-sizing: border-box;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  width: 100%;
  min-height: 8rem;
  border-inline: 1px solid oklch(14.5% 0 0deg);
  border-block: 1px solid oklch(14.5% 0 0deg);

  @media (prefers-color-scheme: dark) {
    border-inline: 1px solid white;
    border-block: 1px solid white;
  }
}

.Panel {
  box-sizing: border-box;
  grid-area: 1 / 1;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  padding: 1rem;

  outline: 0;
  font-size: 0.875rem;
  line-height: 1.25rem;
  text-align: center;
  color: oklch(14.5% 0 0deg);
  background-color: white;

  @media (prefers-color-scheme: dark) {
    color: white;
    background-color: oklch(14.5% 0 0deg);
  }

  &:focus-visible {
    outline: 2px solid oklch(14.5% 0 0deg);
    outline-offset: -1px;
    z-index: 1;

    @media (prefers-color-scheme: dark) {
      outline-color: white;
    }
  }

  &[hidden] {
    display: none;
  }
}

.Paragraph {
  margin: 0;
}
```

```tsx
/* docs-comparison.tsx */
import ReactTabs from './react-tabs';
import StencilTabs from './stencil-tabs';
import { StencilComparison } from 'docs/src/components/StencilComparison';

export default function ExampleTabs() {
  return <StencilComparison component="tabs" react={<ReactTabs />} stencil={<StencilTabs />} />;
}
```

```tsx
/* react-tabs.tsx */
import { Tabs } from '@base-ui/react/tabs';
import styles from './index.module.css';

export default function ExampleTabs() {
  return (
    <Tabs.Root className={styles.Root} defaultValue="overview">
      <Tabs.List className={styles.List}>
        <Tabs.Tab className={styles.Tab} value="overview">
          Overview
        </Tabs.Tab>
        <Tabs.Tab className={styles.Tab} value="projects">
          Projects
        </Tabs.Tab>
        <Tabs.Tab className={styles.Tab} value="account">
          Account
        </Tabs.Tab>
        <Tabs.Indicator className={styles.Indicator} />
      </Tabs.List>
      <div className={styles.PanelViewport}>
        <Tabs.Panel className={styles.Panel} value="overview">
          <p className={styles.Paragraph}>Workspace stats and activity.</p>
        </Tabs.Panel>
        <Tabs.Panel className={styles.Panel} value="projects">
          <p className={styles.Paragraph}>Milestones and deadlines.</p>
        </Tabs.Panel>
        <Tabs.Panel className={styles.Panel} value="account">
          <p className={styles.Paragraph}>Profile and preferences.</p>
        </Tabs.Panel>
      </div>
    </Tabs.Root>
  );
}
```

```tsx
/* stencil-tabs.tsx */
import './stencil.css';

export default function StencilTabs() {
  return (
    <fs-tabs-root default-value="overview">
      <fs-tabs-list>
        <fs-tabs-tab value="overview">Overview</fs-tabs-tab>
        <fs-tabs-tab value="projects">Projects</fs-tabs-tab>
        <fs-tabs-tab value="account">Account</fs-tabs-tab>
        <fs-tabs-indicator />
      </fs-tabs-list>
      <div className="demo-tabs-hero-panel-viewport">
        <fs-tabs-panel value="overview">
          <p className="demo-tabs-hero-paragraph">Workspace stats and activity.</p>
        </fs-tabs-panel>
        <fs-tabs-panel value="projects">
          <p className="demo-tabs-hero-paragraph">Milestones and deadlines.</p>
        </fs-tabs-panel>
        <fs-tabs-panel value="account">
          <p className="demo-tabs-hero-paragraph">Profile and preferences.</p>
        </fs-tabs-panel>
      </div>
    </fs-tabs-root>
  );
}
```

```css
/* stencil.css */
/* Example layout and visual exceptions. Default component styles come from the theme. */
@layer fs-theme {
  .demo-tabs-hero-panel-viewport {
    box-sizing: border-box;
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    width: 100%;
    min-height: 8rem;
    border-inline: var(--fs-border-width) solid var(--fs-color-foreground);
    border-block: var(--fs-border-width) solid var(--fs-color-foreground);
  }

  .demo-tabs-hero-paragraph {
    margin: 0;
  }
}
```

## Stencil boundary

The Stencil family retains Light DOM. Root and List own layout and tablist semantics;
Tab keeps its native control in the same tree as the List and Panel so that focus,
`aria-controls`, `aria-labelledby`, and native form associations remain intact.
Panel owns physical mounting and transitions, while Indicator owns measured positioning.
No additional ShadowRoot or CSS part is introduced.

The Base UI theme is built in; select `data-fs-theme="base-ui"`
on an ancestor to enable the optional theme. Existing CSS variables and Light DOM
selectors remain available; host and native control styles are not isolated.

## Anatomy

Import the component and assemble its parts:

```jsx title="Anatomy"
import { Tabs } from '@base-ui/react/tabs';

<Tabs.Root>
  <Tabs.List>
    <Tabs.Tab />
    <Tabs.Indicator />
  </Tabs.List>
  <Tabs.Panel />
</Tabs.Root>;
```

## Examples

### Animated panels

Animate panels as they activate using the `data-starting-style` and `data-ending-style` attributes.
The `data-activation-direction` attribute indicates which direction the newly active tab is relative to the previously active one, letting panels slide in from the correct side.

## Demo

### Tailwind

This example shows how to implement the component using Tailwind CSS.

```tsx
/* docs-comparison.tsx */
import ReactTabs from './react-tabs';
import StencilTabs from './stencil-tabs';
import { StencilComparison } from 'docs/src/components/StencilComparison';

export default function ExampleTabs() {
  return <StencilComparison component="tabs" react={<ReactTabs />} stencil={<StencilTabs />} />;
}
```

```tsx
/* react-tabs.tsx */
import { Tabs } from '@base-ui/react/tabs';

export default function ExampleAnimatedTabs() {
  return (
    <Tabs.Root className="w-full max-w-xs" defaultValue="overview">
      <Tabs.List className="relative z-1 -mb-px flex gap-1">
        <Tabs.Tab className={tabClassName} value="overview">
          Overview
        </Tabs.Tab>
        <Tabs.Tab className={tabClassName} value="projects">
          Projects
        </Tabs.Tab>
        <Tabs.Tab className={tabClassName} value="account">
          Account
        </Tabs.Tab>
        <Tabs.Indicator className="absolute top-0 left-0 -z-1 h-full w-(--active-tab-width) translate-x-(--active-tab-left) border-x border-t border-neutral-950 bg-white transition-[translate,width] duration-150 ease-in-out dark:border-white dark:bg-neutral-950" />
      </Tabs.List>
      <div className="relative grid min-h-32 w-full grid-cols-1 overflow-hidden border border-neutral-950 bg-white dark:border-white dark:bg-neutral-950">
        <Tabs.Panel className={panelClassName} value="overview">
          <p>Workspace stats and activity.</p>
        </Tabs.Panel>
        <Tabs.Panel className={panelClassName} value="projects">
          <p>Milestones and deadlines.</p>
        </Tabs.Panel>
        <Tabs.Panel className={panelClassName} value="account">
          <p>Profile and preferences.</p>
        </Tabs.Panel>
      </div>
    </Tabs.Root>
  );
}

const tabClassName =
  'flex h-[calc(2rem+1px)] items-center justify-center bg-transparent px-2 py-0 font-inherit text-sm font-normal leading-5 break-keep whitespace-nowrap text-neutral-600 outline-none select-none hover:text-neutral-950 focus-visible:outline-2 focus-visible:outline-solid focus-visible:-outline-offset-1 focus-visible:outline-neutral-950 dark:focus-visible:outline-white data-active:text-neutral-950 dark:text-neutral-300 dark:hover:text-white dark:data-active:text-white';

const panelClassName =
  'col-start-1 row-start-1 flex w-full items-center justify-center p-4 text-center text-sm text-neutral-950 outline-none focus-visible:z-1 focus-visible:outline-2 focus-visible:outline-solid focus-visible:-outline-offset-1 focus-visible:outline-neutral-950 dark:focus-visible:outline-white dark:text-white ' +
  '[transition:opacity_175ms_ease,translate_350ms_cubic-bezier(0.22,1,0.36,1)] ' +
  'data-starting-style:opacity-0 data-ending-style:opacity-0 ' +
  'motion-safe:data-starting-style:data-[activation-direction=left]:translate-x-[-50%] ' +
  'motion-safe:data-starting-style:data-[activation-direction=right]:translate-x-[50%] ' +
  'motion-safe:data-ending-style:data-[activation-direction=left]:translate-x-[50%] ' +
  'motion-safe:data-ending-style:data-[activation-direction=right]:translate-x-[-50%]';
```

```tsx
/* stencil-tabs.tsx */
export default function StencilTabs() {
  return (
    <fs-tabs-root className="w-full max-w-xs" default-value="overview">
      <fs-tabs-list className="relative z-1 -mb-px flex gap-1">
        <fs-tabs-tab className={tabClassName} value="overview">
          Overview
        </fs-tabs-tab>
        <fs-tabs-tab className={tabClassName} value="projects">
          Projects
        </fs-tabs-tab>
        <fs-tabs-tab className={tabClassName} value="account">
          Account
        </fs-tabs-tab>
        <fs-tabs-indicator className="absolute top-0 left-0 -z-1 h-full w-(--active-tab-width) translate-x-(--active-tab-left) border-x border-t border-neutral-950 bg-white transition-[translate,width] duration-150 ease-in-out dark:border-white dark:bg-neutral-950" />
      </fs-tabs-list>
      <div className="relative grid min-h-32 w-full grid-cols-1 overflow-hidden border border-neutral-950 bg-white dark:border-white dark:bg-neutral-950">
        <fs-tabs-panel className={panelClassName} value="overview">
          <p>Workspace stats and activity.</p>
        </fs-tabs-panel>
        <fs-tabs-panel className={panelClassName} value="projects">
          <p>Milestones and deadlines.</p>
        </fs-tabs-panel>
        <fs-tabs-panel className={panelClassName} value="account">
          <p>Profile and preferences.</p>
        </fs-tabs-panel>
      </div>
    </fs-tabs-root>
  );
}

const tabClassName =
  'flex h-[calc(2rem+1px)] items-center justify-center bg-transparent px-2 py-0 font-inherit text-sm font-normal leading-5 break-keep whitespace-nowrap text-neutral-600 outline-none select-none hover:text-neutral-950 focus-visible:outline-2 focus-visible:outline-solid focus-visible:-outline-offset-1 focus-visible:outline-neutral-950 dark:focus-visible:outline-white data-active:text-neutral-950 dark:text-neutral-300 dark:hover:text-white dark:data-active:text-white';

const panelClassName =
  'col-start-1 row-start-1 flex w-full items-center justify-center p-4 text-center text-sm text-neutral-950 outline-none focus-visible:z-1 focus-visible:outline-2 focus-visible:outline-solid focus-visible:-outline-offset-1 focus-visible:outline-neutral-950 dark:focus-visible:outline-white dark:text-white ' +
  '[transition:opacity_175ms_ease,translate_350ms_cubic-bezier(0.22,1,0.36,1)] ' +
  'data-starting-style:opacity-0 data-ending-style:opacity-0 ' +
  'motion-safe:data-starting-style:data-[activation-direction=left]:translate-x-[-50%] ' +
  'motion-safe:data-starting-style:data-[activation-direction=right]:translate-x-[50%] ' +
  'motion-safe:data-ending-style:data-[activation-direction=left]:translate-x-[50%] ' +
  'motion-safe:data-ending-style:data-[activation-direction=right]:translate-x-[-50%]';
```

### CSS Modules

This example shows how to implement the component using CSS Modules.

```css
/* index.module.css */
.Root {
  box-sizing: border-box;
  width: 100%;
  max-width: 20rem;
}

.List {
  display: flex;
  position: relative;
  z-index: 1;
  gap: 0.25rem;
  margin-bottom: -1px;
}

.Tab {
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 0;
  margin: 0;
  outline: 0;
  background: none;
  color: oklch(43.9% 0 0deg);
  font-family: inherit;
  font-size: 0.875rem;
  line-height: 1.25rem;
  font-weight: 400;
  -webkit-user-select: none;
  user-select: none;
  white-space: nowrap;
  word-break: keep-all;
  padding-inline: 0.5rem;
  padding-block: 0;
  height: calc(2rem + 1px);

  @media (prefers-color-scheme: dark) {
    color: oklch(87% 0 0deg);
  }

  @media (hover: hover) {
    &:hover {
      color: oklch(14.5% 0 0deg);

      @media (prefers-color-scheme: dark) {
        color: white;
      }
    }
  }

  &[data-active] {
    color: oklch(14.5% 0 0deg);

    @media (prefers-color-scheme: dark) {
      color: white;
    }
  }

  &:focus-visible {
    outline: 2px solid oklch(14.5% 0 0deg);
    outline-offset: -1px;

    @media (prefers-color-scheme: dark) {
      outline-color: white;
    }
  }
}

.Indicator {
  box-sizing: border-box;
  position: absolute;
  z-index: -1;
  left: 0;
  top: 0;
  translate: var(--active-tab-left);
  width: var(--active-tab-width);
  height: 100%;
  transition-property: translate, width;
  transition-duration: 150ms;
  transition-timing-function: ease-in-out;
  border-top: 1px solid oklch(14.5% 0 0deg);
  border-inline: 1px solid oklch(14.5% 0 0deg);
  background-color: white;

  @media (prefers-color-scheme: dark) {
    border-top: 1px solid white;
    border-inline: 1px solid white;
    background-color: oklch(14.5% 0 0deg);
  }
}

.PanelViewport {
  box-sizing: border-box;
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  width: 100%;
  min-height: 8rem;
  overflow: hidden;
  border-inline: 1px solid oklch(14.5% 0 0deg);
  border-block: 1px solid oklch(14.5% 0 0deg);
  background-color: white;

  @media (prefers-color-scheme: dark) {
    border-inline: 1px solid white;
    border-block: 1px solid white;
    background-color: oklch(14.5% 0 0deg);
  }
}

.Panel {
  box-sizing: border-box;
  grid-area: 1 / 1;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  padding: 1rem;

  outline: 0;
  font-size: 0.875rem;
  line-height: 1.25rem;
  text-align: center;
  color: oklch(14.5% 0 0deg);

  transition:
    opacity 175ms ease,
    transform 350ms cubic-bezier(0.22, 1, 0.36, 1);

  @media (prefers-color-scheme: dark) {
    color: white;
  }

  &:focus-visible {
    outline: 2px solid oklch(14.5% 0 0deg);
    outline-offset: -1px;
    z-index: 1;

    @media (prefers-color-scheme: dark) {
      outline-color: white;
    }
  }

  &[data-starting-style],
  &[data-ending-style] {
    opacity: 0;
  }

  @media (prefers-reduced-motion: no-preference) {
    &[data-starting-style] {
      &[data-activation-direction='left'] {
        transform: translateX(-50%);
      }
      &[data-activation-direction='right'] {
        transform: translateX(50%);
      }
    }

    &[data-ending-style] {
      &[data-activation-direction='left'] {
        transform: translateX(50%);
      }
      &[data-activation-direction='right'] {
        transform: translateX(-50%);
      }
    }
  }
}

.Paragraph {
  margin: 0;
}
```

```tsx
/* docs-comparison.tsx */
import ReactTabs from './react-tabs';
import StencilTabs from './stencil-tabs';
import { StencilComparison } from 'docs/src/components/StencilComparison';

export default function ExampleTabs() {
  return <StencilComparison component="tabs" react={<ReactTabs />} stencil={<StencilTabs />} />;
}
```

```tsx
/* react-tabs.tsx */
import { Tabs } from '@base-ui/react/tabs';
import styles from './index.module.css';

export default function ExampleAnimatedTabs() {
  return (
    <Tabs.Root className={styles.Root} defaultValue="overview">
      <Tabs.List className={styles.List}>
        <Tabs.Tab className={styles.Tab} value="overview">
          Overview
        </Tabs.Tab>
        <Tabs.Tab className={styles.Tab} value="projects">
          Projects
        </Tabs.Tab>
        <Tabs.Tab className={styles.Tab} value="account">
          Account
        </Tabs.Tab>
        <Tabs.Indicator className={styles.Indicator} />
      </Tabs.List>
      <div className={styles.PanelViewport}>
        <Tabs.Panel className={styles.Panel} value="overview">
          <p className={styles.Paragraph}>Workspace stats and activity.</p>
        </Tabs.Panel>
        <Tabs.Panel className={styles.Panel} value="projects">
          <p className={styles.Paragraph}>Milestones and deadlines.</p>
        </Tabs.Panel>
        <Tabs.Panel className={styles.Panel} value="account">
          <p className={styles.Paragraph}>Profile and preferences.</p>
        </Tabs.Panel>
      </div>
    </Tabs.Root>
  );
}
```

```tsx
/* stencil-tabs.tsx */
import './stencil.css';

export default function StencilTabs() {
  return (
    <fs-tabs-root data-fs-variant="animated" default-value="overview">
      <fs-tabs-list>
        <fs-tabs-tab value="overview">Overview</fs-tabs-tab>
        <fs-tabs-tab value="projects">Projects</fs-tabs-tab>
        <fs-tabs-tab value="account">Account</fs-tabs-tab>
        <fs-tabs-indicator />
      </fs-tabs-list>
      <div className="demo-tabs-animated-panels-panel-viewport">
        <fs-tabs-panel value="overview">
          <p className="demo-tabs-animated-panels-paragraph">Workspace stats and activity.</p>
        </fs-tabs-panel>
        <fs-tabs-panel value="projects">
          <p className="demo-tabs-animated-panels-paragraph">Milestones and deadlines.</p>
        </fs-tabs-panel>
        <fs-tabs-panel value="account">
          <p className="demo-tabs-animated-panels-paragraph">Profile and preferences.</p>
        </fs-tabs-panel>
      </div>
    </fs-tabs-root>
  );
}
```

```css
/* stencil.css */
/* Example layout and visual exceptions. Default component styles come from the theme. */
@layer fs-theme {
  .demo-tabs-animated-panels-panel-viewport {
    box-sizing: border-box;
    position: relative;
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    width: 100%;
    min-height: 8rem;
    overflow: hidden;
    border-inline: var(--fs-border-width) solid var(--fs-color-foreground);
    border-block: var(--fs-border-width) solid var(--fs-color-foreground);
    background-color: var(--fs-color-surface);
  }

  .demo-tabs-animated-panels-paragraph {
    margin: 0;
  }
}
```

### Links

Use the `render` prop and set `nativeButton={false}` on `<Tabs.Tab>` to render tabs as anchor elements.

```jsx title="Tabs as links"
import { Tabs } from '@base-ui/react/tabs';
import Link from 'next/link';

<Tabs.Root>
  <Tabs.List>
    {/* @highlight-start */}
    {/* @highlight-text "nativeButton={false}" "render" */}
    <Tabs.Tab nativeButton={false} render={<Link href="/overview" />} value="overview">
      Overview
    </Tabs.Tab>
    {/* @highlight-end */}
  </Tabs.List>
  {/* ... */}
</Tabs.Root>;
```

## API reference

### Root

Groups the tabs and the corresponding panels.
Renders a `<div>` element.

**Root Props:**

| Prop          | Type                                                                                    | Default        | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| :------------ | :-------------------------------------------------------------------------------------- | :------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| defaultValue  | `Tabs.Tab.Value`                                                                        | `0`            | The default value. Use when the component is not controlled.&#xA;When the value is `null`, no Tab will be active.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| value         | `Tabs.Tab.Value`                                                                        | -              | The value of the currently active `Tab`. Use when the component is controlled.&#xA;When the value is `null`, no Tab will be active.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| onValueChange | `((value: Tabs.Tab.Value, eventDetails: Tabs.Root.ChangeEventDetails) => void)`         | -              | Callback invoked when new value is being set. The event `reason` is `'none'` for user-initiated changes, such as a click&#xA;or keyboard navigation; `'initial'` for the first automatic selection or&#xA;fallback in uncontrolled roots when `defaultValue` is omitted or&#xA;`undefined`, including when the implicit initial value is disabled or&#xA;missing; `'disabled'` for automatic fallback when the selected tab becomes&#xA;disabled in uncontrolled roots; or `'missing'` for automatic fallback when&#xA;the selected tab is removed, or when an explicit `defaultValue` never&#xA;matches a mounted tab in uncontrolled roots. For automatic changes, the selected value can be `null` when no enabled Tab&#xA;is available as a fallback. Automatic changes cannot be canceled; calling `eventDetails.cancel()` for&#xA;`'initial'`, `'disabled'`, or `'missing'` has no effect. |
| orientation   | `Tabs.Root.Orientation`                                                                 | `'horizontal'` | The component orientation (layout flow direction).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| className     | `string \| ((state: Tabs.Root.State) => string \| undefined)`                           | -              | CSS class applied to the element, or a function that&#xA;returns a class based on the component's state.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| style         | `React.CSSProperties \| ((state: Tabs.Root.State) => React.CSSProperties \| undefined)` | -              | Style applied to the element, or a function that&#xA;returns a style object based on the component's state.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| render        | `ReactElement \| ((props: HTMLProps, state: Tabs.Root.State) => ReactElement)`          | -              | Allows you to replace the component's HTML element&#xA;with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |

**Root Data Attributes:**

| Attribute                 | Type                                            | Description                                                                   |
| :------------------------ | :---------------------------------------------- | :---------------------------------------------------------------------------- |
| data-orientation          | `'horizontal' \| 'vertical'`                    | Indicates the orientation of the tabs.                                        |
| data-activation-direction | `'left' \| 'right' \| 'up' \| 'down' \| 'none'` | Indicates the direction of the activation (based on the previous active tab). |

### Root.Props

Re-export of [Root](/react/components/tabs.md) props.

### Root.State

```typescript
type TabsRootState = {
  /** The component orientation. */
  orientation: Tabs.Root.Orientation;
  /** The direction used for tab activation. */
  tabActivationDirection: Tabs.Tab.ActivationDirection;
};
```

### Root.ChangeEventReason

```typescript
type TabsRootChangeEventReason = 'none' | 'disabled' | 'missing' | 'initial';
```

### Root.ChangeEventDetails

```typescript
type TabsRootChangeEventDetails = ({ reason: 'none' } | { reason: 'disabled' } | { reason: 'missing' } | { reason: 'initial' }) & {
  /** The native event associated with the custom event. */
  event: Event;
  /** Cancels Base UI from handling the event. */
  cancel: () => void;
  /** Allows the event to propagate in cases where Base UI will stop the propagation. */
  allowPropagation: () => void;
  /** Indicates whether the event has been canceled. */
  isCanceled: boolean;
  /** Indicates whether the event is allowed to propagate. */
  isPropagationAllowed: boolean;
  /** The element that triggered the event, if applicable. */
  trigger: Element | undefined;
  activationDirection: Tabs.Tab.ActivationDirection;
};
```

### Root.Orientation

```typescript
type TabsRootOrientation = 'horizontal' | 'vertical';
```

### List

Groups the individual tab buttons.
Renders a `<div>` element.

**List Props:**

| Prop            | Type                                                                                    | Default | Description                                                                                                                                                                                   |
| :-------------- | :-------------------------------------------------------------------------------------- | :------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| activateOnFocus | `boolean`                                                                               | `false` | Whether to automatically change the active tab on arrow key focus.&#xA;Otherwise, tabs will be activated using Enter or Space key press.                                                      |
| loopFocus       | `boolean`                                                                               | `true`  | Whether to loop keyboard focus back to the first item&#xA;when the end of the list is reached while using the arrow keys.                                                                     |
| className       | `string \| ((state: Tabs.List.State) => string \| undefined)`                           | -       | CSS class applied to the element, or a function that&#xA;returns a class based on the component's state.                                                                                      |
| style           | `React.CSSProperties \| ((state: Tabs.List.State) => React.CSSProperties \| undefined)` | -       | Style applied to the element, or a function that&#xA;returns a style object based on the component's state.                                                                                   |
| render          | `ReactElement \| ((props: HTMLProps, state: Tabs.List.State) => ReactElement)`          | -       | Allows you to replace the component's HTML element&#xA;with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |

**List Data Attributes:**

| Attribute                 | Type                                            | Description                                                                   |
| :------------------------ | :---------------------------------------------- | :---------------------------------------------------------------------------- |
| data-orientation          | `'horizontal' \| 'vertical'`                    | Indicates the orientation of the tabs.                                        |
| data-activation-direction | `'left' \| 'right' \| 'up' \| 'down' \| 'none'` | Indicates the direction of the activation (based on the previous active tab). |

### List.Props

Re-export of [List](/react/components/tabs.md) props.

### List.State

```typescript
type TabsListState = {
  /** The component orientation. */
  orientation: Tabs.Root.Orientation;
  /** The direction used for tab activation. */
  tabActivationDirection: Tabs.Tab.ActivationDirection;
};
```

### Panel

A panel displayed when the corresponding tab is active.
Renders a `<div>` element.

**Panel Props:**

| Prop        | Type                                                                                     | Default | Description                                                                                                                                                                                   |
| :---------- | :--------------------------------------------------------------------------------------- | :------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| value\*     | `Tabs.Tab.Value`                                                                         | -       | The value of the TabPanel. It will be shown when the Tab with the corresponding value is active.                                                                                              |
| className   | `string \| ((state: Tabs.Panel.State) => string \| undefined)`                           | -       | CSS class applied to the element, or a function that&#xA;returns a class based on the component's state.                                                                                      |
| style       | `React.CSSProperties \| ((state: Tabs.Panel.State) => React.CSSProperties \| undefined)` | -       | Style applied to the element, or a function that&#xA;returns a style object based on the component's state.                                                                                   |
| keepMounted | `boolean`                                                                                | `false` | Whether to keep the HTML element in the DOM while the panel is hidden.                                                                                                                        |
| render      | `ReactElement \| ((props: HTMLProps, state: Tabs.Panel.State) => ReactElement)`          | -       | Allows you to replace the component's HTML element&#xA;with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |

**Panel Data Attributes:**

| Attribute                 | Type                                            | Description                                                                   |
| :------------------------ | :---------------------------------------------- | :---------------------------------------------------------------------------- |
| data-orientation          | `'horizontal' \| 'vertical'`                    | Indicates the orientation of the tabs.                                        |
| data-activation-direction | `'left' \| 'right' \| 'up' \| 'down' \| 'none'` | Indicates the direction of the activation (based on the previous active tab). |
| data-hidden               | -                                               | Present when the panel is hidden.                                             |
| data-index                | -                                               | Indicates the index of the tab panel.                                         |
| data-starting-style       | -                                               | Present when the panel begins animating in.                                   |
| data-ending-style         | -                                               | Present when the panel is animating out.                                      |

### Panel.Props

Re-export of [Panel](/react/components/tabs.md) props.

### Panel.State

```typescript
type TabsPanelState = {
  /** Whether the component is hidden. */
  hidden: boolean;
  /** The transition status of the component. */
  transitionStatus: TransitionStatus;
  /** The component orientation. */
  orientation: Tabs.Root.Orientation;
  /** The direction used for tab activation. */
  tabActivationDirection: Tabs.Tab.ActivationDirection;
};
```

### Panel.Metadata

```typescript
type TabsPanelMetadata = { id?: string; value: Tabs.Tab.Value };
```

### Indicator

A visual indicator that can be styled to match the position of the currently active tab.
Renders a `<span>` element.

**Indicator Props:**

| Prop                  | Type                                                                                         | Default | Description                                                                                                                                                                                   |
| :-------------------- | :------------------------------------------------------------------------------------------- | :------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| renderBeforeHydration | `boolean`                                                                                    | `false` | Whether to render itself before React hydrates.&#xA;This minimizes the time that the indicator isn't visible after server-side rendering.                                                     |
| className             | `string \| ((state: Tabs.Indicator.State) => string \| undefined)`                           | -       | CSS class applied to the element, or a function that&#xA;returns a class based on the component's state.                                                                                      |
| style                 | `React.CSSProperties \| ((state: Tabs.Indicator.State) => React.CSSProperties \| undefined)` | -       | Style applied to the element, or a function that&#xA;returns a style object based on the component's state.                                                                                   |
| render                | `ReactElement \| ((props: HTMLProps, state: Tabs.Indicator.State) => ReactElement)`          | -       | Allows you to replace the component's HTML element&#xA;with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |

**Indicator Data Attributes:**

| Attribute                 | Type                                            | Description                                                                   |
| :------------------------ | :---------------------------------------------- | :---------------------------------------------------------------------------- |
| data-orientation          | `'horizontal' \| 'vertical'`                    | Indicates the orientation of the tabs.                                        |
| data-activation-direction | `'left' \| 'right' \| 'up' \| 'down' \| 'none'` | Indicates the direction of the activation (based on the previous active tab). |

**Indicator CSS Variables:**

| Variable              | Type     | Description                                                                                 |
| :-------------------- | :------- | :------------------------------------------------------------------------------------------ |
| `--active-tab-bottom` | `number` | Indicates the distance on the bottom side from the parent's container if the tab is active. |
| `--active-tab-height` | `number` | Indicates the height of the tab if it is active.                                            |
| `--active-tab-left`   | `number` | Indicates the distance on the left side from the parent's container if the tab is active.   |
| `--active-tab-right`  | `number` | Indicates the distance on the right side from the parent's container if the tab is active.  |
| `--active-tab-top`    | `number` | Indicates the distance on the top side from the parent's container if the tab is active.    |
| `--active-tab-width`  | `number` | Indicates the width of the tab if it is active.                                             |

### Indicator.Props

Re-export of [Indicator](/react/components/tabs.md) props.

### Indicator.State

```typescript
type TabsIndicatorState = {
  /** The active tab position. */
  activeTabPosition: Tabs.Tab.Position | null;
  /** The active tab size. */
  activeTabSize: Tabs.Tab.Size | null;
  /** The component orientation. */
  orientation: Tabs.Root.Orientation;
  /** The direction used for tab activation. */
  tabActivationDirection: Tabs.Tab.ActivationDirection;
};
```

### Tab

An individual interactive tab button that toggles the corresponding panel.
Renders a `<button>` element.

**Tab Props:**

| Prop         | Type                                                                                   | Default | Description                                                                                                                                                                                                                                                                                                                                                                                                                |
| :----------- | :------------------------------------------------------------------------------------- | :------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| value\*      | `Tabs.Tab.Value`                                                                       | -       | The value of the Tab.                                                                                                                                                                                                                                                                                                                                                                                                      |
| nativeButton | `boolean`                                                                              | `true`  | Whether the component renders a native `<button>` element when replacing it&#xA;via the `render` prop.&#xA;Set to `false` if the rendered element is not a button (for example, `<div>`).                                                                                                                                                                                                                                  |
| disabled     | `boolean`                                                                              | -       | Whether the Tab is disabled. If a first Tab on a `<Tabs.List>` is disabled, it won't initially be selected.&#xA;Instead, the next enabled Tab will be selected.&#xA;However, it does not work like this during server-side rendering, as it is not known&#xA;during pre-rendering which Tabs are disabled.&#xA;To work around it, ensure that `defaultValue` or `value` on `<Tabs.Root>` is set to an enabled Tab's value. |
| className    | `string \| ((state: Tabs.Tab.State) => string \| undefined)`                           | -       | CSS class applied to the element, or a function that&#xA;returns a class based on the component's state.                                                                                                                                                                                                                                                                                                                   |
| style        | `React.CSSProperties \| ((state: Tabs.Tab.State) => React.CSSProperties \| undefined)` | -       | Style applied to the element, or a function that&#xA;returns a style object based on the component's state.                                                                                                                                                                                                                                                                                                                |
| render       | `ReactElement \| ((props: HTMLProps, state: Tabs.Tab.State) => ReactElement)`          | -       | Allows you to replace the component's HTML element&#xA;with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render.                                                                                                                                                                                                                              |

**Tab Data Attributes:**

| Attribute                 | Type                                            | Description                                                                   |
| :------------------------ | :---------------------------------------------- | :---------------------------------------------------------------------------- |
| data-orientation          | `'horizontal' \| 'vertical'`                    | Indicates the orientation of the tabs.                                        |
| data-disabled             | -                                               | Present when the tab is disabled.                                             |
| data-activation-direction | `'left' \| 'right' \| 'up' \| 'down' \| 'none'` | Indicates the direction of the activation (based on the previous active tab). |
| data-active               | -                                               | Present when the tab is active.                                               |

### Tab.Props

Re-export of [Tab](/react/components/tabs.md) props.

### Tab.State

```typescript
type TabsTabState = {
  /** Whether the component should ignore user interaction. */
  disabled: boolean;
  /** Whether the component is active. */
  active: boolean;
  /** The component orientation. */
  orientation: Tabs.Root.Orientation;
  /** The direction used for tab activation. */
  tabActivationDirection: Tabs.Tab.ActivationDirection;
};
```

### Tab.Value

```typescript
type TabsTabValue = Tabs.Tab.Value;
```

### Tab.ActivationDirection

```typescript
type TabsTabActivationDirection = 'left' | 'right' | 'up' | 'down' | 'none';
```

### Tab.Metadata

```typescript
type TabsTabMetadata = {
  disabled: boolean;
  id: string | undefined;
  value: Tabs.Tab.Value | undefined;
};
```

### Tab.Position

```typescript
type TabsTabPosition = { left: number; right: number; top: number; bottom: number };
```

### Tab.Size

```typescript
type TabsTabSize = { width: number; height: number };
```

## Export Groups

- `Tabs.Root`: `Tabs.Root`, `Tabs.Root.State`, `Tabs.Root.Props`, `Tabs.Root.Orientation`, `Tabs.Root.ChangeEventReason`, `Tabs.Root.ChangeEventDetails`
- `Tabs.Tab`: `Tabs.Tab`, `Tabs.Tab.Value`, `Tabs.Tab.ActivationDirection`, `Tabs.Tab.Position`, `Tabs.Tab.Size`, `Tabs.Tab.Metadata`, `Tabs.Tab.State`, `Tabs.Tab.Props`
- `Tabs.Indicator`: `Tabs.Indicator`, `Tabs.Indicator.State`, `Tabs.Indicator.Props`
- `Tabs.Panel`: `Tabs.Panel`, `Tabs.Panel.Metadata`, `Tabs.Panel.State`, `Tabs.Panel.Props`
- `Tabs.List`: `Tabs.List`, `Tabs.List.State`, `Tabs.List.Props`
- `Default`: `TabsRootOrientation`, `TabsRootState`, `TabsRootProps`, `TabsRootChangeEventReason`, `TabsRootChangeEventDetails`, `TabsIndicatorState`, `TabsIndicatorProps`, `TabsTabValue`, `TabsTabActivationDirection`, `TabsTabPosition`, `TabsTabSize`, `TabsTabMetadata`, `TabsTabState`, `TabsTabProps`, `TabsPanelMetadata`, `TabsPanelState`, `TabsPanelProps`, `TabsListState`, `TabsListProps`

## Canonical Types

Maps `Canonical`: `Alias` — Use Canonical when its namespace is already imported; otherwise use Alias.

- `Tabs.Root.State`: `TabsRootState`
- `Tabs.Root.Props`: `TabsRootProps`
- `Tabs.Root.Orientation`: `TabsRootOrientation`
- `Tabs.Root.ChangeEventReason`: `TabsRootChangeEventReason`
- `Tabs.Root.ChangeEventDetails`: `TabsRootChangeEventDetails`
- `Tabs.Tab.Value`: `TabsTabValue`
- `Tabs.Tab.ActivationDirection`: `TabsTabActivationDirection`
- `Tabs.Tab.Position`: `TabsTabPosition`
- `Tabs.Tab.Size`: `TabsTabSize`
- `Tabs.Tab.Metadata`: `TabsTabMetadata`
- `Tabs.Tab.State`: `TabsTabState`
- `Tabs.Tab.Props`: `TabsTabProps`
- `Tabs.Indicator.State`: `TabsIndicatorState`
- `Tabs.Indicator.Props`: `TabsIndicatorProps`
- `Tabs.Panel.Metadata`: `TabsPanelMetadata`
- `Tabs.Panel.State`: `TabsPanelState`
- `Tabs.Panel.Props`: `TabsPanelProps`
- `Tabs.List.State`: `TabsListState`
- `Tabs.List.Props`: `TabsListProps`
