unnamed-ui
折叠/步骤

Accordion

手风琴组件,用于折叠/展开内容

单选模式(一次只能展开一个)

多选模式(可以同时展开多个)

受控模式(当前值: )

"use client";

import * as React from "react";
import {
  Accordion,
  AccordionItem,
} from "@/components/composed/block-accordion/block-accordion";

// ==================== 演示组件 ====================

export function AccordionDemo() {
  // 状态管理
  const [singleValue, setSingleValue] = React.useState<string>("");

  return (
    <div className="w-full max-w-[650px] mx-auto p-4 space-y-8">
      {/* 单选模式(默认) */}
      <div className="">
        <h3 className="text-xl mb-3">单选模式(一次只能展开一个)</h3>
        <Accordion type="single" collapsible gap="gap-4">
          <AccordionItem
            value="item-1"
            trigger="工作目标:5"
            content={
              <div className="text-sm text-[var(--Text-text-secondary)]">
                工作内容
              </div>
            }
          />
          <AccordionItem
            value="item-2"
            trigger="工作目标:5"
            content={
              <div className="text-sm text-[var(--Text-text-secondary)]">
                工作内容
              </div>
            }
          />
          <AccordionItem
            value="item-3"
            trigger="工作目标:5"
            content={
              <div className="text-sm text-[var(--Text-text-secondary)]">
                工作内容
              </div>
            }
          />
        </Accordion>
      </div>

      {/* 多选模式 */}
      <div>
        <h3 className="text-xl mb-3">多选模式(可以同时展开多个)</h3>
        <Accordion type="multiple" gap="gap-3">
          <AccordionItem
            value="feature-1"
            trigger="工作目标:5"
            content={
              <div className="text-sm text-[var(--Text-text-secondary)]">
                工作内容
              </div>
            }
          />
          <AccordionItem
            value="feature-2"
            trigger="工作目标:5"
            content={
              <div className="text-sm text-[var(--Text-text-secondary)]">
                工作内容
              </div>
            }
          />
          <AccordionItem
            value="feature-3"
            trigger="工作目标:5"
            content={
              <div className="text-sm text-[var(--Text-text-secondary)]">
                工作内容
              </div>
            }
          />
        </Accordion>
      </div>

      {/* 受控模式 */}
      <div>
        <h3 className="text-xl mb-3">
          受控模式(当前值: {singleValue || "无"})
        </h3>
        <Accordion
          type="single"
          value={singleValue}
          onValueChange={(value) => setSingleValue(value as string)}
          gap="gap-2"
        >
          <AccordionItem
            value="controlled-1"
            trigger="受控项目一"
            content={
              <div className="p-4">这是受控模式下的第一个项目内容。</div>
            }
          />
          <AccordionItem
            value="controlled-2"
            trigger="受控项目二"
            content={
              <div className="p-4">这是受控模式下的第二个项目内容。</div>
            }
          />
          <AccordionItem
            value="controlled-3"
            trigger="受控项目三"
            content={
              <div className="p-4">这是受控模式下的第三个项目内容。</div>
            }
          />
        </Accordion>
        <button
          className="mt-3 px-3 py-1.5 text-sm bg-[var(--Container-bg-brand)] text-white rounded-[var(--radius-md)]"
          onClick={() => setSingleValue("")}
        >
          收起全部
        </button>
      </div>
    </div>
  );
}

手风琴组件是一种常用的 UI 模式,可以帮助用户快速导航和浏览长内容。支持单选和多选两种模式。

概述

  • 两种模式:single(单选)和 multiple(多选)
  • 折叠支持:单选模式下可配置是否允许折叠
  • 默认展开:支持默认展开单个或多个项目
  • 展开全部:多选模式下支持一键展开所有项
  • 受控模式:支持完全由代码控制展开状态
  • 间距控制:支持自定义项目间距
  • 无障碍:遵循 WAI-ARIA 标准,支持键盘导航
  • 动画效果:平滑的展开/收起动画

快速开始

import { Accordion, AccordionItem } from "@/registry/wuhan/composed/block-accordion/block-accordion";

