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 Prop | v3 Prop | Notes |
|---|---|---|
key (on Tab) | id (on Tab and Panel) | Changed prop name |
title (on Tab) | - | Removed (content goes directly in Tabs.Tab) |
isVertical | orientation | Changed 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 classescolor- Use Tailwind CSS classessize- Use Tailwind CSS classesradius- Use Tailwind CSS classesplacement- Useorientationprop and layoutclassNames- UseclassNameprops on individual componentsdisableCursorAnimation- UseTabs.IndicatorcomponentdisableAnimation- Animations handled differentlyfullWidth- Use Tailwind CSS classestitle(on Tab) - Content goes directly inTabs.Tab
4. Component Changes
- Tab identification:
key→id(must match betweenTabs.TabandTabs.Panel) - Tab content:
titleprop → direct children inTabs.Tab - Panel content: Tab children → separate
Tabs.Panelcomponent - Indicator: Automatic cursor → explicit
Tabs.Indicatorcomponent
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
keymatched automatically with panel content - v3:
Tabs.Tabidmust matchTabs.Panelidexactly
Indicator
- v2: Cursor indicator was automatic (could be disabled with
disableCursorAnimation) - v3: Must explicitly include
Tabs.Indicatorin eachTabs.Tab
Orientation
- v2: Used
isVerticalboolean prop - v3: Uses
orientationprop with"horizontal"or"vertical"values
Placement
- v2: Had
placementprop ("top","bottom","start","end") - v3:
placementremoved - useorientationand layout with Tailwind CSS
Breaking Changes Summary
- Component Structure: Must use compound components (
Tabs.ListContainer,Tabs.List,Tabs.Tab,Tabs.Indicator,Tabs.Panel) - Tab Identification:
key→id(must match between tab and panel) - Tab Content:
titleprop removed - content goes directly inTabs.Tab - Panel Separation: Panel content moved to separate
Tabs.Panelcomponent - Indicator: Must explicitly include
Tabs.Indicatorin each tab - Orientation:
isVertical→orientationprop - Styling Props Removed:
variant,color,size,radius,placement- use Tailwind CSS - ClassNames Removed: Use
classNameprops on individual components
Tips for Migration
- Update structure: Wrap tabs in
Tabs.ListContainerandTabs.List - Separate tab and panel: Move panel content to
Tabs.Panelcomponent - Update keys: Change
keyprop toidprop (must match between tab and panel) - Remove title prop: Move title content directly into
Tabs.Tab - Add indicator: Include
Tabs.Indicatorin eachTabs.Tab - Update orientation: Change
isVerticaltoorientation="vertical" - Update styling: Replace styling props with Tailwind CSS classes
- Update classNames: Replace
classNamesprop withclassNameprops on individual components
Need Help?
For v3 Tabs features and API:
- See the API Reference
- Check interactive examples
For community support: