> Leaf UI 0.6.0 · Package: @ifhover/leaf-ui · Source: b30c3d3a8fa9c5e15d0d0522cebfbd33f60c6b06
> Browser documentation: https://ifhover.github.io/leaf-ui/v/0.6.0/en/components/tabs.html

> For AI agents: the complete documentation index is available at https://ifhover.github.io/leaf-ui/v/0.6.0/en/llms.txt, the full documentation bundle is available at https://ifhover.github.io/leaf-ui/v/0.6.0/en/llms-full.txt.

# Tabs

Switch between content views within the same region.

Content and panel height update immediately without transition animations. The tab bar's selection indicator keeps its movement animation.

The card-style tab bar fits its labels by default and scrolls within the bar when space is limited. `size` controls the entire tab bar; content panels use the available width.

## Basic usage


Line tabs

Overview

Settings

History

View project activity, members and recent updates.

Card tabs

Overview

Settings

History

View project activity, members and recent updates.

Vertical tabs

Overview

Settings

History

View project activity, members and recent updates.

basic.tsx

Copy code

```tsx lineNumbers file="../../../src/examples/en/tabs/basic.tsx"
import type { TabItem } from '@ifhover/leaf-ui';

import { Input, Space, Tabs } from '@ifhover/leaf-ui';

import { ChartNoAxesColumn, Settings } from 'lucide-react';

const items: readonly TabItem[] = [
  {
    key: 'overview',
    label: 'Overview',
    icon: <ChartNoAxesColumn />,
    children: 'View project activity, members and recent updates.',
  },
  {
    key: 'settings',
    label: 'Settings',
    icon: <Settings />,
    children: <Input aria-label="Project name" placeholder="Type and switch; your draft is kept" />,
  },
  { key: 'history', label: 'History', disabled: true, children: null },
];

export function TabsBasic() {
  return (
    <Space direction="vertical" align="stretch">
      <Space direction="vertical" align="stretch" size="sm">
        <span>Line tabs</span>
        <Tabs aria-label="Project information" items={items} />
      </Space>
      <Space direction="vertical" align="stretch" size="sm">
        <span>Card tabs</span>
        <Tabs aria-label="Card tabs" items={items} type="card" />
      </Space>
      <Space direction="vertical" align="stretch" size="sm">
        <span>Vertical tabs</span>
        <Tabs aria-label="Vertical tabs" items={items} placement="left" />
      </Space>
    </Space>
  );
}
```

Expand code

## Sizes

Compare small, default and large sizes for both line and card tabs.


Line tabs

Small

Overview

Members

View recent project updates.

Default

Overview

Members

View recent project updates.

Large

Overview

Members

View recent project updates.

Card tabs

Small

Overview

Members

View recent project updates.

Default

Overview

Members

View recent project updates.

Large

Overview

Members

View recent project updates.

sizes.tsx

Copy code

```tsx lineNumbers file="../../../src/examples/en/tabs/sizes.tsx"
import { Grid, Space, Tabs, Text } from '@ifhover/leaf-ui';

const items = [
  { key: 'overview', label: 'Overview', children: 'View recent project updates.' },
  { key: 'members', label: 'Members', children: 'Manage project members and access.' },
];

const labels = { sm: 'Small', md: 'Default', lg: 'Large' };

const variants = [
  { type: 'line', label: 'Line tabs' },
  { type: 'card', label: 'Card tabs' },
] as const;

export function TabsSizes() {
  return (
    <Grid columns={{ xs: 1, sm: 2 }} gap={24}>
      {variants.map(({ type, label }) => (
        <Space key={type} direction="vertical" align="stretch" size="lg">
          <Text strong>{label}</Text>
          {(['sm', 'md', 'lg'] as const).map((size) => (
            <Space key={size} direction="vertical" align="stretch" size="sm">
              <span>{labels[size]}</span>
              <Tabs
                aria-label={`${label} · ${labels[size]}`}
                items={items}
                type={type}
                size={size}
              />
            </Space>
          ))}
        </Space>
      ))}
    </Grid>
  );
}
```

Expand code

## Closable tabs

Use the plus button to add a tab. Closing every tab hides the component; the example's add button can reopen it.


First project

Second project

First project content

closable.tsx

Copy code

```tsx lineNumbers file="../../../src/examples/en/tabs/closable.tsx"
import { Button, Space, type TabItem, Tabs } from '@ifhover/leaf-ui';

import { useRef, useState } from 'react';

export function TabsClosable() {
  const sequence = useRef(2);
  const [items, setItems] = useState<TabItem[]>([
    { key: '1', label: 'First project', children: 'First project content', closable: true },
    { key: '2', label: 'Second project', children: 'Second project content', closable: true },
  ]);
  const addTab = () => {
    const key = String(++sequence.current);
    setItems((list) => [
      ...list,
      { key, label: `Project ${key}`, children: `Project ${key} content`, closable: true },
    ]);
  };
  return (
    <Space direction="vertical" align="stretch">
      <Tabs
        aria-label="Closable tabs"
        items={items}
        type="card"
        onClose={(key) => setItems((list) => list.filter((item) => item.key !== key))}
        onAdd={addTab}
        addLabel="Add tab"
      />
      {items.length === 0 && (
        <Space>
          <Button variant="outline" onClick={addTab}>
            Add tab
          </Button>
        </Space>
      )}
    </Space>
  );
}
```

