# Tabs

An accessible tabbed interface for organizing content into multiple panels, showing one section at a time. Built with React Aria hooks for full keyboard navigation, screen reader support, and proper ARIA semantics.

## When to Use

- To organize related content into distinct sections within a single page
- For settings panels with multiple categories
- For navigation between different views without page reload
- When content is mutually exclusive and only one section should be visible at a time
- For file-like interfaces where multiple documents can be open

## Component

---

### Properties

- **`activeKey`** `Key` — Controlled active tab key
- **`defaultActiveKey`** `Key` — Initial active tab key for uncontrolled mode
- **`type`** `'default' | 'narrow' | 'file' | 'radio'` (default: `'default'`) — Visual style variant
- **`size`** `'xsmall' | 'small' | 'medium' | 'large'` (default: `'small'`) — Tab size
- **`prerender`** `boolean` (default: `false`) — If true, all tab panels are rendered but hidden
- **`keepMounted`** `boolean` (default: `false`) — If true, visited tab panels stay in DOM
- **`label`** `string` (default: `'Tabs'`) — Accessible label for the tab list
- **`prefix`** `ReactNode` — Content rendered before the tab list
- **`suffix`** `ReactNode` — Content rendered after the tab list
- **`onChange`** `(key: Key) => void` — Callback when active tab changes
- **`onDelete`** `(key: Key) => void` — Callback when tab delete button is clicked. Presence enables delete buttons
- **`isEditable`** `boolean` (default: `false`) — Whether tab titles can be inline-edited
- **`onTitleChange`** `(key: Key, newTitle: string) => void` — Callback when a tab title is edited
- **`autoHideActions`** `boolean` — Show tab actions only on hover
- **`menu`** `ReactNode` — Menu items for all tabs (use `Menu.Item` children)
- **`menuTriggerProps`** `Partial<CubeItemActionProps>` — Props for the menu trigger button
- **`menuProps`** `Partial<CubeMenuProps>` — Props for the Menu component
- **`contextMenu`** `boolean | 'context-only'` (default: `false`) — `false`: overflow trigger only; `true`: overflow + right-click; `'context-only'`: right-click only (inline close still shown when `onDelete` is set)
- **`onAction`** `(action: string, tabKey: string) => void` — Callback when a menu action is triggered
- **`renderPanel`** `(key: string) => ReactNode` — Render function for lazy panel content
- **`panelCacheKeys`** `Record<string, any>` — Cache keys for `renderPanel` content
- **`isReorderable`** `boolean` (default: `false`) — Enable drag-and-drop tab reordering
- **`keyOrder`** `string[]` — Controlled tab display order
- **`onReorder`** `(newOrder: string[]) => void` — Callback when tabs are reordered
- **`showTabPicker`** `boolean | 'auto'` (default: `false`) — Show dropdown tab picker
- **`showScrollArrows`** `boolean | 'auto'` (default: `false`) — Show scroll navigation arrows
- **`tabPickerPosition`** `'prefix' | 'suffix'` (default: `suffix`) — Position of the tab picker
- **`scrollArrowsPosition`** `'prefix' | 'suffix'` (default: `suffix`) — Position of the scroll arrows
- **`hideTabListScroll`** `boolean` (default: `false`) — Visually hide the custom horizontal scrollbar in the tab list and skip its tracking logic. Does not affect tab picker, scroll arrows, or fade indicators.
- **`tabListPadding`** `string` — Inline padding for the tab list container (e.g., `"2x"`)
- **`tabListStyles`** `Styles` — Custom styles for the tab list container
- **`prefixStyles`** `Styles` — Custom styles for the prefix slot
- **`suffixStyles`** `Styles` — Custom styles for the suffix slot

### Base Properties

Supports [Base properties](../../BaseProperties.md)

### Styling Properties

#### styles

Customizes the root element of the Tabs component.

**Sub-elements:**

- `TabList` - The container holding the tab buttons
- `Prefix` - Content rendered before the tab list
- `Suffix` - Content rendered after the tab list
- `ScrollWrapper` - Wrapper for scroll area and scrollbar positioning
- `Scroll` - Scrollable area containing the tab container
- `ScrollbarH` - Custom horizontal scrollbar indicator

### Style Properties

These properties allow direct style application without using the `styles` prop:

