Collapsible
A single-item disclosure toggle for showing and hiding content.
Usage
The Collapsible component provides a simple open/closed toggle for a single content region. It supports v-model for controlled state and exposes data-state attributes for CSS-driven styling.
<script setup lang="ts">
import { Collapsible } from '@vuetify/v0'
</script>
<template>
<Collapsible.Root
class="border border-divider rounded-lg border-solid overflow-hidden"
>
<Collapsible.Activator
class="w-full px-3 py-2 border-none flex items-center gap-3 text-left cursor-pointer bg-surface hover:bg-surface-tint"
>
<Collapsible.Cue class="inline-flex items-center justify-center w-5 text-sm text-on-surface opacity-60 data-[state=open]:text-primary">
<span class="data-[state=open]:hidden">+</span>
<span class="hidden data-[state=open]:inline">−</span>
</Collapsible.Cue>
<span class="flex-1 font-medium text-on-surface text-base">
Toggle content
</span>
</Collapsible.Activator>
<Collapsible.Content class="px-3 py-2 border-t border-divider border-solid">
<div class="text-on-surface text-sm">
This content can be toggled open and closed. The Collapsible component manages a single boolean state with full keyboard and accessibility support.
</div>
</Collapsible.Content>
</Collapsible.Root>
</template>
Anatomy
<script setup lang="ts">
import { Collapsible } from '@vuetify/v0'
</script>
<template>
<Collapsible.Root>
<Collapsible.Activator>
<Collapsible.Cue />
</Collapsible.Activator>
<Collapsible.Content />
</Collapsible.Root>
</template>Examples
Controlled
Use v-model to control the open state externally. The disabled prop prevents interaction.
v-model. The buttons above change state from outside the component. closed<script setup lang="ts">
import { Collapsible } from '@vuetify/v0'
import { shallowRef } from 'vue'
const open = shallowRef(false)
const disabled = shallowRef(false)
</script>
<template>
<div class="flex flex-col gap-4">
<div class="flex items-center gap-2 flex-wrap">
<button
class="px-3 py-1.5 text-sm rounded-lg border border-divider hover:bg-surface-tint"
@click="open = true"
>
Open
</button>
<button
class="px-3 py-1.5 text-sm rounded-lg border border-divider hover:bg-surface-tint"
@click="open = false"
>
Close
</button>
<button
class="px-3 py-1.5 text-sm rounded-lg border border-divider hover:bg-surface-tint"
@click="open = !open"
>
Toggle
</button>
<label class="flex items-center gap-1.5 text-sm text-on-surface-variant ml-auto">
<input v-model="disabled" type="checkbox">
Disabled
</label>
</div>
<Collapsible.Root
v-model="open"
class="border border-divider rounded-lg border-solid overflow-hidden data-[disabled]:opacity-50"
:disabled
>
<Collapsible.Activator
class="w-full px-3 py-2 border-none flex items-center gap-3 text-left cursor-pointer bg-surface hover:bg-surface-tint data-[disabled]:cursor-not-allowed"
>
<Collapsible.Cue class="inline-flex items-center justify-center w-5 text-sm text-on-surface opacity-60 data-[state=open]:text-primary">
<span class="data-[state=open]:hidden">+</span>
<span class="hidden data-[state=open]:inline">−</span>
</Collapsible.Cue>
<span class="flex-1 font-medium text-on-surface text-base">
Controlled collapsible
</span>
</Collapsible.Activator>
<Collapsible.Content class="px-3 py-2 border-t border-divider border-solid">
<div class="text-on-surface text-sm">
This collapsible is controlled externally via <code class="text-primary">v-model</code>. The buttons above change state from outside the component.
</div>
</Collapsible.Content>
</Collapsible.Root>
<div class="text-sm text-on-surface-variant">
State: <code class="text-primary">{{ open ? 'open' : 'closed' }}</code>
</div>
</div>
</template>
FAQ
Build a reusable FAQ component by wrapping Collapsible in a custom FaqItem component. Each item is an independent Collapsible instance — they don’t coordinate with each other.
| File | Role |
|---|---|
FaqItem.vue | Reusable wrapper around Collapsible with chevron rotation |
faq.vue | Entry point rendering items from data |
Collapsible vs ExpansionPanel
Both components handle expanding and collapsing content, but they solve different problems:
| Collapsible | ExpansionPanel | |
|---|---|---|
| Items | Single | Multiple |
| State | Boolean (open / closed) | Selection set (IDs) |
| Coordination | None — each instance is independent | Shared — accordion mode, mandatory, enroll |
| v-model | v-model (boolean) | v-model (ID or ID[]) |
| Built on | createSingle | createSelection |
Use Collapsible when you have a single region to show/hide — a details section, a settings panel, a mobile navigation drawer. Each Collapsible is independent.
Use ExpansionPanel when you have a list of items where expanding one may collapse another — an FAQ, a settings page with sections, an accordion sidebar.
You can build an FAQ from multiple independent Collapsible instances (see the FAQ example above), but if you need “only one open at a time” behavior, use ExpansionPanel instead — it handles that coordination for you.
Accessibility
Collapsible follows the WAI-ARIA Disclosure pattern↗.
Keyboard
| Key | Action |
|---|---|
| Enter | Toggles the content |
| Space | Toggles the content |
ARIA
| Attribute | Element | Value |
|---|---|---|
aria-expanded | Activator | true when open, false when closed |
aria-controls | Activator | Points to the content element’s id |
role="region" | Content | Landmarks the content area |
aria-labelledby | Content | Points back to the activator’s id |
Data attributes
All three sub-components expose data-state="open" or data-state="closed" for CSS-driven styling without JavaScript. The Root and Activator also expose data-disabled when the disabled prop is set.
Prefer data-state with CSS selectors (e.g., data-[state=open]:rotate-180) for visual changes like icon rotation, background color, and transitions. Use slot props (v-slot="{ isOpen }") when you need conditional rendering or logic that can’t be expressed in CSS.
Yes. Each Collapsible.Root creates its own independent context via the namespace prop. The default namespace is v0:collapsible, so nested instances work without collision. Use custom namespaces if you need to access a specific parent from a deeply nested child.
The data-state attribute transitions between "open" and "closed" on the content element. You can use CSS grid-template-rows or max-height transitions to animate the height change. The content uses the hidden attribute when closed, so your animation approach should account for that.
Collapsible.Root
Props
Events
update:model-value
[value: boolean]Slots
default
CollapsibleRootSlotPropsCollapsible.Activator
Props
Slots
default
CollapsibleActivatorSlotPropsCollapsible.Content
Props
Slots
default
CollapsibleContentSlotPropsCollapsible.Cue
Props
Slots
default
CollapsibleCueSlotProps