Skip to content
返回设计中心

手风琴与折叠设计

单开 vs 多开手风琴、高度动画、可折叠区段的 ARIA 接线。

组件模式入门

为什么

手风琴让用户扫区段标题并只深入所需内容,减少长页压倒。对非同级内容比标签页便宜,对可选细节比模态便宜。

复合组件模式被 Radix UI、Headless UI 和 shadcn/ui 采用 — 这是 2024 年采用最多的三个无头库。相比扁平组件 API,它们减少 70% 的属性逐层传递。

何时使用

用于 3-10 区段且用户一次需一两个(FAQ、高级设置)。用户须从头读到尾的内容避免 — 全展开更好。

如何做

1. ARIA — button aria-expanded={open}、region aria-labelledby 指向 button id 2. 开放模式 — 单开(开一个关其他)或多开(独立);FAQ 默认单开 3. 触发器 — 真正 <button> 支持键盘+屏幕阅读器,绝不用 div 4. 动画 — 用 grid-template-rows 0fr→1fr 动画高度(无需测量),或 opacity+translate 5. 默认展开 — 首区段或全无;绝不全开(违背目的) 6. 图标 — 折叠时箭头旋转 90°,aria-hidden,仅装饰 7. 减弱动效 — prefers-reduced-motion 下动画即时

示例

function Accordion({ items, mode = "single" }: { items: Item[]; mode?: "single" | "multi" }) { const [open, setOpen] = useState<Set<string>>(new Set()); const toggle = (id: string) => { setOpen((prev) => { const next = new Set(prev); if (next.has(id)) next.delete(id); else { if (mode === "single") next.clear(); next.add(id); } return next; }); }; return items.map((it) => ( <div key={it.id}> <button aria-expanded={open.has(it.id)} aria-controls={`p-${it.id}`} onClick={() => toggle(it.id)}> {it.title} <span aria-hidden className={open.has(it.id) ? "rotate-90" : ""}>▸</span> </button> <div id={`p-${it.id}`} role="region" aria-labelledby={`b-${it.id}`} className="grid transition-all" style={{ gridTemplateRows: open.has(it.id) ? "1fr" : "0fr" }}> <div className="overflow-hidden">{it.content}</div> </div> </div> )); }

常见问题

  • ⚠用猜值动画 max-height — 超过猜值的内容被裁
  • ⚠div 触发器加 onClick — 不可键盘操作,屏幕阅读器说 '可点击' 而非 '按钮'
  • ⚠默认全开 — 页面巨大,违背扫标题收益

最佳实践

  • ✓用 grid-template-rows 0fr→1fr 动画 — 无高度测量、无裁切、GPU 便宜
  • ✓用真正 <button> 触发器 — 免费获键盘+屏幕阅读器支持
  • ✓FAQ 默认单开 — 多开用于用户比较区段的设置

常见问题解答