wui
组件

折叠面板 Collapsible

按需展开与收起补充内容的渐进式披露组件,内置平滑高度补间动效与完整的键盘无障碍控制。

第三方依赖 · radix-ui第三方依赖 · lucide-react第三方依赖 · motion

基础用法

最基础的折叠面板用法。点击触发器区域展开或收起下方关联的内容区块:

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

根容器组件,统一分发展开状态上下文:

属性类型默认值说明
openboolean—受控模式下的展开状态。
defaultOpenbooleanfalse非受控模式下的初始展开状态。
onOpenChange(open: boolean) => void—展开状态发生改变时的回调函数。
disabledbooleanfalse是否禁用折叠面板的展开/收起交互。
asChildbooleanfalse是否将属性与行为合并到唯一的子元素上渲染。
classNamestring—应用于外层容器的额外 CSS 类名。

CollapsibleTrigger

折叠交互触发器,默认输出带旋转箭头的按钮:

属性类型默认值说明
showIndicatorbooleantrue是否在右侧展示跟随展开状态 180 度旋转的 Chevron 下拉箭头指示器。
asChildbooleanfalse是否将触发器属性与点击行为合并到传入的自定义子元素上。
classNamestring—应用于触发器按钮的额外 CSS 类名。

CollapsibleContent

折叠内容区域,内置高度从 0 到 auto 的平滑过渡动效:

属性类型默认值说明
classNamestring—应用于内容动效容器的额外 CSS 类名。
childrenReact.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)”时动画持续时间自动归零,实现瞬间无闪烁切换。