export function Example() {
  return (
    <Accordion type="single" collapsible>
      <AccordionItem
        value="item-1"
        trigger="第一章:概述"
        content={
          <div className="p-4 text-sm text-[var(--Text-text-secondary)]">
            这里是第一章的内容。
          </div>
        }
      />
      <AccordionItem
        value="item-2"
        trigger="第二章:详情"
        content={
          <div className="p-4 text-sm text-[var(--Text-text-secondary)]">
            这里是第二章的内容。
          </div>
        }
      />
    </Accordion>
  );
}

特性

  • 单选模式:一次只能展开一个项目,适合互斥展开的场景
  • 多选模式:可以同时展开多个项目,适合对比查看的场景
  • 默认展开:支持默认展开单个或多个项目
  • 展开全部:多选模式下可一键展开所有项目
  • 受控模式:支持完全由代码控制展开状态
  • 间距控制:可自定义项目间距
  • 无障碍:遵循 WAI-ARIA 标准,支持键盘导航

安装

pnpm dlx shadcn@latest add http://localhost:3000/r/wuhan/block-accordion.json

代码演示

单选模式

"use client";

import {
  Accordion,
  AccordionItem,
} from "@/components/composed/block-accordion/block-accordion";

export function AccordionSingleDemo() {
  return (
    <div className="w-full max-w-[650px]">
      <Accordion type="single" collapsible>
        <AccordionItem
          value="item-1"
          trigger="项目一"
          content={
            <div className="p-4 text-sm text-[var(--Text-text-secondary)]">
              这是项目一的内容
            </div>
          }
        />
        <AccordionItem
          value="item-2"
          trigger="项目二"
          content={
            <div className="p-4 text-sm text-[var(--Text-text-secondary)]">
              这是项目二的内容
            </div>
          }
        />
        <AccordionItem
          value="item-3"
          trigger="项目三"
          content={
            <div className="p-4 text-sm text-[var(--Text-text-secondary)]">
              这是项目三的内容
            </div>
          }
        />
      </Accordion>
    </div>
  );
}

单选模式下,一次只能展开一个项目。设置 collapsibletrue 可以允许收起所有项目。

多选模式

"use client";

import {
  Accordion,
  AccordionItem,
} from "@/components/composed/block-accordion/block-accordion";

export function AccordionMultipleDemo() {
  return (
    <div className="w-full max-w-[650px]">
      <Accordion type="multiple">
        <AccordionItem
          value="feature-1"
          trigger="特性一"
          content={
            <div className="p-4 text-sm text-[var(--Text-text-secondary)]">
              特性一的详细内容说明
            </div>
          }
        />
        <AccordionItem
          value="feature-2"
          trigger="特性二"
          content={
            <div className="p-4 text-sm text-[var(--Text-text-secondary)]">
              特性二的详细内容说明
            </div>
          }
        />
        <AccordionItem
          value="feature-3"
          trigger="特性三"
          content={
            <div className="p-4 text-sm text-[var(--Text-text-secondary)]">
              特性三的详细内容说明
            </div>
          }
        />
      </Accordion>
    </div>
  );
}

多选模式下,可以同时展开多个项目。

默认展开(单选)

这个项目默认是展开的

"use client";

import {
  Accordion,
  AccordionItem,
} from "@/components/composed/block-accordion/block-accordion";

export function AccordionDefaultSingleDemo() {
  return (
    <div className="w-full max-w-[650px]">
      <Accordion type="single" defaultValue="item-2">
        <AccordionItem
          value="item-1"
          trigger="项目一"
          content={
            <div className="p-4 text-sm text-[var(--Text-text-secondary)]">
              这个项目默认是收起的
            </div>
          }
        />
        <AccordionItem
          value="item-2"
          trigger="项目二(默认展开)"
          content={
            <div className="p-4 text-sm text-[var(--Text-text-secondary)]">
              这个项目默认是展开的
            </div>
          }
        />
        <AccordionItem
          value="item-3"
          trigger="项目三"
          content={
            <div className="p-4 text-sm text-[var(--Text-text-secondary)]">
              这个项目默认是收起的
            </div>
          }
        />
      </Accordion>
    </div>
  );
}

单选模式下,通过 defaultValue 设置默认展开的项目(字符串)。

默认展开(多选)

默认展开多个项(使用数组)

这个项目默认是展开的

这个项目默认是展开的

默认展开单个项(使用字符串)

这个项目默认是展开的

"use client";

import {
  Accordion,
  AccordionItem,
} from "@/components/composed/block-accordion/block-accordion";

