Accordion
Migration guide for Accordion from HeroUI v2 to v3
Refer to the v3 Accordion documentation for complete API reference, styling guide, and advanced examples. This guide only focuses on migrating from HeroUI v2.
Overview
The Accordion component in HeroUI v3 has been redesigned with a compound component pattern, requiring explicit structure with Accordion.Item and its subcomponents.
Structure Changes
v2: Flat Component Structure
In v2, AccordionItem was a self-contained component that accepted props for title, subtitle, content, and other elements:
import { Accordion, AccordionItem } from "@heroui/react";
<Accordion>
<AccordionItem
key="1"
title="Accordion 1"
subtitle="Press to expand"
>
Content here
</AccordionItem>
</Accordion>v3: Compound Component Structure
In v3, Accordion uses a compound component pattern with explicit subcomponents:
import { Accordion } from "@heroui/react";
<Accordion>
<Accordion.Item>
<Accordion.Heading>
<Accordion.Trigger>
Accordion 1
<Accordion.Indicator />
</Accordion.Trigger>
</Accordion.Heading>
<Accordion.Panel>
<Accordion.Body>Content here</Accordion.Body>
</Accordion.Panel>
</Accordion.Item>
</Accordion>Key Changes
1. Component Structure
v2: Single AccordionItem component with props
v3: Compound components: Accordion.Item, Accordion.Heading, Accordion.Trigger, Accordion.Panel, Accordion.Body, Accordion.Indicator
2. State Management Props
| v2 Prop | v3 Prop | Notes |
|---|---|---|
selectedKeys | expandedKeys | Changed from Set<Key> to Iterable<Key> |
defaultSelectedKeys | defaultExpandedKeys | Same change |
onSelectionChange | onExpandedChange | Callback signature: (keys: Set<Key>) => void |
selectionMode="multiple" | allowsMultipleExpanded | Changed from string to boolean |
3. Variants
v2 Variants: light, shadow, bordered, splitted
v3 Variants: default, surface
The v3 variants are simplified. To achieve similar effects to v2 variants:
- v2
light→ v3default(default variant) - v2
shadow→ v3surface(or usedefault+shadow-lgclass for custom styling) - v2
bordered→ v3default+ add border classes - v2
splitted→ v3default+ add spacing/margin between items
4. Removed Props
The following props are no longer available in v3:
isCompact- Use Tailwind CSS classes liketext-smorpy-2insteadhideIndicator- Simply don't render<Accordion.Indicator />disableAnimation- Animations are handled by React Aria ComponentsdisableIndicatorAnimation- Use CSS to control indicator animationsmotionProps- Animation is handled internallyshowDivider- Add dividers manually between items if neededdividerProps- Use a separateDividercomponentkeepContentMounted- Content is always mounted in v3selectionBehavior- Not applicable with new APIdisallowEmptySelection- Not applicableitemClasses- UseclassNameon individual itemsstartContent- Place content directly in<Accordion.Trigger>title/subtitleprops - Place content directly in<Accordion.Trigger>
5. Item Identification
v2: Used key prop on AccordionItem
v3: Use id prop on Accordion.Item
Migration Examples
Basic Usage
import { Accordion, AccordionItem } from "@heroui/react";
export default function App() {
return (
<Accordion>
<AccordionItem
key="1"
aria-label="Accordion 1"
title="Accordion 1"
>
Lorem ipsum dolor sit amet...
</AccordionItem>
<AccordionItem
key="2"
aria-label="Accordion 2"
title="Accordion 2"
>
Lorem ipsum dolor sit amet...
</AccordionItem>
</Accordion>
);
}import { Accordion } from "@heroui/react";
export default function App() {
return (
<Accordion>
<Accordion.Item id="1">
<Accordion.Heading>
<Accordion.Trigger>
Accordion 1
<Accordion.Indicator />
</Accordion.Trigger>
</Accordion.Heading>
<Accordion.Panel>
<Accordion.Body>
Lorem ipsum dolor sit amet...
</Accordion.Body>
</Accordion.Panel>
</Accordion.Item>
<Accordion.Item id="2">
<Accordion.Heading>
<Accordion.Trigger>
Accordion 2
<Accordion.Indicator />
</Accordion.Trigger>
</Accordion.Heading>
<Accordion.Panel>
<Accordion.Body>
Lorem ipsum dolor sit amet...
</Accordion.Body>
</Accordion.Panel>
</Accordion.Item>
</Accordion>
);
}Multiple Expanded Items
<Accordion selectionMode="multiple">
<AccordionItem key="1" title="Item 1">
Content 1
</AccordionItem>
<AccordionItem key="2" title="Item 2">
Content 2
</AccordionItem>
</Accordion><Accordion allowsMultipleExpanded>
<Accordion.Item id="1">
<Accordion.Heading>
<Accordion.Trigger>
Item 1
<Accordion.Indicator />
</Accordion.Trigger>
</Accordion.Heading>
<Accordion.Panel>
<Accordion.Body>Content 1</Accordion.Body>
</Accordion.Panel>
</Accordion.Item>
<Accordion.Item id="2">
<Accordion.Heading>
<Accordion.Trigger>
Item 2
<Accordion.Indicator />
</Accordion.Trigger>
</Accordion.Heading>
<Accordion.Panel>
<Accordion.Body>Content 2</Accordion.Body>
</Accordion.Panel>
</Accordion.Item>
</Accordion>Controlled State
import { useState } from "react";
import { Accordion, AccordionItem } from "@heroui/react";
const [selectedKeys, setSelectedKeys] = useState(new Set(["1"]));
<Accordion
selectedKeys={selectedKeys}
onSelectionChange={setSelectedKeys}
>
<AccordionItem key="1" title="Item 1">
Content 1
</AccordionItem>
<AccordionItem key="2" title="Item 2">
Content 2
</AccordionItem>
</Accordion>import { useState } from "react";
import { Accordion } from "@heroui/react";
import type { Key } from "@heroui/react";
const [expandedKeys, setExpandedKeys] = useState<Set<Key>>(new Set(["1"]));
<Accordion
expandedKeys={expandedKeys}
onExpandedChange={setExpandedKeys}
>
<Accordion.Item id="1">
<Accordion.Heading>
<Accordion.Trigger>
Item 1
<Accordion.Indicator />
</Accordion.Trigger>
</Accordion.Heading>
<Accordion.Panel>
<Accordion.Body>Content 1</Accordion.Body>
</Accordion.Panel>
</Accordion.Item>
<Accordion.Item id="2">
<Accordion.Heading>
<Accordion.Trigger>
Item 2
<Accordion.Indicator />
</Accordion.Trigger>
</Accordion.Heading>
<Accordion.Panel>
<Accordion.Body>Content 2</Accordion.Body>
</Accordion.Panel>
</Accordion.Item>
</Accordion>With Subtitle and Start Content
<Accordion>
<AccordionItem
key="1"
title="Accordion 1"
subtitle="Press to expand"
startContent={<Icon icon="gravity-ui:box" />}
>
Content here
</AccordionItem>
</Accordion>import { Icon } from "@iconify/react";
<Accordion>
<Accordion.Item id="1">
<Accordion.Heading>
<Accordion.Trigger>
<Icon icon="gravity-ui:box" className="mr-2" />
<div className="flex flex-col">
<span>Accordion 1</span>
<span className="text-sm text-muted">Press to expand</span>
</div>
<Accordion.Indicator />
</Accordion.Trigger>
</Accordion.Heading>
<Accordion.Panel>
<Accordion.Body>Content here</Accordion.Body>
</Accordion.Panel>
</Accordion.Item>
</Accordion>Custom Indicator
<Accordion>
<AccordionItem
key="1"
title="Item 1"
indicator={(props) => (
props.isOpen ? <Icon icon="minus" /> : <Icon icon="plus" />
)}
>
Content
</AccordionItem>
</Accordion>import { Icon } from "@iconify/react";
import { useState } from "react";
import { Accordion } from "@heroui/react";
import type { Key } from "@heroui/react";
const [expandedKeys, setExpandedKeys] = useState<Set<Key>>(new Set());
<Accordion
expandedKeys={expandedKeys}
onExpandedChange={setExpandedKeys}
>
<Accordion.Item id="1">
<Accordion.Heading>
<Accordion.Trigger>
Item 1
<Accordion.Indicator>
{expandedKeys.has("1") ? (
<Icon icon="gravity-ui:minus" />
) : (
<Icon icon="gravity-ui:plus" />
)}
</Accordion.Indicator>
</Accordion.Trigger>
</Accordion.Heading>
<Accordion.Panel>
<Accordion.Body>Content</Accordion.Body>
</Accordion.Panel>
</Accordion.Item>
</Accordion>Disabled Items
<Accordion disabledKeys={["2"]}>
<AccordionItem key="1" title="Item 1">
Content 1
</AccordionItem>
<AccordionItem key="2" title="Item 2">
Content 2
</AccordionItem>
</Accordion><Accordion>
<Accordion.Item id="1">
<Accordion.Heading>
<Accordion.Trigger>
Item 1
<Accordion.Indicator />
</Accordion.Trigger>
</Accordion.Heading>
<Accordion.Panel>
<Accordion.Body>Content 1</Accordion.Body>
</Accordion.Panel>
</Accordion.Item>
<Accordion.Item id="2" isDisabled>
<Accordion.Heading>
<Accordion.Trigger>
Item 2
<Accordion.Indicator />
</Accordion.Trigger>
</Accordion.Heading>
<Accordion.Panel>
<Accordion.Body>Content 2</Accordion.Body>
</Accordion.Panel>
</Accordion.Item>
</Accordion>Default Expanded Keys
<Accordion defaultExpandedKeys={["2"]}>
<AccordionItem key="1" title="Item 1">
Content 1
</AccordionItem>
<AccordionItem key="2" title="Item 2">
Content 2
</AccordionItem>
</Accordion><Accordion defaultExpandedKeys={["2"]}>
<Accordion.Item id="1">
<Accordion.Heading>
<Accordion.Trigger>
Item 1
<Accordion.Indicator />
</Accordion.Trigger>
</Accordion.Heading>
<Accordion.Panel>
<Accordion.Body>Content 1</Accordion.Body>
</Accordion.Panel>
</Accordion.Item>
<Accordion.Item id="2">
<Accordion.Heading>
<Accordion.Trigger>
Item 2
<Accordion.Indicator />
</Accordion.Trigger>
</Accordion.Heading>
<Accordion.Panel>
<Accordion.Body>Content 2</Accordion.Body>
</Accordion.Panel>
</Accordion.Item>
</Accordion>Styling Changes
v2: classNames Prop
<AccordionItem
classNames={{
base: "custom-base",
title: "custom-title",
content: "custom-content"
}}
/>v3: Direct className Props
<Accordion.Item className="custom-base">
<Accordion.Heading>
<Accordion.Trigger className="custom-title">
Title
<Accordion.Indicator />
</Accordion.Trigger>
</Accordion.Heading>
<Accordion.Panel>
<Accordion.Body className="custom-content">
Content
</Accordion.Body>
</Accordion.Panel>
</Accordion.Item>Component Anatomy
The v3 Accordion follows this structure:
Accordion (Root)
└── Accordion.Item
├── Accordion.Heading
│ └── Accordion.Trigger
│ ├── [Your content: title, subtitle, icons, etc.]
│ └── Accordion.Indicator
└── Accordion.Panel
└── Accordion.Body
└── [Your content]Breaking Changes Summary
- Component Structure: Must use compound components instead of props
- State Props:
selectedKeys→expandedKeys,onSelectionChange→onExpandedChange - Multiple Selection:
selectionMode="multiple"→allowsMultipleExpanded={true} - Item Keys:
keyprop →idprop - Variants: Reduced from 4 to 2 variants
- Removed Props: Many convenience props removed; use Tailwind CSS classes instead
- Content Structure: Title, subtitle, and start content must be manually placed in
Trigger - Indicator: Must be explicitly rendered; no automatic indicator
Tips for Migration
- Start with structure: Convert the component structure first, then handle props
- Use TypeScript: The v3 types will help guide you through the migration
- Leverage Tailwind: Many removed props can be replaced with Tailwind utility classes
- Test interactions: Ensure keyboard navigation and accessibility still work
- Check animations: v3 handles animations differently, so verify they meet your needs
Need Help?
For v3 Accordion features and API:
- See the API Reference
- Check interactive examples
For community support: