Collapsible
Overview
An interactive component that expands/collapses a panel. SCollapsible wraps the Aria CollapsibleRoot with the collapsibleVariants style recipe (3 slots: root/trigger/content; 6 sizes), and the Aria CollapsibleTrigger/CollapsibleContent primitives provide the toggle button and the animated panel.
Use it for a single “expand to reveal” section, FAQ items, or inline collapsible filters. Prefer accordion when you need multiple coordinated sections (single/multiple open), and dialog/popover for floating surfaces.
Usage
Features
- 🧩 Aria/styled split —
SCollapsibleinjectscollapsibleVariants; the Aria primitives own state, a11y and animation - 🎛️ Controlled / uncontrolled —
v-model:openordefaultOpen - 📜 Animated panel —
CollapsibleContentmeasures real dimensions and animates open/closed viadata-state - 🗑️
unmountOnHide— removes closed content from the DOM, or keeps it mounted (hidden) - 🧲
forceMount— keep content present even when closed (for measurement/custom animation) - 🔗 Accessible wiring — the trigger exposes
aria-expanded/aria-controls; root/content reflectdata-state/data-disabled - 🔧 Polymorphic —
as/asChildon root, trigger and content
Component family
SCollapsible(styled) — the root wrapper;collapsibleVariantsrecipe andprovideCollapsibleUiCollapsibleRoot(Aria) — the state owner;useControllableState+provideCollapsibleRootContextCollapsibleTrigger(Aria) — theButton-based trigger;aria-expanded/aria-controls/data-stateCollapsibleContent(Aria) — the animated panel; presence-based dimension measurement anddata-state
Demos
Basic
API
Aria Layer
4Collapsible
Props
| Prop | Type | Default | Description |
|---|---|---|---|
size | ThemeSize | — | Visual size of the component. |
ui | Partial<CollapsibleUi> | — | Per-slot class overrides for the component. |
defaultOpen | boolean | — | The open state of the collapsible when it is initially rendered.
Use when you do not need to control its open state. |
open | boolean | — | The controlled open state of the collapsible. Can be bound with `v-model`. |
disabled | boolean | — | When `true`, prevents the user from interacting with the collapsible. |
unmountOnHide | boolean | — | When `true`, the element will be unmounted on closed state. |
asChild | boolean | — | Change the default rendered element for the one passed as a child, merging their props and behavior. |
as | AsTag | Component | 'div' | The element or component this component should render as. Can be overwrite by `asChild` |
Emits
| Emit Name | Parameters | Description |
|---|---|---|
update:open | boolean | Event handler called when the open state of the collapsible changes. |
CollapsibleContent
Props
| Prop | Type | Default | Description |
|---|---|---|---|
forceMount | boolean | — | Used to force mounting when more control is needed.
Useful when controlling animation with Vue animation libraries. |
asChild | boolean | — | Change the default rendered element for the one passed as a child, merging their props and behavior. |
as | AsTag | Component | 'div' | The element or component this component should render as. Can be overwrite by `asChild` |
CollapsibleRoot
Props
| Prop | Type | Default | Description |
|---|---|---|---|
defaultOpen | boolean | — | The open state of the collapsible when it is initially rendered.
Use when you do not need to control its open state. |
open | boolean | — | The controlled open state of the collapsible. Can be bound with `v-model`. |
disabled | boolean | — | When `true`, prevents the user from interacting with the collapsible. |
unmountOnHide | boolean | — | When `true`, the element will be unmounted on closed state. |
asChild | boolean | — | Change the default rendered element for the one passed as a child, merging their props and behavior. |
as | AsTag | Component | 'div' | The element or component this component should render as. Can be overwrite by `asChild` |
Emits
| Emit Name | Parameters | Description |
|---|---|---|
update:open | boolean | Event handler called when the open state of the collapsible changes. |
CollapsibleTrigger
Props
| Prop | Type | Default | Description |
|---|---|---|---|
disabledCollapsible | boolean | — | When `true`, prevents the user from toggling the collapsible. |
type | ButtonType | 'button' | The type of the button element. Can be one of 'button', 'submit', or 'reset'. |
disabled | boolean | — | Whether the component is disabled. |
asChild | boolean | — | Change the default rendered element for the one passed as a child, merging their props and behavior. |
as | AsTag | Component | 'div' | The element or component this component should render as. Can be overwrite by `asChild` |
Notes
Architecture and benchmark differences
SCollapsible is a thin styled wrapper; the Aria primitives own the open state (useControllableState), the presence/animation cycle (usePresence + getBoundingClientRect dimension measurement) and the accessibility wiring (aria-expanded/aria-controls/data-state). This mirrors shadcn/ui and Radix’s headless Collapsible, unlike Ant Design, Element Plus and Mantine which ship a config-driven collapsible or fold. VeanUI routes the trigger through its Button primitive so the styled Button variants are reused instead of a bespoke toggle.
| Capability | VeanUI | shadcn/ui | Radix Collapsible | Ant Design | Element Plus | Mantine |
|---|---|---|---|---|---|---|
| Aria/styled split | ✅ | ✅ | ✅ | — | — | — |
| Controlled/uncontrolled | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Height animation | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
unmountOnHide | ✅ | ✅ | ✅ | — | — | — |
forceMount | ✅ | ✅ | ✅ | — | — | — |
as/asChild | ✅ | ✅ | ✅ | — | — | — |
| Trigger reuses Button | ✅ | — | — | — | — | — |
— = unsupported or a different interaction model.
Cautions
- The collapse animation measures real dimensions with
getBoundingClientRect, so it only runs on the client; under SSR the first paint renders the open/closed state without animating. - With
unmountOnHide: true(default) closed content is removed from the DOM; withfalseit stays mounted but is hidden (hidden="until-found"). forceMountkeeps the content element present even when closed — combine it with your own animation/transition to control reveal.- Wire the trigger via the Aria
CollapsibleTrigger(aButton) soaria-expanded/aria-controlsstay correct; a custom button must set these manually. - The content element carries
data-state/data-disabledand the recipe’sdata-[state=open]:animate-*classes drive the transition.
Roadmap
No blocking gaps identified for the core collapsible API.
FAQ
How do I build a collapsible with a toggle?
Use SCollapsible with the Aria CollapsibleTrigger for a styled button trigger:
<SCollapsible v-model:open="open">
<template #default="{ open }">
<SCollapsibleTrigger>Toggle ({{ open ? 'on' : 'off' }})</SCollapsibleTrigger>
<SCollapsibleContent>Hidden panel content</SCollapsibleContent>
</template>
</SCollapsible>How do I control the open state?
Bind open with v-model (or use default-open):
<SCollapsible v-model:open="open">…</SCollapsible>How do I keep the content mounted when closed?
Set unmount-on-hide="false" so it stays in the DOM (hidden) instead of being removed:
<SCollapsible :unmount-on-hide="false">…</SCollapsible>How do I animate with my own transition?
Use force-mount and drive the reveal with your own classes or a <Transition>:
<SCollapsible force-mount>…</SCollapsible>Is the panel keyboard accessible?
Yes — the CollapsibleTrigger is a real <button> that sets aria-expanded and aria-controls to the content id, and the content reflects data-state; focus stays on the trigger after toggling.