- **Position:** `gridArea`, `order`, `gridColumn`, `gridRow`, `placeSelf`, `alignSelf`, `justifySelf`, `zIndex`, `margin`, `inset`, `position`
- **Dimension:** `width`, `height`, `flexBasis`, `flexGrow`, `flexShrink`, `flex`
- **Block:** `border`, `radius`, `shadow`, `outline`

### Modifiers

The `mods` property accepts the following modifiers:

- **`type`** `'default' \| 'narrow' \| 'file' \| 'radio'` — Current tab type
- **`deletable`** `boolean` — True when onDelete callback is provided
- **`scrolling`** `boolean` — True when tabs are being scrolled
- **`fade-left`** `boolean` — True when content is scrolled (left fade visible)
- **`fade-right`** `boolean` — True when more content exists to the right
- **`has-panels`** `boolean` — True when tabs have panel content

For individual tabs:

- **`type`** `'default' \| 'narrow' \| 'file' \| 'radio'` — Current tab type
- **`active`** `boolean` — True when the tab is selected
- **`deletable`** `boolean` — True when delete button is visible
- **`disabled`** `boolean` — True when tab is disabled
- **`editing`** `boolean` — True when tab title is being edited
- **`hovered`** `boolean` — True when tab is hovered
- **`focused`** `boolean` — True when tab has focus
- **`focus-visible`** `boolean` — True when tab has keyboard focus
- **`draggable`** `boolean` — True when tab can be dragged (reorderable)
- **`dragging`** `boolean` — True when tab is being dragged

## Variants

### Types

- `default` - Standard tabs with selection indicator below (default)
- `narrow` - Same as default but with collapsed horizontal label padding for compact layouts
- `file` - File-style tabs with border bottom highlight on selection, delimiter between tabs
- `radio` - Radio button style for tab selection

### Sizes

- `xsmall` - Extra small size with t4 typography
- `small` - Small size with t3m typography (default)
- `medium` - Default size with t3m typography
- `large` - Larger tabs with t3m typography

**Note:** Radio type supports `large` (default, 40px total) and `medium` (32px total) sizes, mapped to Item button sizes `medium` (32px) and `xsmall` (24px) respectively.

## Compound Components

### Tab

Each `Tab` renders an [Item](../content/Item.md) internally, so it inherits most Item props — `icon`, `rightIcon`, `prefix`, `suffix`, `tooltip`, `isLoading`, `loadingSlot`, and all Item style props (like `width`, `fill`, `padding`, etc.). Props that don't apply to tabs (`description`, `hotkeys`, `theme`, `shape`, `variant`, `highlight`, etc.) are omitted.

```jsx
<Tab key="unique-id" title="Tab Title">
  Panel content here
</Tab>
```

#### Tab with icon

```jsx
<Tabs type="radio" defaultActiveKey="daily">
  <Tab key="daily" icon={<IconCalendar />} title="Daily" />
  <Tab key="weekly" title="Weekly" />
  <Tab key="monthly" title="Monthly" />
</Tabs>
```

#### Tab with constrained width

The `width` style prop (inherited from Item) is useful for file-style tabs where titles may be long.

```jsx
<Tabs isEditable type="file" onTitleChange={handleTitleChange}>
  <Tab key="tab1" title="A Very Long Document Name.tsx" width="max 30x">
    Content
  </Tab>
</Tabs>
```

### Tabs.List

Optional wrapper for tabs when using explicit panel placement.

```jsx
<Tabs.List>
  <Tab key="tab1" title="Tab 1" />
  <Tab key="tab2" title="Tab 2" />
</Tabs.List>
```

### Tabs.Panel

Explicit panel definition for advanced layout control.

```jsx
<Tabs.Panel key="tab1">
  Panel 1 content
</Tabs.Panel>
```

### Tabs.Action

Action button designed for the Tabs prefix/suffix slots. Styled with sharp edges and no border to match the TabPicker trigger. When multiple `Tabs.Action` components are placed together, they automatically display dividers between them.

```jsx
<Tabs
  suffix={
    <>
      <Tabs.Action icon={<PlusIcon />} onPress={handleAdd} />
      <Tabs.Action icon={<SettingsIcon />} onPress={handleSettings} />
    </>
  }
>
  <Tab key="tab1" title="Tab 1">Content</Tab>
</Tabs>
```

- **`icon`** `ReactNode` — Icon to display in the action button
- **`size`** `TabSize` — Override size (inherits from Tabs by default)
- **`onPress`** `() => void` — Callback when the action is pressed
- **`aria-label`** `string` — Accessible label for the button

Inherits all props from `ItemButton` except `shape` (always sharp).