Expand code

## Extended features


Item 1

Item 2

Item 3

Item 4

Item 5

Item 6

Item 1 content

extensions.tsx

Copy code

```tsx lineNumbers file="../../../src/examples/en/tabs/extensions.tsx"
import { Button, Space, type TabItem, Tabs } from '@ifhover/leaf-ui';

import { useRef, useState } from 'react';

export function ExtensionDemo() {
  const count = useRef(7);
  const [items, setItems] = useState<TabItem[]>(
    Array.from({ length: 6 }, (_, i) => ({
      key: String(i),
      label: `Item ${i + 1}`,
      children: `Item ${i + 1} content`,
      closable: true,
    })),
  );
  const addTab = () => {
    const id = String(count.current++);
    setItems((current) => [
      ...current,
      { key: id, label: `Item ${id}`, children: 'New item', closable: true },
    ]);
  };
  return (
    <Space direction="vertical" align="stretch">
      <Tabs
        aria-label="Sortable tabs"
        items={items}
        sortable
        onReorder={setItems}
        onAdd={addTab}
        onClose={(key) => setItems((current) => current.filter((item) => item.key !== key))}
      />
      {items.length === 0 && (
        <Space>
          <Button variant="outline" onClick={addTab}>
            Add tab
          </Button>
        </Space>
      )}
    </Space>
  );
}
```

Expand code

## Bottom and right placement


Bottom placement

Design

Tokens

Design settings

Right placement

Design

Tokens

Design settings

capabilities.tsx

Copy code

```tsx lineNumbers file="../../../src/examples/tabs/capabilities.tsx"
import { Space, Tabs } from '@ifhover/leaf-ui';

const items = [
  { key: 'design', label: 'Design', children: 'Design settings' },
  { key: 'tokens', label: 'Tokens', children: 'Theme tokens' },
];

export function Capabilities() {
  return (
    <Space direction="vertical" align="stretch">
      <Space direction="vertical" align="stretch" size="sm">
        <span>{'Bottom placement'}</span>
        <Tabs
          aria-label={'Bottom tabs'}
          placement="bottom"
          items={items}
        />
      </Space>
      <Space direction="vertical" align="stretch" size="sm">
        <span>{'Right placement'}</span>
        <Tabs aria-label={'Right tabs'} placement="right" items={items} />
      </Space>
    </Space>
  );
}
```

Expand code

## Tabs API

| Property           | Type                                                          | Default            | Description                                                                                 |
| ------------------ | ------------------------------------------------------------- | ------------------ | ------------------------------------------------------------------------------------------- |
| `items`            | `readonly `[`TabItem`](#tabitem)`[]`                          | Required           | Tab items; an empty array hides the component                                               |
| `activeKey`        | `string`                                                      | First enabled item | Controlled / initial active key                                                             |
| `defaultActiveKey` | `string`                                                      | First enabled item | Controlled / initial active key                                                             |
| `onChange`         | `(key: string) => void`                                       | —                  | Active key change                                                                           |
| `type`             | `'line' \| 'card'`                                            | `'line'`           | Tab appearance                                                                              |
| `placement`        | `'top' \| 'bottom' \| 'left' \| 'right'`                      | `'top'`            | Tab bar placement                                                                           |
| `size`             | `'sm' \| 'md' \| 'lg'`                                        | `'md'`             | Tab size; the card style includes the tab bar padding and border                            |
| `activationMode`   | `'automatic' \| 'manual'`                                     | `'automatic'`      | Activate on arrow navigation / Enter or Space                                               |
| `destroyInactive`  | `boolean`                                                     | `false`            | Unmount content when inactive                                                               |
| `extra`            | `ReactNode`                                                   | —                  | Extra tab bar content                                                                       |
| `onClose`          | `(key: string, event: MouseEvent<HTMLButtonElement>) => void` | —                  | Close request: remove the item in the application; preventDefault cancels selection changes |
| `onAdd`            | `() => void`                                                  | —                  | Add tab; enables add button                                                                 |
| `addLabel`         | `string`                                                      | —                  | Accessible add button label                                                                 |
| `overflow`         | `boolean`                                                     | `true`             | Collect overflow tabs in a menu                                                             |
| `sortable`         | `boolean`                                                     | `false`            | Enable sorting; use with onReorder                                                          |
| `onReorder`        | `(items: `[`TabItem`](#tabitem)`[]) => void`                  | —                  | Reordered tabs                                                                              |

## TabItem

| Property   | Type        | Default  | Description                           |
| ---------- | ----------- | -------- | ------------------------------------- |
| `key`      | `string`    | Required | Required. Unique identifier           |
| `label`    | `ReactNode` | Required | Required. Tab label                   |
| `children` | `ReactNode` | Required | Required. Tab content                 |
| `disabled` | `boolean`   | `false`  | Disable interaction.                  |
| `icon`     | `ReactNode` | —        | Leading icon                          |
| `closable` | `boolean`   | `false`  | Show a close button; requires onClose |

## Types

Import the component props and item types when preparing your own values.

```ts
import type { TabsProps, TabItem } from '@ifhover/leaf-ui';
```

## Usage notes

Content mounts on first visit. Visited panels and their input state are kept by default. Arrow keys skip disabled tabs, and Home / End move to the first / last tab. Use manual activation when changing content takes time.
