ProComponents, templates & AI tooling
HeroUI
27.7k

Accordion

Accordion 从 HeroUI v2 到 v3 的迁移指南。

完整的 API 参考、样式指南与高级示例请参阅 v3 Accordion 文档。本指南只关注从 HeroUI v2 的迁移。

结构变化

在 v2 中,AccordionItem 是一个独立组件,通过 props 接收标题、副标题、内容等元素:

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

在 v3 中,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>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>
  );
}

主要变化

1. 组件结构

v2: 单个带 props 的 AccordionItem 组件
v3: 复合组件:Accordion.ItemAccordion.HeadingAccordion.TriggerAccordion.PanelAccordion.BodyAccordion.Indicator

2. Prop 变更

v2 propv3 位置说明
selectedKeysAccordion已重命名为 expandedKeys;类型为 Iterable<Key>
defaultSelectedKeysAccordion已重命名为 defaultExpandedKeys
onSelectionChangeAccordion已重命名为 onExpandedChange;签名为 (keys: Set<Key>) => void
selectionMode="multiple"Accordion使用 allowsMultipleExpanded(布尔值)
isCompact已移除(请使用 Tailwind CSS,例如 text-smpy-2
hideIndicator不渲染 <Accordion.Indicator /> 即可隐藏
disableAnimationdisableIndicatorAnimationmotionProps已移除(不支持动画 / 请使用 CSS)
showDividerdividerProps已移除(请手动添加分隔线或使用 Divider
keepContentMounted已移除(v3 中内容始终挂载)
selectionBehaviordisallowEmptySelection已移除(不适用)
itemClasses在各项上使用 className
startContent将内容放在 <Accordion.Trigger>
titlesubtitle将内容放在 <Accordion.Trigger>

3. 变体

v2 变体: lightshadowborderedsplitted
v3 变体: defaultsurface

v3 的变体更精简。若要接近 v2 的视觉效果:

  • v2 light → v3 default
  • v2 shadow → v3 surface
  • v2 bordered → v3 default + 添加边框类
  • v2 splitted → v3 default + 为各项添加背景色与间距 / 外边距

4. 条目标识

v2: React 的 key 同时用于列表调和与展开状态。
v3:Accordion.Item 上使用 id 标识展开状态;列表中仍可为各项保留 React 的 key

迁移示例

受控状态

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>

副标题与前置内容

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>

自定义指示器

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>

禁用项与默认展开键

<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>

样式变化

v2:classNames prop

<AccordionItem 
  classNames={{
    base: "custom-base",
    title: "custom-title",
    content: "custom-content"
  }}
/>

v3:在各子组件上使用 className

<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>

组件剖析

v3 Accordion 的结构如下:

Accordion (Root)
  └── Accordion.Item
      ├── Accordion.Heading
      │   └── Accordion.Trigger
      │       ├── [Your content: title, subtitle, icons, etc.]
      │       └── Accordion.Indicator (optional)
      └── Accordion.Panel
          └── Accordion.Body
              └── [Your content]

总结

  1. 组件结构:必须使用复合组件,而不是仅靠 props 拼装。
  2. 状态 propsselectedKeysexpandedKeysonSelectionChangeonExpandedChange
  3. 多段展开selectionMode="multiple"allowsMultipleExpanded={true}
  4. 条目标识:展开状态用 id;列表调和仍使用 React 的 key
  5. 变体:由 4 种缩减为 2 种。
  6. 已移除的 props:大量便捷 props 已移除;请改用 Tailwind CSS 类。
  7. 内容结构:标题、副标题与前置内容需手动放入 Trigger
  8. 指示器:必须显式渲染;不再自动生成指示器。

本页目录