### Tab Props

`Tab` extends [Item](../content/Item.md), so it accepts all Item props except those that don't apply to tabs (`description`, `hotkeys`, `theme`, `shape`, `variant`, `highlight`, `isSelected`, `htmlType`). This includes `icon`, `rightIcon`, `prefix`, `suffix`, `tooltip`, `isLoading`, `loadingSlot`, and all style props.

In addition, Tab accepts these tab-specific props:

- **`title`** `ReactNode` — Content displayed in the tab button (required)
- **`isDisabled`** `boolean` — Disables the tab
- **`size`** `TabSize` — Override tab size for this tab
- **`type`** `TabType` — Override tab type for this tab
- **`actions`** `ReactNode` — Custom actions to render in the tab
- **`autoHideActions`** `boolean` — Show actions only on hover (overrides Tabs-level)
- **`isEditable`** `boolean` — Whether the tab title can be edited (overrides Tabs-level)
- **`onTitleChange`** `(newTitle: string) => void` — Callback when title changes (overrides Tabs-level)
- **`menu`** `ReactNode \| null` — Menu items for this tab (overrides Tabs-level, `null` disables)
- **`menuTriggerProps`** `Partial<CubeItemActionProps>` — Props for menu trigger button
- **`menuProps`** `Partial<CubeMenuProps>` — Props for Menu component
- **`contextMenu`** `boolean | 'context-only'` — Same as Tabs `contextMenu`
- **`onAction`** `(action: Key) => void` — Callback when menu action is triggered
- **`prerender`** `boolean` — Override panel prerender behavior
- **`keepMounted`** `boolean` — Override panel keepMounted behavior

## Examples

### Basic Usage

```jsx
<Tabs defaultActiveKey="overview">
  <Tab key="overview" title="Overview">
    Overview content goes here.
  </Tab>
  <Tab key="settings" title="Settings">
    Settings content goes here.
  </Tab>
</Tabs>
```

### Controlled Tabs

Control the active tab externally with `activeKey` and `onChange`.

```jsx
const [activeKey, setActiveKey] = useState('tab1');

<Tabs activeKey={activeKey} onChange={setActiveKey}>
  <Tab key="tab1" title="Tab 1">Content 1</Tab>
  <Tab key="tab2" title="Tab 2">Content 2</Tab>
</Tabs>
```

### Disabled Tab

```jsx
<Tabs defaultActiveKey="tab1">
  <Tab key="tab1" title="Active Tab">Content</Tab>
  <Tab key="tab2" title="Disabled Tab" isDisabled>
    Inaccessible content
  </Tab>
</Tabs>
```

### With Prefix and Suffix

Add buttons or other elements before or after the tab list.

```jsx
<Tabs
  defaultActiveKey="items"
  prefix={<Button size="small">Menu</Button>}
  suffix={<Button size="small">Add New</Button>}
>
  <Tab key="items" title="Items">Item list</Tab>
  <Tab key="archived" title="Archived">Archived items</Tab>
</Tabs>
```

### Deletable Tabs

Enable delete buttons with the `onDelete` callback. When no `menu` is defined, a delete button appears on each tab. Use `Tabs.Action` in the suffix slot to add an "Add" button.

```jsx
const [tabs, setTabs] = useState([
  { key: 'tab1', title: 'Tab 1' },
  { key: 'tab2', title: 'Tab 2' },
]);

<Tabs
  defaultActiveKey="tab1"
  onDelete={(key) => setTabs(tabs.filter(t => t.key !== key))}
  suffix={
    <Tabs.Action
      icon={<PlusIcon />}
      aria-label="Add tab"
      onPress={handleAdd}
    />
  }
>
  {tabs.map(tab => (
    <Tab key={tab.key} title={tab.title}>
      Content for {tab.title}
    </Tab>
  ))}
</Tabs>
```

### Tab Menus

Add dropdown menus to tabs using the `menu` prop. This replaces the need for manually composing `MenuTrigger`, `ItemAction`, and `Menu` components.

```jsx
<Tabs
  onDelete={handleDelete}
  menu={
    <>
      <Menu.Item key="duplicate">Duplicate</Menu.Item>
      <Menu.Item key="delete">Delete</Menu.Item>
    </>
  }
  onAction={(action, tabKey) => {
    if (action === 'duplicate') handleDuplicate(tabKey);
    // 'delete' is handled automatically via onDelete
  }}
>
  <Tab key="tab1" title="Tab 1">Content</Tab>
</Tabs>
```

**Key features:**

- **Predefined actions**: `key="rename"` and `key="delete"` are handled automatically
- **Auto-labels**: Predefined actions get default labels if children not provided
- **Tabs-level menu**: Define menu once, all tabs inherit it
- **Tab-level override**: Individual tabs can override or disable menu with `menu={null}`
- **Delete button logic**: When `menu` is present, delete button is hidden (use `key="delete"` in menu instead)

### Editable Tabs with Menu

Combine editable tabs with a menu for rename, duplicate, and delete actions.

```jsx
<Tabs
  isEditable
  onDelete={handleDelete}
  onTitleChange={handleTitleChange}
  menu={
    <>
      <Menu.Item key="rename">Rename</Menu.Item>
      <Menu.Item key="duplicate">Duplicate</Menu.Item>
      <Menu.Item key="delete">Delete</Menu.Item>
    </>
  }
  onAction={(action, tabKey) => {
    if (action === 'duplicate') handleDuplicate(tabKey);
    // 'rename' triggers inline editing automatically
    // 'delete' triggers onDelete automatically
  }}
>
  <Tab key="tab1" title="Tab 1">Content</Tab>
</Tabs>
```

### Context Menu

Enable right-click context menus with the `contextMenu` prop. Uses the same menu items as `menu`. With `contextMenu={true}`, the ⋮ overflow trigger stays visible. With `contextMenu="context-only"`, only right-click (and Shift+F10) opens the menu; the actions row behaves like tabs without an overflow menu (e.g. inline close when `onDelete` is set).

```jsx
<Tabs
  contextMenu
  menu={
    <>
      <Menu.Item key="rename">Rename</Menu.Item>
      <Menu.Item key="delete">Delete</Menu.Item>
    </>
  }
>
  <Tab key="tab1" title="Tab 1">Content</Tab>
</Tabs>
```

```jsx
<Tabs
  isEditable
  contextMenu="context-only"
  onDelete={handleClose}
  menu={
    <>
      <Menu.Item key="rename">Rename</Menu.Item>
    </>
  }
>
  <Tab key="tab1" title="Tab 1">Content</Tab>
</Tabs>
```

### Per-Tab Menu Override

Override or disable menus on individual tabs.

```jsx
<Tabs
  menu={
    <>
      <Menu.Item key="rename">Rename</Menu.Item>
      <Menu.Item key="info">Show Info</Menu.Item>
    </>
  }
>
  <Tab key="tab1" title="Uses Default Menu">Content</Tab>
  <Tab 
    key="tab2" 
    title="Custom Menu"
    menu={
      <>
        <Menu.Item key="special">Special Action</Menu.Item>
      </>
    }
  >
    Content
  </Tab>
  <Tab key="tab3" title="No Menu" menu={null}>Content</Tab>
</Tabs>
```

### Menu with Sections

Use `Menu.Section` for organized menus.

```jsx
<Tabs
  menu={
    <>
      <Menu.Item key="rename">Rename</Menu.Item>
      <Menu.Item key="duplicate">Duplicate</Menu.Item>
      <Menu.Section title="Danger Zone">
        <Menu.Item key="delete">Delete</Menu.Item>
      </Menu.Section>
    </>
  }
>
  <Tab key="tab1" title="Tab 1">Content</Tab>
</Tabs>
```

### Reorderable Tabs

Enable drag-and-drop tab reordering with the `isReorderable` prop. Control the order via `keyOrder` and handle changes with `onReorder`.

```jsx
const [keyOrder, setKeyOrder] = useState(['tab1', 'tab2', 'tab3']);

<Tabs
  isReorderable
  keyOrder={keyOrder}
  onReorder={(newOrder) => setKeyOrder(newOrder)}
>
  <Tab key="tab1" title="Tab 1">Content 1</Tab>
  <Tab key="tab2" title="Tab 2">Content 2</Tab>
  <Tab key="tab3" title="Tab 3">Content 3</Tab>
</Tabs>
```

**Key features:**

- **Drag-and-drop**: Reorder tabs by dragging them to new positions
- **Controlled order**: Use `keyOrder` to control tab display order
- **Callback**: `onReorder` is called with the new key order after a drop
- **Accessibility**: Full keyboard support for reordering

### Reorderable Tabs with Menu

Combine drag-and-drop reordering with menu actions, editing, and delete functionality.

```jsx
const [tabs, setTabs] = useState([...]);
const [keyOrder, setKeyOrder] = useState(['tab1', 'tab2', 'tab3']);

<Tabs
  isReorderable
  isEditable
  keyOrder={keyOrder}
  onReorder={(newOrder) => setKeyOrder(newOrder)}
  onDelete={handleDelete}
  menu={
    <>
      <Menu.Item key="rename">Rename</Menu.Item>
      <Menu.Item key="duplicate">Duplicate</Menu.Item>
      <Menu.Item key="delete">Delete</Menu.Item>
    </>
  }
>
  {tabs.map((tab) => (
    <Tab key={tab.id} title={tab.title}>{tab.content}</Tab>
  ))}
</Tabs>
```

### Scroll Arrows

Add left/right navigation arrows to scroll overflowed tabs. Use `showScrollArrows` with `true`, `false`, or `'auto'` (shows only when tabs overflow).

```jsx
<Tabs
  defaultActiveKey="tab1"
  showScrollArrows="auto"
  styles={{ width: '500px' }}
>
  {tabs.map(tab => (
    <Tab key={tab.id} title={tab.title}>
      {tab.content}
    </Tab>
  ))}
</Tabs>
```

Combine with `showTabPicker` for both scroll arrows and a dropdown picker:

```jsx
<Tabs
  defaultActiveKey="tab1"
  showScrollArrows="auto"
  showTabPicker="auto"
  type="file"
>
  {tabs.map(tab => (
    <Tab key={tab.id} title={tab.title}>
      {tab.content}
    </Tab>
  ))}
</Tabs>
```

### Actions in Prefix

By default, scroll arrows and the tab picker appear in the suffix (after the tab list). Use `scrollArrowsPosition` and `tabPickerPosition` to move them to the prefix (before the tab list).

```jsx
<Tabs
  defaultActiveKey="tab1"
  showTabPicker
  showScrollArrows
  tabPickerPosition="prefix"
  scrollArrowsPosition="prefix"
  type="file"
  styles={{ width: '500px' }}
>
  {tabs.map(tab => (
    <Tab key={tab.id} title={tab.title}>
      {tab.content}
    </Tab>
  ))}
</Tabs>
```

When both are in the same slot:
- In **prefix**: TabPicker appears first (left), then scroll arrows (right)
- In **suffix**: Scroll arrows appear first (left), then TabPicker (right)

### Navigation-Only Tabs

Use tabs without panel content for navigation purposes.

```jsx
const [view, setView] = useState('overview');

<Tabs activeKey={view} onChange={setView}>
  <Tab key="overview" title="Overview" />
  <Tab key="settings" title="Settings" />
</Tabs>

{view === 'overview' && <OverviewPage />}
{view === 'settings' && <SettingsPage />}
```

### Explicit Panels

Use `Tabs.List` and `Tabs.Panel` for more control over panel placement.

```jsx
<Tabs defaultActiveKey="tab1">
  <Tabs.List>
    <Tab key="tab1" title="First" />
    <Tab key="tab2" title="Second" />
  </Tabs.List>
  <Tabs.Panel key="tab1">First panel content</Tabs.Panel>
  <Tabs.Panel key="tab2">Second panel content</Tabs.Panel>
</Tabs>
```

### Prerender Mode

Render all panels immediately but keep inactive ones hidden.

```jsx
<Tabs defaultActiveKey="tab1" prerender>
  <Tab key="tab1" title="Tab 1">Content 1</Tab>
  <Tab key="tab2" title="Tab 2">Content 2</Tab>
</Tabs>
```

### Keep Mounted Mode

Keep visited panels in the DOM to preserve their state.

```jsx
<Tabs defaultActiveKey="tab1" keepMounted>
  <Tab key="tab1" title="Tab 1">
    <FormWithState />
  </Tab>
  <Tab key="tab2" title="Tab 2">
    <AnotherForm />
  </Tab>
</Tabs>
```

## Panel Rendering Modes

- **Default** — Panel renders only when active, unmounts when inactive
- **`prerender`** — All panels render on mount, inactive ones hidden via `display: none`
- **`keepMounted`** — Panel renders when first activated, stays mounted thereafter

Override per-tab/panel:

```jsx
<Tabs prerender>
  <Tab key="tab1" title="Prerendered">Always in DOM</Tab>
  <Tab key="tab2" title="Lazy" prerender={false}>Lazy loaded</Tab>
</Tabs>
```

### Optimized Lazy Rendering with `renderPanel`

For tabs with expensive content, use the `renderPanel` prop for optimized lazy rendering.
This prevents content from being evaluated on every parent render - only the active tab's
content is computed, while inactive tabs use cached content.

```jsx
<Tabs
  defaultActiveKey="tab1"
  renderPanel={(key) => {
    switch (key) {
      case 'tab1': return <ExpensiveChart data={chartData} />;
      case 'tab2': return <LargeTable rows={tableRows} />;
      case 'tab3': return <ComplexForm fields={formFields} />;
      default: return null;
    }
  }}
>
  <Tab key="tab1" title="Charts" />
  <Tab key="tab2" title="Data Table" />
  <Tab key="tab3" title="Settings" />
</Tabs>
```

**How it works:**

1. By default, `renderPanel(key)` is called on every Tabs render (no caching)
2. Add `panelCacheKeys` to enable caching for specific panels
3. Cached content is reused until the cache key changes

```jsx
// Enable caching with dependencies as cache keys
<Tabs
  renderPanel={(key) => <Panel data={data[key]} />}
  panelCacheKeys={{
    tab1: data.tab1.version,  // Cached, re-renders when version changes
    tab2: data.tab2.version,  // Cached, re-renders when version changes
    // tab3 has no cache key - re-renders on every Tabs render
  }}
>
  ...
</Tabs>
```

This approach is ideal for:
- Tabs with complex visualizations or charts
- Large data tables that are expensive to render
- Forms with many fields
- Any content that benefits from render caching
- File Editor

### Inside Layout

Tabs work seamlessly inside Layout components. When placed in a `Layout` or `Layout.Content`, the tab panels automatically stretch to fill all remaining space below the tab bar. Use nested Layout components inside panels to create complex layouts.

```jsx
<Layout height="400px">
  <Layout.Content scrollbar="none" padding="0"> <!-- can be omitted if no other components are inside -->
    <Tabs defaultActiveKey="tab1">
      <Tab key="tab1" title="Dashboard">
        <Layout fill="#purple.15">
          <Layout.Content>
            Dashboard content - stretches to fill remaining space
          </Layout.Content>
        </Layout>
      </Tab>
      <Tab key="tab2" title="Settings">
        <Layout fill="#success.15">
          <Layout.Content>
            Settings content
          </Layout.Content>
        </Layout>
      </Tab>
    </Tabs>
  </Layout.Content>
</Layout>
```

**How it works:**
- The tab bar has fixed height and doesn't grow
- Tab panels use `display: contents`, allowing children to participate in parent flex layout
- Nested Layout components with `flexGrow: 1` (default) stretch to fill remaining space

## Accessibility

### Keyboard Navigation

- `Tab` - Move focus into/out of the tab list
- `ArrowLeft` / `ArrowRight` - Navigate between tabs
- `Home` - Move to first tab
- `End` - Move to last tab
- `Enter` / `Space` - Activate focused tab
- `Shift+F10` - Open tab menu (when menu is present)
- `Delete` - Delete focused tab (when deletable)
- `F2` - Start editing tab title (when editable)

### Screen Reader Support

- Tab list announces as "tablist" role with proper label
- Individual tabs announce as "tab" with selected state
- Panels announce as "tabpanel" linked to their tab
- Disabled tabs are announced as such

### ARIA Properties

- `aria-label` - Set via `label` prop on Tabs
- `aria-selected` - Managed automatically for active tab
- `aria-disabled` - Applied when `isDisabled` is true
- `aria-controls` - Links tabs to their panels automatically
- `aria-labelledby` - Links panels to their tabs automatically
- `aria-haspopup` - Set to "menu" when tab has a menu

## Best Practices

1. **Do**: Use clear, descriptive tab titles

   ```jsx
   <Tab key="billing" title="Billing & Payments">...</Tab>
   ```

2. **Don't**: Use vague or single-letter titles

   ```jsx
   <Tab key="b" title="B">...</Tab>
   ```

3. **Do**: Provide meaningful accessible labels

   ```jsx
   <Tabs label="Account Settings">...</Tabs>
   ```

4. **Performance**: Use `keepMounted` when tabs contain forms or stateful components

5. **Performance**: Use `renderPanel` for tabs with expensive content to avoid re-evaluation on every render

6. **Accessibility**: Ensure tab content is keyboard accessible

## Related Components

- [Disclosure](../content/Disclosure.md) - For collapsible content sections
- [Menu](../actions/Menu.md) - For dropdown navigation menus
