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, and this page is available as Markdown at https://ifhover.github.io/leaf-ui/v/0.6.0/en/components/tabs.md.
  • English
  • 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
    View project activity, members and recent updates.
    Card tabs
    View project activity, members and recent updates.
    Vertical tabs
    View project activity, members and recent updates.
    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>
      );
    }
    

    Sizes

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

    Line tabs
    Small
    View recent project updates.
    Default
    View recent project updates.
    Large
    View recent project updates.
    Card tabs
    Small
    View recent project updates.
    Default
    View recent project updates.
    Large
    View recent project updates.
    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>
      );
    }
    

    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 content
    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>
      );
    }
    

    Extended features

    Item 1 content
    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>
      );
    }
    

    Bottom and right placement

    Bottom placement
    Design settings
    Right placement
    Design settings
    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>
      );
    }
    

    Tabs API

    PropertyTypeDefaultDescription
    itemsreadonly TabItem[]RequiredTab items; an empty array hides the component
    activeKeystringFirst enabled itemControlled / initial active key
    defaultActiveKeystringFirst enabled itemControlled / 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
    destroyInactivebooleanfalseUnmount content when inactive
    extraReactNode—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
    addLabelstring—Accessible add button label
    overflowbooleantrueCollect overflow tabs in a menu
    sortablebooleanfalseEnable sorting; use with onReorder
    onReorder(items: TabItem[]) => void—Reordered tabs

    TabItem

    PropertyTypeDefaultDescription
    keystringRequiredRequired. Unique identifier
    labelReactNodeRequiredRequired. Tab label
    childrenReactNodeRequiredRequired. Tab content
    disabledbooleanfalseDisable interaction.
    iconReactNode—Leading icon
    closablebooleanfalseShow a close button; requires onClose

    Types

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

    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.