Pro--% off in--d : --h : --m : --s
HeroUI

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.

Structure Changes

In v2, AccordionItem was a self-contained component that accepted props for title, subtitle, content, and other elements:

import { Accordion, AccordionItem } from "@heroui/react";

export default function App() {
  return (
    <Accordion>
      <AccordionItem
        key="1"
        title="Accordion 1"
      >
        Content here
      </AccordionItem>
      <AccordionItem
        key="2"
        title="Accordion 2"
      >
        Content here
      </AccordionItem>
    </Accordion>
  );
}

In v3, Accordion uses a compound component pattern with explicit subcomponents:

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>Content here</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>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. Prop Changes

v2 Propv3 LocationNotes
selectedKeysAccordionRenamed to expandedKeys; type Iterable<Key>
defaultSelectedKeysAccordionRenamed to defaultExpandedKeys
onSelectionChangeAccordionRenamed to onExpandedChange; (keys: Set<Key>) => void
selectionMode="multiple"AccordionUse allowsMultipleExpanded (boolean)
isCompact-Removed (use Tailwind CSS e.g. text-sm, py-2)
hideIndicator-Omit <Accordion.Indicator /> to hide
disableAnimation, disableIndicatorAnimation, motionProps-Removed (animations not supported / use CSS)
showDivider, dividerProps-Removed (add dividers manually or use Divider)
keepContentMounted-Removed (content always mounted in v3)
selectionBehavior, disallowEmptySelection-Removed (not applicable)
itemClasses-Use className on items
startContent-Place content in <Accordion.Trigger>
title, subtitle-Place content in <Accordion.Trigger>

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 → v3 default
  • v2 shadow → v3 surface
  • v2 bordered → v3 default + add border classes
  • v2 splitted → v3 default + add background color and spacing/margin between items

4. Item Identification

v2: React's key was used for both list reconciliation and expanded state.
v3: Use id on Accordion.Item for expanded state; keep React's key on items in lists.

Migration Examples

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

import { Icon } from "@iconify/react";

<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

import { Icon } from "@iconify/react";

<Accordion>
  <AccordionItem 
    key="1"
    title="Item 1"
    indicator={(props) => (
      props.isOpen ? <Icon icon="mdi:minus" /> : <Icon icon="mdi: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 and Default Expanded Keys

<Accordion 
  defaultExpandedKeys={["1"]}
  disabledKeys={["2"]}
>
  <AccordionItem key="1" title="Item 1">
    Content 1
  </AccordionItem>
  <AccordionItem key="2" title="Item 2">
    Content 2
  </AccordionItem>
</Accordion>
<Accordion defaultExpandedKeys={["1"]}>
  <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>

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 (optional)
      └── Accordion.Panel
          └── Accordion.Body
              └── [Your content]

Summary

  1. Component Structure: Must use compound components instead of props
  2. State Props: selectedKeysexpandedKeys, onSelectionChangeonExpandedChange
  3. Multiple Selection: selectionMode="multiple"allowsMultipleExpanded={true}
  4. Item identity: Use id for expanded state; keep React's key for list reconciliation
  5. Variants: Reduced from 4 to 2 variants
  6. Removed Props: Many convenience props removed; use Tailwind CSS classes instead
  7. Content Structure: Title, subtitle, and start content must be manually placed in Trigger
  8. Indicator: Must be explicitly rendered; no automatic indicator

On this page