Collapsible
Accessible, unstyled disclosure primitive with controlled and uncontrolled state.
|
|
| Package |
@vizejs/ui/collapsible |
| Maturity |
stable |
| Own the source |
vize lib pull collapsible |
| Requires |
context, controllable-state, id |
| Aliases |
disclosure, collapsible region, expand collapse |
| Covers |
WAI-ARIA Disclosure, Radix UI Collapsible, Reka UI Collapsible |
Minimal setup
Use a Vue 3.5+ project with Vue SFC compilation configured (see Vite+ integration).
vp install @vizejs/ui
Add the public import below to <script setup lang="ts">. Components provide behavior without styles; the preview uses optional Paper styles.
import { Collapsible, CollapsibleRoot, CollapsibleContent, CollapsibleTrigger } from "@vizejs/ui/collapsible";
Or copy the source into your project with vize lib pull collapsible (see Source Distribution).
Try it
This is the basic example maintained with the component source, using the public package imports. The preview adds the optional Paper styles.
Open the interactive example · Styling these components
Copy the example
<!-- Disclosure controlled with v-model:open that reveals advanced settings. -->
<script setup lang="ts">
import { ref, useId } from "vue";
import { CollapsibleContent, CollapsibleRoot, CollapsibleTrigger } from "@vizejs/ui/collapsible";
const open = ref(false);
const inputId = useId();
</script>
<template>
<CollapsibleRoot v-model:open="open">
<CollapsibleTrigger>{{ open ? "Hide" : "Show" }} advanced settings</CollapsibleTrigger>
<CollapsibleContent>
<label :for="inputId">Request timeout (seconds)</label>
<input :id="inputId" name="timeout" type="number" value="30" />
</CollapsibleContent>
</CollapsibleRoot>
</template>
API
Collapsible
Source: src/families/disclosure/collapsible/collapsible-root.vue
Props
| Prop |
Type |
Default |
Description |
id |
string | null |
undefined |
Consumer-owned Collapsible base id. null and undefined select a deterministic fallback. |
open |
boolean |
undefined |
Controlled open state. undefined selects uncontrolled behavior. |
defaultOpen |
boolean |
false |
Initial open state for uncontrolled use. |
disabled |
boolean |
false |
Disable trigger activation while preserving the current disclosure state. |
Events
| Event |
Payload |
Description |
update:open |
[value: boolean] |
Fired when the Collapsible requests a controlled open value. |
open-change |
[value: boolean, previous: boolean, nativeEvent: Event | null] |
Fired after any distinct open-state request. |
Slots
| Slot |
Slot props |
Description |
default |
CollapsibleSlotState |
Compound Collapsible children. Receives the current open and disabled state. |
Exposed
| Member |
Type |
Description |
collapse |
— |
|
contentId |
— |
|
disabled |
— |
|
expand |
— |
|
id |
— |
|
open |
— |
|
setOpen |
— |
|
state |
— |
|
toggle |
— |
|
triggerId |
— |
|
CollapsibleRoot
Source: src/families/disclosure/collapsible/collapsible-root.vue
Props
| Prop |
Type |
Default |
Description |
id |
string | null |
undefined |
Consumer-owned Collapsible base id. null and undefined select a deterministic fallback. |
open |
boolean |
undefined |
Controlled open state. undefined selects uncontrolled behavior. |
defaultOpen |
boolean |
false |
Initial open state for uncontrolled use. |
disabled |
boolean |
false |
Disable trigger activation while preserving the current disclosure state. |
Events
| Event |
Payload |
Description |
update:open |
[value: boolean] |
Fired when the Collapsible requests a controlled open value. |
open-change |
[value: boolean, previous: boolean, nativeEvent: Event | null] |
Fired after any distinct open-state request. |
Slots
| Slot |
Slot props |
Description |
default |
CollapsibleSlotState |
Compound Collapsible children. Receives the current open and disabled state. |
Exposed
| Member |
Type |
Description |
open |
boolean |
Whether the controlled content is currently visible. |
disabled |
boolean |
Whether trigger activation is disabled by the root. |
state |
CollapsibleState |
Stable state token for styling and tests. |
id |
string |
Root-owned base id for the disclosure family. |
triggerId |
string |
Id wired to the native trigger button. |
contentId |
string |
Id wired from CollapsibleTrigger to CollapsibleContent. |
setOpen |
(value: boolean, event?: Event | null) => boolean |
Request a specific open value and report whether it differs. |
expand |
(event?: Event | null) => boolean |
Request the open state. |
collapse |
(event?: Event | null) => boolean |
Request the closed state. |
toggle |
(event?: Event | null) => boolean |
Request the opposite open state. |
CollapsibleContent
Source: src/families/disclosure/collapsible/collapsible-content.vue
Props
| Prop |
Type |
Default |
Description |
role |
CollapsibleContentRole | null |
"region" |
Optional landmark role for the content. null renders a plain div. |
ariaLabel |
string |
undefined |
Accessible name when no visible label or aria-labelledby supplies one. |
ariaLabelledby |
string | null |
undefined |
Space-separated ids that label the content. null omits the default trigger id. |
ariaDescribedby |
string |
undefined |
Space-separated ids that describe the content. |
Slots
| Slot |
Slot props |
Description |
default |
CollapsibleSlotState |
Collapsible content. Receives the current open and disabled state. |
Exposed
| Member |
Type |
Description |
open |
boolean |
Whether the controlled content is currently visible. |
disabled |
boolean |
Whether trigger activation is disabled by the root. |
state |
CollapsibleState |
Stable state token for styling and tests. |
element |
HTMLDivElement | null |
Rendered content region. |
focusContent |
(options?: FocusOptions) => void |
Move focus to the content element when it can receive focus. |
CollapsibleTrigger
Source: src/families/disclosure/collapsible/collapsible-trigger.vue
Props
| Prop |
Type |
Default |
Description |
type |
"button" | "reset" | "submit" |
"button" |
Native button submission behavior. |
disabled |
boolean |
false |
Remove this trigger from activation and sequential keyboard focus. |
ariaLabel |
string |
undefined |
Accessible name when no visible label supplies one. |
ariaLabelledby |
string |
undefined |
Space-separated ids that label the trigger. |
Events
| Event |
Payload |
Description |
click |
[nativeEvent: MouseEvent] |
Fired before the trigger requests a toggle. Call preventDefault() to keep state unchanged. |
Slots
| Slot |
Slot props |
Description |
default |
CollapsibleSlotState |
Trigger contents. Receives the current Collapsible state and trigger availability. |
Exposed
| Member |
Type |
Description |
element |
HTMLButtonElement | null |
Rendered native button. |
focus |
(options?: FocusOptions) => void |
Move focus to the trigger. |
Behavior
Normative behavior for the @vizejs/ui/collapsible disclosure primitive.
| Area |
Input |
Observable outcome |
Evidence |
| State |
collapsible-root.vue receives defaultOpen without open |
root owns uncontrolled state, trigger activation toggles content visibility, and update:open plus open-change report distinct requests |
collapsible.test.ts |
| Controlled state |
open is provided |
trigger activation emits the requested value while rendered state follows the prop until the parent accepts it |
collapsible.test.ts |
| Disclosure semantics |
collapsible-trigger.vue and collapsible-content.vue render inside a root |
trigger is a native button with aria-expanded and aria-controls; content owns the controlled id, default region role, and trigger-backed label |
collapsible.test.ts |
| Native keyboard |
collapsible-trigger.vue receives Enter or Space |
the native button activation path toggles once per key press with no custom roving focus or Accordion navigation |
collapsible.test.ts |
| Disabled trigger |
root or trigger is disabled |
trigger leaves tab order through the native disabled attribute and user activation does not change state |
collapsible.test.ts |
| Preventable trigger |
trigger click handler calls preventDefault() |
open state remains unchanged and no root state event is emitted |
collapsible.test.ts |
| SSR |
isolated server requests render the same tree |
generated root, trigger, and content ids are stable; closed content renders with hidden; hydration keeps ids and markup |
collapsible-ssr.test.ts |
| DOM/SSR/Vapor |
authored SFCs compile |
root, trigger, and content compile in every renderer lane without handwritten render functions |
scripts/check-renderers.ts |
| Composition |
a composed family provides the exported collapsibleContext |
Collapsible trigger and content parts read that provider's ids and state, as Accordion items do |
../accordion/accordion.test.ts |
| Root/subpath |
consumer imports Collapsible |
root and subpath bundles are byte-equivalent, CSS-free, and retain only Collapsible plus shared state/context/id helpers |
scripts/check-tree-shaking.mjs |
Public Root Props
| Prop |
Type |
Default |
Contract |
id |
string | null |
undefined |
Consumer-owned base id. null and undefined use the deterministic fallback. |
open |
boolean |
undefined |
Controlled open value. undefined selects uncontrolled behavior. |
defaultOpen |
boolean |
false |
Initial uncontrolled open value. |
disabled |
boolean |
false |
Disables trigger activation while preserving current state. |
Public Trigger Props
| Prop |
Type |
Default |
Contract |
type |
"button" | "reset" | "submit" |
"button" |
Native button submission behavior. |
disabled |
boolean |
false |
Disables this trigger in addition to any root disabled state. |
ariaLabel |
string |
undefined |
Accessible trigger name when no visible label supplies one. |
ariaLabelledby |
string |
undefined |
Space-separated ids that label the trigger. |
Public Content Props
| Prop |
Type |
Default |
Contract |
role |
"group" | "region" | null |
"region" |
Content landmark role. null renders a plain div. |
ariaLabel |
string |
undefined |
Accessible content name. |
ariaLabelledby |
string | null |
undefined |
Content label ids. undefined uses the trigger id; null omits it. |
ariaDescribedby |
string |
undefined |
Space-separated ids that describe the content. |
Public Events
| Event |
Payload |
Contract |
update:open |
[value: boolean] |
Emitted by CollapsibleRoot for every distinct requested open value. |
open-change |
[value: boolean, previous: boolean, nativeEvent: Event | null] |
Emitted by CollapsibleRoot after a distinct request. |
click |
[nativeEvent: MouseEvent] |
Emitted by CollapsibleTrigger before requesting toggle; prevent it to keep state unchanged. |
Public Slots
| Component |
Slot |
Props |
CollapsibleRoot |
default |
CollapsibleSlotState |
CollapsibleTrigger |
default |
CollapsibleSlotState |
CollapsibleContent |
default |
CollapsibleSlotState |
Public Expose
| Component |
Exposed member |
Contract |
CollapsibleRoot |
id, triggerId, contentId, open, disabled, state |
Read current compound ids and disclosure state. |
CollapsibleRoot |
setOpen(value, event?), expand(event?), collapse(event?), toggle(event?) |
Programmatic state requests returning whether the requested value differs. |
CollapsibleTrigger |
element, focus(options?) |
Read and focus the native trigger button. |
CollapsibleContent |
element, open, disabled, state, focusContent(options?) |
Read state and focus the content element when the consumer makes it focusable. |
Parts And Data
| Target |
Public contract |
| Root |
part="root", data-vize-ui="collapsible-root", data-state, data-disabled |
| Trigger |
part="trigger", data-vize-ui="collapsible-trigger", data-state, data-disabled |
| Content |
part="content", data-vize-ui="collapsible-content", data-state, data-disabled, native hidden |
Collapsible defines no CSS custom properties and ships no stylesheet. Consumers own all visual styling.