> ## Documentation Index
> Fetch the complete documentation index at: https://anvil.servicetitan.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Tab - Web

> Tabs organize related content into groups to allow users to move between groups.

> [!NOTE]
>
> For development, import `Tab` from
> `@servicetitan/anvil2`. For design, find it in the
> [Anvil2 file](https://www.figma.com/design/xaNQsvRKczoxu7pj0M6QT8/Anvil2-v3?node-id=194-71).

![](https://mintcdn.com/servicetitan/H5FwKyiqhPVZ1UJQ/images/docs/web/components/shared/tab-overview.png?fit=max\&auto=format\&n=H5FwKyiqhPVZ1UJQ\&q=85\&s=2ba572fa3b1df54371aefcec4ce30b44)

## Anatomy

The Tab consists of five primary elements that work together to organize related content into groups.

![Anatomy of a
Tab](https://mintcdn.com/servicetitan/usDdQzJfkl6jYYLR/images/docs/web/components/tab/design/anatomy-of-a-tab.png?fit=max\&auto=format\&n=usDdQzJfkl6jYYLR\&q=85\&s=72db99c92390267c197bca549fc2e725)

1. Tablist
2. Tab content or panel
3. Tab
4. Active Tab
5. Divider

## Options

The Tab supports active state, label, and width configurations to accommodate various content organization scenarios.

### Active

Tabs always have exactly one active Tab at a time. Typically, the first Tab in a group is the default active Tab.

![Example of an active
tab](https://mintcdn.com/servicetitan/usDdQzJfkl6jYYLR/images/docs/web/components/tab/design/example-of-an-active-tab.png?fit=max\&auto=format\&n=usDdQzJfkl6jYYLR\&q=85\&s=1404e9f1e377dff20adb75a82409eee5)

### Label

![Example of tabs with different label
widths](https://mintcdn.com/servicetitan/ar8TJanaRSxOpbCO/images/docs/web/components/tab/design/example-of-tabs-with-different-label-widths.png?fit=max\&auto=format\&n=ar8TJanaRSxOpbCO\&q=85\&s=335611fc6aa10b76323c77087a69aeeb)

#### Custom labels

The Tab label can be replaced as needed with custom, non-interactive content.

### Width options

#### Fixed width

By default, an individual Tab will be as wide as its text, with a minimum width of 48px. This is useful when Tabs are in wide containers.

![Example of Tabs with a fixed
width](https://mintcdn.com/servicetitan/ar8TJanaRSxOpbCO/images/docs/web/components/tab/design/example-of-tabs-with-a-fixed-width.png?fit=max\&auto=format\&n=ar8TJanaRSxOpbCO\&q=85\&s=bb6c9a77cc5bca36864412a2b244861b)

#### Filled width

Tabs can also fill all available width in its container. This is useful in smaller containers.

![Example of Tabs with a filled
width](https://mintcdn.com/servicetitan/usDdQzJfkl6jYYLR/images/docs/web/components/tab/design/example-of-tabs-with-a-filled-width.png?fit=max\&auto=format\&n=usDdQzJfkl6jYYLR\&q=85\&s=a458a4100d2869178e7da2a6df4880ec)

## Behavior

The Tab responds to space constraints with compression and overflow behaviors while maintaining consistent visual states.

### Visual States

Visual states apply independently of the active state.

![Tab visual
states](https://mintcdn.com/servicetitan/ar8TJanaRSxOpbCO/images/docs/web/components/tab/design/tab-visual-states.png?fit=max\&auto=format\&n=ar8TJanaRSxOpbCO\&q=85\&s=4d5a9a7fe9fa2e05f2cde145ad23efd5)

#### Disabled

Disabled Tabs are not supported. Consider either removing the Tab, or disabling the Tab's content in some way instead.

### Tab compression

With both fixed and filled Tab widths, when not enough space is available, the Tabs will shrink, but not truncate, to fit the container. A minimum of `1rem` of space will exist between Tab labels.

![Behavior of filled Tabs with minimal space
available](https://mintcdn.com/servicetitan/usDdQzJfkl6jYYLR/images/docs/web/components/tab/design/behavior-of-filled-tabs-with-minimal-space-available.png?fit=max\&auto=format\&n=usDdQzJfkl6jYYLR\&q=85\&s=7e98db96ea14381674e80b63c0eefa70)

### Tab group overflow

When compression is not enough, the Tab group will scroll overflow, rather than truncate. A user can both scroll to reveal (either by keyboard or mouse), or use the next / previous buttons provided by the group.

![Example of Tab scrolling
behavior](https://mintcdn.com/servicetitan/usDdQzJfkl6jYYLR/images/docs/web/components/tab/design/example-of-tab-scrolling-behavior.png?fit=max\&auto=format\&n=usDdQzJfkl6jYYLR\&q=85\&s=206bdf8a7ef2655bb6afdff1fca08ed7)

## Usage Guidelines

Use the Tab to organize content into multiple sections. Tabs help to break up content into easier to process information.

| Standard | Requirement |
| - | - |
| DoKeep exactly one Tab active | Exactly one Tab should always be active in a Tab list; see [Always have one Tab active](#always-have-one-tab-active). |
| DoKeep Tab scope clear | Keep the scope of content change to inside the boundaries of Tabs; see [Make the scope of Tabs clear](#make-the-scope-of-tabs-clear). |
| DoPair Tabs with informational Chips | Pair a Tab with a Chip when the number value is informational in nature; see [Showing numerical values](#showing-numerical-values). |
| DoPlace status Alert near Tabs | Pair a status with an [Alert](https://anvil.servicetitan.com/docs/components/alert/web.md), placed below the Tab for Tab-scoped status or above the Tabs for cross-Tab status; place it below the Tabs as a rule; see [Representing status](#representing-status). |
| Don'tPrimary navigation | Tabs should not represent the primary method of navigation. A side or top nav is used instead. |
| Don'tStarting an action | Tabs should not be used to directly start an action, such as a flow. |
| Don'tUnclear content organization | Tabs should not be used when content cannot be organized in a way a user would understand. |
| Don'tDirectly comparing tab content | Tabs should not be used when information between tabs is being directly compared. |
| Don'tFiltering page content | Tabs should not be used for filtering content on the page. |
| Don'tNo active tab | Don't have a tab list where no tab is active; see [Always have one Tab active](#always-have-one-tab-active). |
| Don'tMultiple selected tabs | Don't show multiple tabs as selected; see [Always have one Tab active](#always-have-one-tab-active). |
| Don'tChanging content outside Tab scope | Don't change content outside of the Tab scope between Tabs; see [Make the scope of Tabs clear](#make-the-scope-of-tabs-clear). |
| Don'tColoring Chips in Tabs | Don't add color to Chips when used in Tabs; see [Showing numerical values](#showing-numerical-values). |
| Don'tStatus directly in Tab labels | Don't represent status directly in the Tabs — this isn't a common experience and raises accessibility concerns for keyboard and screen reader users; see [Representing status](#representing-status). |

### Alternatives

#### Tab vs Details

Details are used to show additional information around a sub-topic on a page, while a Tab categorizes information. The context of Details is small, non-essential information, while Tabs can categorize both small and large, essential and non-essential information.

#### Tab vs Segmented Control

Segmented Control allows users to change the presentation format of displayed content or to filter it. Tabs changes the view to show new content.

#### Tab vs Side Nav

Side Navs and Tabs perform similar tasks of organizing content. In most situations, Side Navs are a higher level of the navigation structure compared to Tabs. Side Navs are also able to organize less related items together compared to Tabs.

#### Tab vs Stepper

Tabs help organize information about a subject on the page, while a Stepper helps complete a flow. They can't substitute for each other, although both could be used on the same page.

### How to Use

#### Always have one Tab active

Exactly one Tab should always be active in a Tab list.

tab-active-single-do

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/tab-active-single-do?file=src/App.tsx)

```tsx lines theme={null}
import { Tab } from "@servicetitan/anvil2";

function App() {
  return (
    <Tab defaultIndex={0} style={{ minWidth: "55rem" }}>
      <Tab.List>
        <Tab.Button id="t1" controls="p1" onClick={console.log}>
          Tab A
        </Tab.Button>
        <Tab.Button id="t2" controls="p2" onClick={console.log}>
          Tab B
        </Tab.Button>
        <Tab.Button id="t3" controls="p3" onClick={console.log}>
          Tab C
        </Tab.Button>
      </Tab.List>
    </Tab>
  );
}

export default App;
```

> [!TIP]
>
> **Do**

tab-active-none-dont

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/tab-active-none-dont?file=src/App.tsx)

```tsx lines theme={null}
import { Tab } from "@servicetitan/anvil2";

function App() {
  return (
    <Tab.List style={{ minWidth: "55rem" }}>
      <Tab.Button id="t1" controls="p1" onClick={console.log}>
        Tab A
      </Tab.Button>
      <Tab.Button id="t2" controls="p2" onClick={console.log}>
        Tab B
      </Tab.Button>
      <Tab.Button id="t3" controls="p3" onClick={console.log}>
        Tab C
      </Tab.Button>
    </Tab.List>
  );
}

export default App;
```

> [!CAUTION]
>
> **Don't**

Don't have a tab list where no tab is active.

tab-active-multiple-dont

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/tab-active-multiple-dont?file=src/App.tsx)

```tsx lines theme={null}
import { Tab } from "@servicetitan/anvil2";

function App() {
  return (
    <Tab defaultIndex={0} style={{ minWidth: "55rem" }}>
      <Tab.List>
        <Tab.Button id="t5" controls="p1" onClick={console.log}>
          Tab A
        </Tab.Button>
        <Tab.Button id="t3" controls="p2" onClick={console.log}>
          Tab B
        </Tab.Button>
        <Tab.Button id="t5" controls="p3" onClick={console.log}>
          Tab C
        </Tab.Button>
      </Tab.List>
    </Tab>
  );
}

export default App;
```

> [!CAUTION]
>
> **Don't**

Don't show multiple tabs as selected

#### Make the scope of Tabs clear

A user should be able to understand what is and isn't part of a Tab list. This usually means content below the Tabs, extending across the Tab's divider line. Additional visual consideration may be needed if the Tab scope is a sub section of a page.

tab-scope-do

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/tab-scope-do?file=src/App.tsx)

```tsx lines theme={null}
import { Tab, Text, Button } from "@servicetitan/anvil2";

function App() {
  return (
    <div style={{ minWidth: "55rem", padding: 24 }}>
      <div
        style={{
          display: "flex",
          justifyContent: "space-between",
          alignItems: "center",
          marginBottom: 16,
        }}
      >
        <Text variant="headline" el="h1" style={{ marginBottom: 0 }}>
          Page title
        </Text>
        <Button appearance="primary">Create</Button>
      </div>
      <Tab defaultIndex={0}>
        <Tab.List>
          <Tab.Button id="t1" controls="p1" onClick={console.log}>
            Tab A
          </Tab.Button>
          <Tab.Button id="t2" controls="p2" onClick={console.log}>
            Tab B
          </Tab.Button>
          <Tab.Button id="t3" controls="p3" onClick={console.log}>
            Tab C
          </Tab.Button>
        </Tab.List>
        <Tab.Panel id="p1">Scope of Tab A</Tab.Panel>
        <Tab.Panel id="p2">Scope of Tab B</Tab.Panel>
        <Tab.Panel id="p3">Scope of Tab C</Tab.Panel>
      </Tab>
    </div>
  );
}

export default App;
```

> [!TIP]
>
> **Do**

Keep the scope of content change to inside the boundaries of Tabs.

tab-scope-dont

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/tab-scope-dont?file=src/App.tsx)

```tsx lines theme={null}
import { Tab, Text, Button } from "@servicetitan/anvil2";

function App() {
  return (
    <div style={{ minWidth: "55rem", padding: 24 }}>
      <Tab defaultIndex={0}>
        <Tab.Panel id="p1">
          <div
            style={{
              display: "flex",
              justifyContent: "space-between",
              alignItems: "center",
              marginBottom: 16,
            }}
          >
            <Text variant="headline" el="h1" style={{ marginBottom: 0 }}>
              Page title A
            </Text>
            <Button appearance="primary">Create</Button>
          </div>
        </Tab.Panel>
        <Tab.Panel id="p2">
          <div
            style={{
              display: "flex",
              justifyContent: "space-between",
              alignItems: "center",
              marginBottom: 16,
            }}
          >
            <Text variant="headline" el="h1" style={{ marginBottom: 0 }}>
              Page title B
            </Text>
            <Button appearance="primary">Edit</Button>
          </div>
        </Tab.Panel>
        <Tab.Panel id="p3">
          <div
            style={{
              display: "flex",
              justifyContent: "space-between",
              alignItems: "center",
              marginBottom: 16,
            }}
          >
            <Text variant="headline" el="h1" style={{ marginBottom: 0 }}>
              Page title C
            </Text>
            <Button appearance="danger">Delete</Button>
          </div>
        </Tab.Panel>
        <Tab.List>
          <Tab.Button id="t1" controls="p1" onClick={console.log}>
            Tab A
          </Tab.Button>
          <Tab.Button id="t2" controls="p2" onClick={console.log}>
            Tab B
          </Tab.Button>
          <Tab.Button id="t3" controls="p3" onClick={console.log}>
            Tab C
          </Tab.Button>
        </Tab.List>
        <Tab.Panel id="p1">Scope of Tab A</Tab.Panel>
        <Tab.Panel id="p2">Scope of Tab B</Tab.Panel>
        <Tab.Panel id="p3">Scope of Tab C</Tab.Panel>
      </Tab>
    </div>
  );
}

export default App;
```

> [!CAUTION]
>
> **Don't**

Don't change content outside of the Tab scope between Tabs.

#### Showing numerical values

Tabs can utilize small [Chips](https://anvil.servicetitan.com/docs/components/chip/web.md) to convey value. Pair a Tab with a Chip when the number value is informational in nature.

tab-chips-do

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/tab-chips-do?file=src/App.tsx)

```tsx lines theme={null}
import { Tab, Flex, Chip } from "@servicetitan/anvil2";

function App() {
  return (
    <Tab defaultIndex={0} style={{ minWidth: "55rem" }}>
      <Tab.List>
        <Tab.Button id="t1" controls="p1" onClick={console.log}>
          <Flex gap={2} alignItems="center">
            All
            <Chip label="55" size="small" />
          </Flex>
        </Tab.Button>
        <Tab.Button id="t2" controls="p2" onClick={console.log}>
          <Flex gap={2} alignItems="center">
            Foo
            <Chip label="18" size="small" />
          </Flex>
        </Tab.Button>
        <Tab.Button id="t3" controls="p3" onClick={console.log}>
          <Flex gap={2} alignItems="center">
            Bar
            <Chip label="37" size="small" />
          </Flex>
        </Tab.Button>
      </Tab.List>
    </Tab>
  );
}

export default App;
```

> [!TIP]
>
> **Do**

If a numerical value is relevant, it may be optionally added to a Tab.

tab-chips-dont

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/tab-chips-dont?file=src/App.tsx)

```tsx lines theme={null}
import { Tab, Flex, Chip } from "@servicetitan/anvil2";
import { core } from "@servicetitan/anvil2/token";

function App() {
  return (
    <Tab defaultIndex={0} style={{ minWidth: "55rem" }}>
      <Tab.List>
        <Tab.Button id="t1" controls="p1" onClick={console.log}>
          <Flex gap={2} alignItems="center">
            All
            <Chip label="55" size="small" />
          </Flex>
        </Tab.Button>
        <Tab.Button id="t2" controls="p2" onClick={console.log}>
          <Flex gap={2} alignItems="center">
            Foo
            <Chip label="18" size="small" />
          </Flex>
        </Tab.Button>
        <Tab.Button id="t3" controls="p3" onClick={console.log}>
          <Flex gap={2} alignItems="center">
            Bar
            <Chip
              label="37"
              size="small"
              color={core.semantic?.StatusColorDanger?.value}
            />
          </Flex>
        </Tab.Button>
      </Tab.List>
    </Tab>
  );
}

export default App;
```

> [!CAUTION]
>
> **Don't**

Don't add color to Chips when used in Tabs.

#### Representing status

When a status, typically an error, needs representation at the Tab level, pair it with an [Alert](https://anvil.servicetitan.com/docs/components/alert/web.md), either below the Tab or above the Tab depending on how critical it is a user should know about the status. As a rule, place it below the Tabs.

Don't represent status directly in the Tabs. Status in Tabs directly is not a common experience in apps, in addition to accessibility concerns for keyboard and screenreader users.

tab-error-inside-do

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/tab-error-inside-do?file=src/App.tsx)

```tsx lines theme={null}
import { Tab, Alert } from "@servicetitan/anvil2";

function App() {
  return (
    <>
      <Tab defaultIndex={0} style={{ minWidth: "55rem" }}>
        <Tab.List>
          <Tab.Button id="t1" controls="p1" onClick={console.log}>
            Tab A
          </Tab.Button>
          <Tab.Button id="t2" controls="p2" onClick={console.log}>
            Tab B
          </Tab.Button>
          <Tab.Button id="t3" controls="p3" onClick={console.log}>
            Tab C
          </Tab.Button>
        </Tab.List>
        <Tab.Panel id="p1">
          <Alert
            title="Error Alert scoped to the Tab"
            status="danger"
            onClose={console.log}
          >
            Place an Alert below the Tab when its scope is limited to just this
            Tab content.
          </Alert>
        </Tab.Panel>
        <Tab.Panel id="p2">Scope of Tab B</Tab.Panel>
        <Tab.Panel id="p3">Scope of Tab C</Tab.Panel>
      </Tab>
    </>
  );
}

export default App;
```

> [!TIP]
>
> **Do**

tab-error-outside-do

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/tab-error-outside-do?file=src/App.tsx)

```tsx lines theme={null}
import { Tab, Flex, Alert } from "@servicetitan/anvil2";

function App() {
  return (
    <Flex direction="column" gap={2} style={{ minWidth: "55rem" }}>
      <Alert
        title="Error Alert scoped above the Tab"
        status="danger"
        onClose={console.log}
      >
        Place an Alert above the Tabs when its scope is across Tabs.
      </Alert>
      <Tab defaultIndex={0}>
        <Tab.List>
          <Tab.Button id="t1" controls="p1" onClick={console.log}>
            Tab A
          </Tab.Button>
          <Tab.Button id="t2" controls="p2" onClick={console.log}>
            Tab B
          </Tab.Button>
          <Tab.Button id="t3" controls="p3" onClick={console.log}>
            Tab C
          </Tab.Button>
        </Tab.List>
        <Tab.Panel id="p1">Scope of Tab A</Tab.Panel>
        <Tab.Panel id="p2">Scope of Tab B</Tab.Panel>
        <Tab.Panel id="p3">Scope of Tab C</Tab.Panel>
      </Tab>
    </Flex>
  );
}

export default App;
```

> [!TIP]
>
> **Do**

tab-error-dont

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/tab-error-dont?file=src/App.tsx)

```tsx lines theme={null}
import { Tab, Flex, Icon } from "@servicetitan/anvil2";
import { core } from "@servicetitan/anvil2/token";
import Warning from "@servicetitan/anvil2/assets/icons/material/round/warning.svg";

function App() {
  return (
    <Flex gap={2}>
      <Tab defaultIndex={0} style={{ minWidth: "55rem" }}>
        <Tab.List>
          <Tab.Button id="t1" controls="p1" onClick={console.log}>
            Tab A
          </Tab.Button>
          <Tab.Button id="t2" controls="p2" onClick={console.log}>
            Tab B
          </Tab.Button>
          <Tab.Button id="t3" controls="p3" onClick={console.log}>
            <Flex gap={2} alignItems="center">
              Tab C
              <Icon
                size="medium"
                svg={Warning}
                color={core.semantic?.StatusColorDanger?.value}
              />
            </Flex>
          </Tab.Button>
        </Tab.List>
      </Tab>
    </Flex>
  );
}

export default App;
```

> [!CAUTION]
>
> **Don't**

#### Nested Tabs

Use caution when nesting Tabs within Tabs, as they can be difficult to use simultaneously. Consider alternative ways of organizing information, such as a Side Nav, Details, or Stepper.

## Content

Content within the Tab should clearly communicate each section through descriptive labels.

## Keyboard Interaction

Users can navigate the Tab using standard keyboard controls.

| Key | Description |
| - | - |
| Tab | Moves focus to the next focusable element outside of the Tab Group. |
| Right Arrow | Moves focus to the next Tab. If on the last Tab in the group, it focuses on the first Tabbed element. |
| Left Arrow | Moves focus to the previous Tab. If on the first Tab in the group, it focuses on the right Tabbed element. |
| Space or Enter | Selects the focused Tab. |

#### Manual activation only

Tab groups only support manual tab activation, meaning keyboard users can cycle through tab focus, but must also activate the tab with a separate key.

### Accessibility

Anvil provides all the accessibility pieces needed. There are some additional accessibility considerations if new behavior is added on top of the design system.

#### Automatic tab activation

This refers to tabs being automatically activates on focus. This is considered an acceptable behavior, but is not provided by Anvil. If a design needs this feature, consider following the [W3C ARIA implementation of automatic activation](https://www.w3.org/WAI/ARIA/apg/patterns/tabs/examples/tabs-automatic/), including how it handles keyboard interaction. Automatic tabs do not need a new visual treatment.

#### Use caution when adding actions in Tabs

Be sure that when adding an action of any kind to Tabs that it is still reachable by keyboard. This is especially true if your Tab action also uses arrow keys.

For more guidance on custom tab behaviors and keyboard interaction, see [changing content best practices](https://anvil.servicetitan.com/docs/accessibility/changing-content.md).

## Code

**Implementation**

tab-playground

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/tab-playground?file=src/App.tsx)

```tsx lines expandable theme={null}
import { Tab } from "@servicetitan/anvil2";

function App() {
  return (
    <Tab defaultIndex={0} style={{ minWidth: "55rem" }}>
      <Tab.List>
        <Tab.Button id="t-invoices" controls="p-invoices">
          Invoices
        </Tab.Button>
        <Tab.Button id="t-payments" controls="p-payments">
          Payments
        </Tab.Button>
        <Tab.Button id="t-vendor-bills" controls="p-vendor-bills">
          Vendor Bills
        </Tab.Button>
      </Tab.List>
      <Tab.Panel id="p-invoices">Invoices Content...</Tab.Panel>
      <Tab.Panel id="p-payments">Payments Content...</Tab.Panel>
      <Tab.Panel id="p-vendor-bills">Vendor Bills Content...</Tab.Panel>
    </Tab>
  );
}

export default App;
```

## Common Examples

```tsx theme={null}
import { Tab } from "@servicetitan/anvil2";

function ExampleComponent() {
  return (
    <Tab>
      <Tab.List>
        <Tab.Button id="t-1" controls="p-1">
          Tab 1
        </Tab.Button>
        <Tab.Button id="t-2" controls="p-2">
          Tab 2
        </Tab.Button>
      </Tab.List>
      <Tab.Panel id="p-1">Tab 1 content...</Tab.Panel>
      <Tab.Panel id="p-2">Tab 2 content...</Tab.Panel>
    </Tab>
  );
}
```

### Basic Tabs

Tabs are used to separate large blocks of content relevant to the user's current context. Four components are used to makeup Anvil tabs: `Tab`, `Tab.List`, `Tab.Button`, and `Tab.Panel`.

tab-components

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/tab-components?file=src/App.tsx)

```tsx lines expandable theme={null}
import { Tab } from "@servicetitan/anvil2";

function App() {
  return (
    <Tab defaultIndex={0} style={{ minWidth: "55rem" }}>
      <Tab.List>
        <Tab.Button id="t1" controls="p1">
          First
        </Tab.Button>
        <Tab.Button id="t2" controls="p2">
          Second
        </Tab.Button>
        <Tab.Button id="t3" controls="p3">
          Third
        </Tab.Button>
      </Tab.List>
      <Tab.Panel id="p1">Content 1</Tab.Panel>
      <Tab.Panel id="p2">Content 2</Tab.Panel>
      <Tab.Panel id="p3">Content 3</Tab.Panel>
    </Tab>
  );
}

export default App;
```

#### Notes

* There should always be an equal number of `Tab.Button` and `Tab.Panel` components.
* Values in the `controls` prop of each `Tab.Button` component should match the `id` of the corresponding `Tab.Panel`.

### Changing the Default Tab

By default, the content of the `Tab.Panel` associated to the first `Tab.Button` will be displayed. To change this behavior, supply the index of the `Tab.Button` that should be displayed first to the `defaultIndex` of the parent `Tab`.

tab-defaultindex

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/tab-defaultindex?file=src/App.tsx)

```tsx lines expandable theme={null}
import { Tab } from "@servicetitan/anvil2";

function App() {
  return (
    <Tab defaultIndex={1} style={{ minWidth: "55rem" }}>
      <Tab.List>
        <Tab.Button id="t1" controls="p1">
          First
        </Tab.Button>
        <Tab.Button id="t2" controls="p2">
          Second
        </Tab.Button>
      </Tab.List>
      <Tab.Panel id="p1">Content 1</Tab.Panel>
      <Tab.Panel id="p2">Content 2</Tab.Panel>
    </Tab>
  );
}

export default App;
```

### Controlled Tabs

To control the active tab from outside the component, use the `index` and `onIndexChange` props. This is useful when you need to synchronize the active tab with external state, such as a URL parameter or another UI element.

tab-controlled

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/tab-controlled?file=src/App.tsx)

```tsx lines expandable theme={null}
import { useState } from "react";
import { Tab, Button, Flex } from "@servicetitan/anvil2";

function App() {
  const [activeIndex, setActiveIndex] = useState(0);

  return (
    <Flex direction="column" gap="4" style={{ minWidth: "55rem" }}>
      <Flex gap="2">
        <Button onClick={() => setActiveIndex(0)}>Show Invoices</Button>
        <Button onClick={() => setActiveIndex(1)}>Show Payments</Button>
        <Button onClick={() => setActiveIndex(2)}>Show Vendor Bills</Button>
      </Flex>
      <Tab index={activeIndex} onIndexChange={setActiveIndex}>
        <Tab.List>
          <Tab.Button id="t-invoices" controls="p-invoices">
            Invoices
          </Tab.Button>
          <Tab.Button id="t-payments" controls="p-payments">
            Payments
          </Tab.Button>
          <Tab.Button id="t-vendor-bills" controls="p-vendor-bills">
            Vendor Bills
          </Tab.Button>
        </Tab.List>
        <Tab.Panel id="p-invoices">Invoices Content...</Tab.Panel>
        <Tab.Panel id="p-payments">Payments Content...</Tab.Panel>
        <Tab.Panel id="p-vendor-bills">Vendor Bills Content...</Tab.Panel>
      </Tab>
    </Flex>
  );
}

export default App;
```

> [!NOTE]
>
> When using `index`, you must also provide `onIndexChange` to keep the active tab in sync when the user interacts with it directly. Without `onIndexChange`, the tab will appear frozen.

## React Accessibility

* An Anvil `Tab.Button` cannot be disabled.
* When a `Tab.Button` has focus, the left and right arrow keys and enter can be used to navigate tabs.

For more guidance on custom tab behaviors and keyboard interaction, see [changing content best practices](https://anvil.servicetitan.com/docs/accessibility/changing-content.md).

**Tab Props**

```tsx theme={null}
<Tab defaultIndex={0}>
  <Tab.List>
    <Tab.Button id="t-1" controls="p-1">
      Tab 1
    </Tab.Button>
  </Tab.List>
  <Tab.Panel id="p-1">Content</Tab.Panel>
</Tab>
```

## `Tab` Props

**defaultIndex** `number` (default: 0)

The index of the tab that should be active by default. Use for uncontrolled usage. For controlled usage, use `index` instead.

**fill** `boolean` (default: false)

Whether the tabs should fill the available width.

**index** `number`

The index of the currently active tab. Use for controlled usage. For uncontrolled usage, use `defaultIndex` instead.

**onIndexChange** `(index: number) =\> void`

Callback fired when the active tab changes. Use with `index` for controlled usage.

**Tab.List Props**

```tsx theme={null}
<Tab.List>
  <Tab.Button id="t-1" controls="p-1">
    Tab 1
  </Tab.Button>
</Tab.List>
```

## `Tab.List` Props

The `Tab.List` component accepts any valid HTML `div` props, and does not have any custom props.

**Tab.Button Props**

```tsx theme={null}
<Tab.Button
  id="t-1"
  controls="p-1"
  onClick={(e, index) => console.log("Clicked", index)}
>
  Tab 1
</Tab.Button>
```

## `Tab.Button` Props

The `Tab.Button` component accepts any valid HTML `button` props, other than `disabled`, as well as the following:

**controls** `string` *required*

`id` of the `Tab.Panel` that this button controls.

**id** `string` *required*

Unique identifier for the tab button.

**onClick** `(e: MouseEvent, index?: number) =\> void`

**Tab.Panel Props**

```tsx theme={null}
<Tab.Panel id="p-1">Tab content</Tab.Panel>
```

## `Tab.Panel` Props

The `Tab.Panel` component accepts any valid HTML `div` props in addition to what is listed below.

**id** `string` *required*

Unique identifier for the tab panel. Used by the `controls` prop of the `Tab.Button` component to determine when to show this panel.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.