export function AccordionDefaultMultipleDemo() {
  return (
    <div className="w-full max-w-[650px] space-y-8">
      {/* 默认展开多个 */}
      <div>
        <h3 className="text-sm font-medium mb-3 text-[var(--Text-text-primary)]">
          默认展开多个项(使用数组)
        </h3>
        <Accordion type="multiple" defaultValue={["item-1", "item-3"]}>
          <AccordionItem
            value="item-1"
            trigger="项目一(默认展开)"
            content={
              <div className="p-4 text-sm text-[var(--Text-text-secondary)]">
                这个项目默认是展开的
              </div>
            }
          />
          <AccordionItem
            value="item-2"
            trigger="项目二"
            content={
              <div className="p-4 text-sm text-[var(--Text-text-secondary)]">
                这个项目默认是收起的
              </div>
            }
          />
          <AccordionItem
            value="item-3"
            trigger="项目三(默认展开)"
            content={
              <div className="p-4 text-sm text-[var(--Text-text-secondary)]">
                这个项目默认是展开的
              </div>
            }
          />
        </Accordion>
      </div>

      {/* 默认展开单个(使用字符串) */}
      <div>
        <h3 className="text-sm font-medium mb-3 text-[var(--Text-text-primary)]">
          默认展开单个项(使用字符串)
        </h3>
        <Accordion type="multiple" defaultValue="item-2">
          <AccordionItem
            value="item-1"
            trigger="项目一"
            content={
              <div className="p-4 text-sm text-[var(--Text-text-secondary)]">
                这个项目默认是收起的
              </div>
            }
          />
          <AccordionItem
            value="item-2"
            trigger="项目二(默认展开)"
            content={
              <div className="p-4 text-sm text-[var(--Text-text-secondary)]">
                这个项目默认是展开的
              </div>
            }
          />
          <AccordionItem
            value="item-3"
            trigger="项目三"
            content={
              <div className="p-4 text-sm text-[var(--Text-text-secondary)]">
                这个项目默认是收起的
              </div>
            }
          />
        </Accordion>
      </div>
    </div>
  );
}

多选模式下,defaultValue 可以是字符串(展开单个)或字符串数组(展开多个)。

展开全部

所有项目都默认展开

所有项目都默认展开

所有项目都默认展开
"use client";

import {
  Accordion,
  AccordionItem,
} from "@/components/composed/block-accordion/block-accordion";

export function AccordionExpandAllDemo() {
  return (
    <div className="w-full max-w-[650px]">
      <Accordion type="multiple" expandAll>
        <AccordionItem
          value="item-1"
          trigger="项目一(默认展开)"
          content={
            <div className="p-4 text-sm text-[var(--Text-text-secondary)]">
              所有项目都默认展开
            </div>
          }
        />
        <AccordionItem
          value="item-2"
          trigger="项目二(默认展开)"
          content={
            <div className="p-4 text-sm text-[var(--Text-text-secondary)]">
              所有项目都默认展开
            </div>
          }
        />
        <AccordionItem
          value="item-3"
          trigger="项目三(默认展开)"
          content={
            <div className="p-4 text-sm text-[var(--Text-text-secondary)]">
              所有项目都默认展开
            </div>
          }
        />
      </Accordion>
    </div>
  );
}

多选模式下,设置 expandAlltrue 可以默认展开所有项目。

受控模式

当前展开:

"use client";

import * as React from "react";
import {
  Accordion,
  AccordionItem,
} from "@/components/composed/block-accordion/block-accordion";

export function AccordionControlledDemo() {
  const [value, setValue] = React.useState("");

  return (
    <div className="w-full max-w-[650px] space-y-4">
      <div className="flex items-center gap-4">
        <span className="text-sm text-[var(--Text-text-secondary)]">
          当前展开: <span className="font-medium">{value || "无"}</span>
        </span>
        <button
          className="px-3 py-1.5 text-sm bg-[var(--Container-bg-brand)] text-white rounded-[var(--radius-md)] hover:opacity-90"
          onClick={() => setValue("")}
        >
          收起全部
        </button>
      </div>
      <Accordion
        type="single"
        value={value}
        onValueChange={(val) => setValue(val)}
      >
        <AccordionItem
          value="item-1"
          trigger="项目一"
          content={
            <div className="p-4 text-sm text-[var(--Text-text-secondary)]">
              这是项目一的内容
            </div>
          }
        />
        <AccordionItem
          value="item-2"
          trigger="项目二"
          content={
            <div className="p-4 text-sm text-[var(--Text-text-secondary)]">
              这是项目二的内容
            </div>
          }
        />
        <AccordionItem
          value="item-3"
          trigger="项目三"
          content={
            <div className="p-4 text-sm text-[var(--Text-text-secondary)]">
              这是项目三的内容
            </div>
          }
        />
      </Accordion>
    </div>
  );
}

