27.5k

Tabs

Migration guide for Tabs from HeroUI v2 to v3

Refer to the v3 Tabs documentation for complete API reference, styling guide, and advanced examples. This guide only focuses on migrating from HeroUI v2.

Overview

The Tabs component in HeroUI v3 has been redesigned with a compound component pattern, requiring explicit structure with Tabs.ListContainer, Tabs.List, Tabs.Tab, Tabs.Indicator, and Tabs.Panel components.

Structure Changes

v2: Tab Component with Title

In v2, Tabs used Tab component with title prop and children as panel content:

import { Tabs, Tab } from "@heroui/react";

<Tabs aria-label="Options">
  <Tab key="photos" title="Photos">
    <Card>Content here</Card>
  </Tab>
</Tabs>

v3: Compound Components

In v3, Tabs requires compound components with separate tab and panel:

import { Tabs } from "@heroui/react";

<Tabs>
  <Tabs.ListContainer>
    <Tabs.List aria-label="Options">
      <Tabs.Tab id="photos">
        Photos
        <Tabs.Indicator />
      </Tabs.Tab>
    </Tabs.List>
  </Tabs.ListContainer>
  <Tabs.Panel id="photos">
    <Card>Content here</Card>
  </Tabs.Panel>
</Tabs>

Key Changes

1. Component Structure

v2: Tabs with Tab children (title prop + children as panel)
v3: Compound components (Tabs.ListContainer, Tabs.List, Tabs.Tab, Tabs.Indicator, Tabs.Panel)

2. Prop Changes

v2 Propv3 PropNotes
key (on Tab)id (on Tab and Panel)Changed prop name
title (on Tab)-Removed (content goes directly in Tabs.Tab)
isVerticalorientationChanged to "horizontal" | "vertical"
placement-Removed (use orientation and layout)
variant-Removed (use Tailwind CSS)
color-Removed (use Tailwind CSS)
size-Removed (use Tailwind CSS)
radius-Removed (use Tailwind CSS)
classNames-Use className props
disableCursorAnimation-Removed (use Tabs.Indicator)
disableAnimation-Removed
fullWidth-Removed (use Tailwind CSS)

3. Removed Props

The following props are no longer available in v3:

  • variant - Use Tailwind CSS classes
  • color - Use Tailwind CSS classes
  • size - Use Tailwind CSS classes
  • radius - Use Tailwind CSS classes
  • placement - Use orientation prop and layout
  • classNames - Use className props on individual components
  • disableCursorAnimation - Use Tabs.Indicator component
  • disableAnimation - Animations handled differently
  • fullWidth - Use Tailwind CSS classes
  • title (on Tab) - Content goes directly in Tabs.Tab

4. Component Changes

  • Tab identification: keyid (must match between Tabs.Tab and Tabs.Panel)
  • Tab content: title prop → direct children in Tabs.Tab
  • Panel content: Tab children → separate Tabs.Panel component
  • Indicator: Automatic cursor → explicit Tabs.Indicator component

Migration Examples

Basic Usage

import { Tabs, Tab, Card, CardBody } from "@heroui/react";

export default function App() {
  return (
    <Tabs aria-label="Options">
      <Tab key="photos" title="Photos">
        <Card>
          <CardBody>
            Lorem ipsum dolor sit amet...
          </CardBody>
        </Card>
      </Tab>
      <Tab key="music" title="Music">
        <Card>
          <CardBody>
            Ut enim ad minim veniam...
          </CardBody>
        </Card>
      </Tab>
    </Tabs>
  );
}
import { Tabs } from "@heroui/react";

export default function App() {
  return (
    <Tabs className="w-full max-w-md">
      <Tabs.ListContainer>
        <Tabs.List aria-label="Options">
          <Tabs.Tab id="photos">
            Photos
            <Tabs.Indicator />
          </Tabs.Tab>
          <Tabs.Tab id="music">
            Music
            <Tabs.Indicator />
          </Tabs.Tab>
        </Tabs.List>
      </Tabs.ListContainer>
      <Tabs.Panel className="pt-4" id="photos">
        <Card>
          <CardBody>
            Lorem ipsum dolor sit amet...
          </CardBody>
        </Card>
      </Tabs.Panel>
      <Tabs.Panel className="pt-4" id="music">
        <Card>
          <CardBody>
            Ut enim ad minim veniam...
          </CardBody>
        </Card>
      </Tabs.Panel>
    </Tabs>
  );
}

Controlled Tabs

import { useState } from "react";

const [selected, setSelected] = useState("photos");

<Tabs selectedKey={selected} onSelectionChange={setSelected}>
  <Tab key="photos" title="Photos">Content</Tab>
  <Tab key="music" title="Music">Content</Tab>
</Tabs>
import { useState } from "react";

const [selected, setSelected] = useState("photos");

<Tabs selectedKey={selected} onSelectionChange={setSelected}>
  <Tabs.ListContainer>
    <Tabs.List aria-label="Options">
      <Tabs.Tab id="photos">
        Photos
        <Tabs.Indicator />
      </Tabs.Tab>
      <Tabs.Tab id="music">
        Music
        <Tabs.Indicator />
      </Tabs.Tab>
    </Tabs.List>
  </Tabs.ListContainer>
  <Tabs.Panel id="photos">Content</Tabs.Panel>
  <Tabs.Panel id="music">Content</Tabs.Panel>
</Tabs>

Vertical Tabs

<Tabs isVertical aria-label="Options">
  <Tab key="photos" title="Photos">Content</Tab>
  <Tab key="music" title="Music">Content</Tab>
</Tabs>
<Tabs orientation="vertical">
  <Tabs.ListContainer>
    <Tabs.List aria-label="Options">
      <Tabs.Tab id="photos">
        Photos
        <Tabs.Indicator />
      </Tabs.Tab>
      <Tabs.Tab id="music">
        Music
        <Tabs.Indicator />
      </Tabs.Tab>
    </Tabs.List>
  </Tabs.ListContainer>
  <Tabs.Panel id="photos">Content</Tabs.Panel>
  <Tabs.Panel id="music">Content</Tabs.Panel>
</Tabs>

Disabled Tab

<Tabs aria-label="Options">
  <Tab key="photos" title="Photos">Content</Tab>
  <Tab key="disabled" title="Disabled" isDisabled>Content</Tab>
</Tabs>
<Tabs>
  <Tabs.ListContainer>
    <Tabs.List aria-label="Options">
      <Tabs.Tab id="photos">
        Photos
        <Tabs.Indicator />
      </Tabs.Tab>
      <Tabs.Tab isDisabled id="disabled">
        Disabled
        <Tabs.Indicator />
      </Tabs.Tab>
    </Tabs.List>
  </Tabs.ListContainer>
  <Tabs.Panel id="photos">Content</Tabs.Panel>
  <Tabs.Panel id="disabled">Content</Tabs.Panel>
</Tabs>

With Icons

<Tabs aria-label="Options">
  <Tab key="photos" title={<><PhotoIcon /> Photos</>}>
    Content
  </Tab>
</Tabs>
<Tabs>
  <Tabs.ListContainer>
    <Tabs.List aria-label="Options">
      <Tabs.Tab id="photos">
        <PhotoIcon />
        Photos
        <Tabs.Indicator />
      </Tabs.Tab>
    </Tabs.List>
  </Tabs.ListContainer>
  <Tabs.Panel id="photos">Content</Tabs.Panel>
</Tabs>

Custom Styling

<Tabs
  variant="underlined"
  color="primary"
  size="lg"
  classNames={{
    base: "custom-base",
    tabList: "custom-tab-list",
    tab: "custom-tab"
  }}
>
  <Tab key="photos" title="Photos">Content</Tab>
</Tabs>
<Tabs className="custom-base">
  <Tabs.ListContainer>
    <Tabs.List className="custom-tab-list" aria-label="Options">
      <Tabs.Tab className="custom-tab" id="photos">
        Photos
        <Tabs.Indicator />
      </Tabs.Tab>
    </Tabs.List>
  </Tabs.ListContainer>
  <Tabs.Panel id="photos">Content</Tabs.Panel>
</Tabs>

Component Anatomy

The v3 Tabs follows this structure:

Tabs (Root)
  ├── Tabs.ListContainer
  │   └── Tabs.List
  │       └── Tabs.Tab
  │           └── Tabs.Indicator (optional)
  └── Tabs.Panel (one per tab, matching id)

Important Notes

Tab and Panel Matching

  • v2: Tab key matched automatically with panel content
  • v3: Tabs.Tab id must match Tabs.Panel id exactly

Indicator

  • v2: Cursor indicator was automatic (could be disabled with disableCursorAnimation)
  • v3: Must explicitly include Tabs.Indicator in each Tabs.Tab

Orientation

  • v2: Used isVertical boolean prop
  • v3: Uses orientation prop with "horizontal" or "vertical" values

Placement

  • v2: Had placement prop ("top", "bottom", "start", "end")
  • v3: placement removed - use orientation and layout with Tailwind CSS

Breaking Changes Summary

  1. Component Structure: Must use compound components (Tabs.ListContainer, Tabs.List, Tabs.Tab, Tabs.Indicator, Tabs.Panel)
  2. Tab Identification: keyid (must match between tab and panel)
  3. Tab Content: title prop removed - content goes directly in Tabs.Tab
  4. Panel Separation: Panel content moved to separate Tabs.Panel component
  5. Indicator: Must explicitly include Tabs.Indicator in each tab
  6. Orientation: isVerticalorientation prop
  7. Styling Props Removed: variant, color, size, radius, placement - use Tailwind CSS
  8. ClassNames Removed: Use className props on individual components

Tips for Migration

  1. Update structure: Wrap tabs in Tabs.ListContainer and Tabs.List
  2. Separate tab and panel: Move panel content to Tabs.Panel component
  3. Update keys: Change key prop to id prop (must match between tab and panel)
  4. Remove title prop: Move title content directly into Tabs.Tab
  5. Add indicator: Include Tabs.Indicator in each Tabs.Tab
  6. Update orientation: Change isVertical to orientation="vertical"
  7. Update styling: Replace styling props with Tailwind CSS classes
  8. Update classNames: Replace classNames prop with className props on individual components

Need Help?

For v3 Tabs features and API:

For community support: