组件
折叠面板 Collapsible
按需展开与收起补充内容的渐进式披露组件,内置平滑高度补间动效与完整的键盘无障碍控制。
基础用法
最基础的折叠面板用法。点击触发器区域展开或收起下方关联的内容区块:
Loading…
安装与引入
通过 CLI 自动添加组件,或手动复制源码至项目中:
pnpm dlx @wui-design/cli@latest add @wui/collapsible安装基础依赖、Radix 原语与动效库
pnpm add radix-ui motion lucide-react clsx tailwind-merge复制组件源码到
components/ui/collapsible.tsx"use client"
import * as React from "react"
import { Collapsible as CollapsiblePrimitive } from "radix-ui"
import { ChevronDownIcon } from "lucide-react"
import { motion, useReducedMotion } from "motion/react"
import { cn } from "@/lib/utils"
const CollapsibleContext = React.createContext(false)
export interface CollapsibleProps
extends React.ComponentProps<typeof CollapsiblePrimitive.Root> {}
/** A disclosure region with controlled and uncontrolled open state. */
function Collapsible({
open,
defaultOpen = false,
onOpenChange,
...props
}: CollapsibleProps) {
const [internalOpen, setInternalOpen] = React.useState(defaultOpen)
const resolvedOpen = open ?? internalOpen
return (
<CollapsibleContext.Provider value={resolvedOpen}>
<CollapsiblePrimitive.Root
data-slot="collapsible"
open={resolvedOpen}
onOpenChange={(next) => {
if (open === undefined) setInternalOpen(next)
onOpenChange?.(next)
}}
{...props}
/>
</CollapsibleContext.Provider>
)
}
export interface CollapsibleTriggerProps
extends React.ComponentProps<typeof CollapsiblePrimitive.Trigger> {
/** Show the built-in rotating chevron after the label. @default true */
showIndicator?: boolean
}
function CollapsibleTrigger({
className,
children,
showIndicator = true,
asChild = false,
...props
}: CollapsibleTriggerProps) {
return (
<CollapsiblePrimitive.Trigger
data-slot="collapsible-trigger"
asChild={asChild}
className={cn(
"group flex w-full items-center gap-3 rounded-md px-3 py-2 text-left text-sm font-medium outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:ring-[3px] focus-visible:ring-ring/35 disabled:pointer-events-none disabled:opacity-50",
className
)}
{...props}
>
{asChild ? (
children
) : (
<>
<span className="min-w-0 flex-1">{children}</span>
{showIndicator ? (
<ChevronDownIcon
aria-hidden
data-slot="collapsible-indicator"
className="size-4 shrink-0 text-muted-foreground transition-transform duration-300 ease-[cubic-bezier(0.22,1,0.36,1)] group-data-[state=open]:rotate-180 motion-reduce:transition-none"
/>
) : null}
</>
)}
</CollapsiblePrimitive.Trigger>
)
}
function CollapsibleContent({
className,
children,
...props
}: Omit<
React.ComponentProps<typeof CollapsiblePrimitive.Content>,
"asChild" | "forceMount"
>) {
const open = React.useContext(CollapsibleContext)
const reduceMotion = useReducedMotion()
return (
<CollapsiblePrimitive.Content forceMount asChild {...props}>
<motion.div
data-slot="collapsible-content"
aria-hidden={!open}
inert={!open}
className={cn("overflow-hidden", className)}
initial={false}
animate={{ height: open ? "auto" : 0, opacity: open ? 1 : 0 }}
transition={
reduceMotion
? { duration: 0 }
: { duration: 0.24, ease: [0.22, 1, 0.36, 1] }
}
>
{children}
</motion.div>
</CollapsiblePrimitive.Content>
)
}
export { Collapsible, CollapsibleContent, CollapsibleTrigger }
属性 Props
Collapsible
根容器组件,统一分发展开状态上下文:
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| open | boolean | — | 受控模式下的展开状态。 |
| defaultOpen | boolean | false | 非受控模式下的初始展开状态。 |
| onOpenChange | (open: boolean) => void | — | 展开状态发生改变时的回调函数。 |
| disabled | boolean | false | 是否禁用折叠面板的展开/收起交互。 |
| asChild | boolean | false | 是否将属性与行为合并到唯一的子元素上渲染。 |
| className | string | — | 应用于外层容器的额外 CSS 类名。 |
CollapsibleTrigger
折叠交互触发器,默认输出带旋转箭头的按钮:
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| showIndicator | boolean | true | 是否在右侧展示跟随展开状态 180 度旋转的 Chevron 下拉箭头指示器。 |
| asChild | boolean | false | 是否将触发器属性与点击行为合并到传入的自定义子元素上。 |
| className | string | — | 应用于触发器按钮的额外 CSS 类名。 |
CollapsibleContent
折叠内容区域,内置高度从 0 到 auto 的平滑过渡动效:
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| className | string | — | 应用于内容动效容器的额外 CSS 类名。 |
| children | React.ReactNode | — | 需要按需展示的折叠内容。 |
事件 Events
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| onOpenChange | (open: boolean) => void | — | 用户点击触发器、使用快捷键或外部状态更新导致展开/收起状态改变时触发,返回最新的布尔值。 |
使用场景与设计规范
Collapsible 用于在不跳转页面的前提下,按需**渐进式披露(Progressive Disclosure)**次要或高级内容。
- 组件选型对比:
- Collapsible:用于单一独立区域的按需展开(如单个卡片内部的“高级设置”、单项代码变更 Diff)。
- Accordion(手风琴):用于一组多项并列折叠面板(通常支持单项互斥或多项批量展开,如常见 FAQ 列表、多分组导航树)。
- 默认展开策略:
- 关键主流程字段:切勿默认收起;若用户必须填写或频繁阅读,应直接平铺展示。
- 低频配置(如高级安全签名、日志明细):默认收起(
defaultOpen={false}),保持界面整洁精炼。
- 指示器旋转动效:右侧箭头在展开时顺畅旋转 180°,明确传达当前开合状态。
场景示例
受控状态模式
通过 open 与 onOpenChange 将折叠状态与外部指示器或工具栏联动:
Loading…
代码变更折叠列表(Git Diff)
在代码评审或发布日志中,将每个文件的改动包裹在独立的折叠面板中按需查阅:
Loading…
表单卡片中的高级配置区块
在配置面板中将高级安全参数置于卡片底部的折叠区中,保持主表单专注干净:
Loading…
无障碍与交互 Accessibility
- ARIA 状态关联:底层基于 Radix UI 原语,触发器自动携带
aria-expanded="true | false"并通过aria-controls关联对应内容块 ID。 - 键盘快捷键:
- Tab:聚焦到触发器按钮上,展现清晰的高对比度焦点圈。
- Enter / Space:快速切换展开与收起状态。
- 焦点隔离与惰性隐藏:内容在收起状态下会自动设置
inert与aria-hidden="true",键盘 Tab 键无法意外聚焦到已折叠隐藏的内部输入框或链接上。 - 平滑动画与动效降级:高度补间通过
motion/react驱动,系统开启“减少动态效果(prefers-reduced-motion)”时动画持续时间自动归零,实现瞬间无闪烁切换。