通过 valueonValueChange 属性实现完全受控,可以通过代码控制展开状态。

间距控制

无间距 (gap-0)

中等间距 (gap-4)

"use client";

import {
  Accordion,
  AccordionItem,
} from "@/components/composed/block-accordion/block-accordion";

export function AccordionGapDemo() {
  return (
    <div className="w-full max-w-[650px] space-y-8">
      {/* gap-0 (无间距) */}
      <div>
        <h3 className="text-sm font-medium mb-3 text-[var(--Text-text-primary)]">
          无间距 (gap-0)
        </h3>
        <Accordion type="single" collapsible gap="gap-0">
          <AccordionItem
            value="item-1"
            trigger="项目一"
            content={
              <div className="p-4 text-sm text-[var(--Text-text-secondary)]">
                内容一
              </div>
            }
          />
          <AccordionItem
            value="item-2"
            trigger="项目二"
            content={
              <div className="p-4 text-sm text-[var(--Text-text-secondary)]">
                内容二
              </div>
            }
          />
        </Accordion>
      </div>

      {/* gap-4 (中等间距) */}
      <div>
        <h3 className="text-sm font-medium mb-3 text-[var(--Text-text-primary)]">
          中等间距 (gap-4)
        </h3>
        <Accordion type="single" collapsible gap="gap-4">
          <AccordionItem
            value="item-1"
            trigger="项目一"
            content={
              <div className="p-4 text-sm text-[var(--Text-text-secondary)]">
                内容一
              </div>
            }
          />
          <AccordionItem
            value="item-2"
            trigger="项目二"
            content={
              <div className="p-4 text-sm text-[var(--Text-text-secondary)]">
                内容二
              </div>
            }
          />
        </Accordion>
      </div>
    </div>
  );
}

通过 gap 属性控制 AccordionItem 之间的间距,支持所有 Tailwind gap 类名。

API

Accordion Props

通用属性

参数说明类型默认值
type手风琴类型'single' | 'multiple''single'
gapAccordionItem 之间的间距(支持 Tailwind gap 类名)string-
className自定义类名string-
children子元素ReactNode-

单选模式属性 (type="single")

参数说明类型默认值
collapsible是否允许折叠所有项booleantrue
defaultValue默认展开的值(支持 stringstring[],数组时取第一项)string | string[]-
value当前展开的值(受控模式)string-
onValueChange值变化回调(value: string) => void-

多选模式属性 (type="multiple")

参数说明类型默认值
expandAll是否默认展开所有项booleanfalse
defaultValue默认展开的值(支持 stringstring[],字符串时自动转为数组)string | string[]-
value当前展开的值(受控模式)string[]-
onValueChange值变化回调(value: string[]) => void-

AccordionItem Props

参数说明类型默认值
value项目的唯一标识string-
trigger触发器内容(标题)ReactNode-
content展开的内容ReactNode-

设计规范

尺寸规范

尺寸高度内边距字号
默认auto16px14px

交互规范

  • 单选模式:用于需要互斥展开的场景,如 FAQ、分类列表
  • 多选模式:用于需要对比多个展开项的场景,如特性对比
  • 展开全部:用于需要快速查看所有内容的场景
  • 内容样式:建议为内容添加内边距(如 p-4)和次要文本颜色
  • 键盘支持:使用方向键和 Enter 键可以展开/收起项目
  • 标题命名:trigger 应该是简洁的标题,避免过长的文本

最佳实践

内容容器样式:为了更好的视觉效果,建议为 content 添加样式:

<AccordionItem
  value="item-1"
  trigger="标题"
  content={
    <div className="p-4 text-sm text-[var(--Text-text-secondary)]">
      内容文本
    </div>
  }
/>