Accordion
Accessible, unstyled WAI-ARIA Accordion built on the Collapsible contract.
| Package | @vizejs/ui/accordion |
| Maturity | stable |
| Own the source | vize lib pull accordion |
| Requires | collapsible, collection, context, controllable-state, id, primitive |
| Aliases | accordion, expansion panel, disclosure group, faq |
| Covers | WAI-ARIA Accordion, Radix UI Accordion, Reka UI Accordion, HTML hidden=until-found |
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 { Accordion, AccordionRoot, AccordionContent, AccordionHeader, AccordionItem, AccordionTrigger } from "@vizejs/ui/accordion";
Or copy the source into your project with vize lib pull accordion (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
<!-- Single-open collapsible accordion bound with v-model to the open item. -->
<script setup lang="ts">
import { ref } from "vue";
import {
AccordionContent,
AccordionHeader,
AccordionItem,
AccordionRoot,
AccordionTrigger,
} from "@vizejs/ui/accordion";
const open = ref<string | null>("shipping");
</script>
<template>
<AccordionRoot v-model="open" type="single" collapsible>
<AccordionItem value="shipping">
<AccordionHeader>
<AccordionTrigger>Shipping</AccordionTrigger>
</AccordionHeader>
<AccordionContent>Orders ship within two business days.</AccordionContent>
</AccordionItem>
<AccordionItem value="returns">
<AccordionHeader>
<AccordionTrigger>Returns</AccordionTrigger>
</AccordionHeader>
<AccordionContent>Return unused items within 30 days for a full refund.</AccordionContent>
</AccordionItem>
<AccordionItem value="warranty" disabled>
<AccordionHeader>
<AccordionTrigger>Warranty (coming soon)</AccordionTrigger>
</AccordionHeader>
<AccordionContent>Warranty details are not available yet.</AccordionContent>
</AccordionItem>
</AccordionRoot>
</template>
API
Accordion<Value extends AccordionValue = string, Type extends AccordionType = 'single'>
Source: src/families/disclosure/accordion/accordion-root.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
type (required) |
Type |
required |
Whether one ("single") or many ("multiple") items may be open. Selects the model shape: Value | null or readonly Value[]. |
id |
string | null |
undefined |
Consumer-owned Accordion base id. null and undefined select a deterministic fallback. |
modelValue |
AccordionModelValue<Value, Type> |
undefined |
Controlled open item(s). undefined selects uncontrolled behavior. |
defaultValue |
AccordionModelValue<Value, Type> |
undefined |
Initial open item(s) for uncontrolled use. |
disabled |
boolean |
false |
Disable user activation of every item while preserving open state. |
collapsible |
boolean |
false |
Let users collapse the open item of a single accordion. multiple accordions are always collapsible. |
orientation |
AccordionOrientation |
"vertical" |
Axis used by arrow-key focus navigation between triggers. |
dir |
AccordionDirection |
undefined |
Reading direction used for horizontal arrow-key navigation. undefined inherits DirectionProvider/LocaleProvider, then "ltr". |
loop |
boolean |
true |
Whether arrow-key navigation wraps at the first and last enabled trigger. |
hiddenUntilFound |
boolean |
false |
Keep closed panels searchable with hidden="until-found"; a find-in-page or fragment match expands the item through beforematch. |
headingLevel |
AccordionHeadingLevel |
3 |
Default native heading level rendered by AccordionHeader. |
Events
| Event | Payload | Description |
|---|---|---|
update:modelValue |
[value: AccordionModelValue<Value, Type>] |
Fired when the open item(s) request a new controlled value. |
value-change |
[ value: AccordionModelValue<Value, Type>, previous: AccordionModelValue<Value, Type>, nativeEvent: Event | null, ] |
Fired after any distinct open-item request. |
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
AccordionSlotState<Value, Type> |
Compound AccordionItem children. Receives the current model and configuration. |
Exposed
| Member | Type | Description |
|---|---|---|
collapse |
— | |
collapseAll |
— | |
expand |
— | |
expandAll |
— | |
focus |
— | |
id |
— | |
isOpen |
— | |
openValues |
— | |
setValue |
— | |
toggle |
— | |
type |
— | |
value |
— |
AccordionRoot<Value extends AccordionValue = string, Type extends AccordionType = 'single'>
Source: src/families/disclosure/accordion/accordion-root.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
type (required) |
Type |
required |
Whether one ("single") or many ("multiple") items may be open. Selects the model shape: Value | null or readonly Value[]. |
id |
string | null |
undefined |
Consumer-owned Accordion base id. null and undefined select a deterministic fallback. |
modelValue |
AccordionModelValue<Value, Type> |
undefined |
Controlled open item(s). undefined selects uncontrolled behavior. |
defaultValue |
AccordionModelValue<Value, Type> |
undefined |
Initial open item(s) for uncontrolled use. |
disabled |
boolean |
false |
Disable user activation of every item while preserving open state. |
collapsible |
boolean |
false |
Let users collapse the open item of a single accordion. multiple accordions are always collapsible. |
orientation |
AccordionOrientation |
"vertical" |
Axis used by arrow-key focus navigation between triggers. |
dir |
AccordionDirection |
undefined |
Reading direction used for horizontal arrow-key navigation. undefined inherits DirectionProvider/LocaleProvider, then "ltr". |
loop |
boolean |
true |
Whether arrow-key navigation wraps at the first and last enabled trigger. |
hiddenUntilFound |
boolean |
false |
Keep closed panels searchable with hidden="until-found"; a find-in-page or fragment match expands the item through beforematch. |
headingLevel |
AccordionHeadingLevel |
3 |
Default native heading level rendered by AccordionHeader. |
Events
| Event | Payload | Description |
|---|---|---|
update:modelValue |
[value: AccordionModelValue<Value, Type>] |
Fired when the open item(s) request a new controlled value. |
value-change |
[ value: AccordionModelValue<Value, Type>, previous: AccordionModelValue<Value, Type>, nativeEvent: Event | null, ] |
Fired after any distinct open-item request. |
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
AccordionSlotState<Value, Type> |
Compound AccordionItem children. Receives the current model and configuration. |
Exposed
| Member | Type | Description |
|---|---|---|
id |
string |
Root-owned base id for the Accordion family. |
type |
Type |
Selection mode. |
value |
AccordionModelValue<Value, Type> |
Current model value in the shape selected by type. |
openValues |
readonly Value[] |
Every open item value, regardless of type. |
isOpen |
(value: Value) => boolean |
Whether the item with value is expanded. |
setValue |
(value: AccordionModelValue<Value, Type>, event?: Event | null) => boolean |
Replace the model value, bypassing the collapsible guard. Returns whether it changed. |
expand |
(value: Value, event?: Event | null) => boolean |
Expand one item. In single mode this collapses the previously open item. |
collapse |
(value: Value, event?: Event | null) => boolean |
Collapse one item, respecting collapsible in single mode. |
toggle |
(value: Value, event?: Event | null) => boolean |
Toggle one item, respecting collapsible in single mode. |
expandAll |
(event?: Event | null) => boolean |
Expand every registered enabled item. single accordions return false. |
collapseAll |
(event?: Event | null) => boolean |
Collapse every item, respecting collapsible in single mode. |
focus |
(value?: Value, options?: FocusOptions) => boolean |
Focus the trigger for value, or the first enabled trigger. |
AccordionContent
Source: src/families/disclosure/accordion/accordion-content.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
role |
AccordionContentRole | null |
"region" |
Landmark role for the panel. null renders a plain div; prefer null when an accordion has many panels to avoid landmark proliferation. |
hiddenUntilFound |
boolean |
undefined |
Keep this closed panel searchable with hidden="until-found". undefined uses the root hiddenUntilFound. |
ariaLabel |
string |
undefined |
Accessible name when no visible label or aria-labelledby supplies one. |
ariaLabelledby |
string | null |
undefined |
Space-separated ids that label the panel. null omits the default trigger id. |
ariaDescribedby |
string |
undefined |
Space-separated ids that describe the panel. |
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
AccordionItemSlotState |
Panel contents. Receives the item state. |
Exposed
| Member | Type | Description |
|---|---|---|
value |
AccordionValue |
Item identity. |
open |
boolean |
Whether the item panel is expanded. |
disabled |
boolean |
Whether user activation is disabled for this item. |
locked |
boolean |
Whether the trigger is the open item of a non-collapsible single accordion and therefore reports aria-disabled="true". |
state |
AccordionItemState |
Stable state token for styling and tests. |
element |
HTMLDivElement | null |
Rendered content element. |
hiddenUntilFound |
boolean |
Whether closed content stays searchable through hidden="until-found". |
AccordionHeader
Source: src/families/disclosure/accordion/accordion-header.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
level |
AccordionHeadingLevel |
undefined |
Native heading level. undefined uses the root headingLevel. |
as |
PrimitiveAs |
undefined |
Element or component to render instead of the native h${level}. |
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
AccordionItemSlotState |
Heading contents, normally one AccordionTrigger. Receives the item state. |
Exposed
| Member | Type | Description |
|---|---|---|
element |
Element | null |
Rendered heading element or component instance. |
AccordionItem
Source: src/families/disclosure/accordion/accordion-item.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
value (required) |
AccordionValue |
required |
Item identity used by the Accordion model. |
disabled |
boolean |
false |
Disable user activation of this item while preserving its open state. |
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
AccordionItemSlotState |
AccordionHeader and AccordionContent children. Receives the item state. |
Exposed
| Member | Type | Description |
|---|---|---|
value |
AccordionValue |
Item identity. |
open |
boolean |
Whether the item panel is expanded. |
disabled |
boolean |
Whether user activation is disabled for this item. |
locked |
boolean |
Whether the trigger is the open item of a non-collapsible single accordion and therefore reports aria-disabled="true". |
state |
AccordionItemState |
Stable state token for styling and tests. |
triggerId |
string |
Id wired to the item trigger. |
contentId |
string |
Id wired to the item content. |
element |
HTMLDivElement | null |
Rendered item element. |
expand |
(event?: Event | null) => boolean |
Request expansion. |
collapse |
(event?: Event | null) => boolean |
Request collapse, respecting collapsible in single mode. |
toggle |
(event?: Event | null) => boolean |
Request the opposite open state. |
AccordionTrigger
Source: src/families/disclosure/accordion/accordion-trigger.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
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 |
AccordionItemSlotState |
Trigger contents. Receives the item state. |
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/accordion compound primitive, following the
WAI-ARIA APG Accordion pattern.
Every row is proven by the named mounted-DOM, SSR, or compile-time test.
Accordion is built on the Collapsible contract: each AccordionItem provides the
Collapsible context, so CollapsibleTrigger and CollapsibleContent also work inside an
item. Native <details name> exclusive groups are intentionally not used: they cannot
express controlled state, the non-collapsible single mode, heading structure around the
trigger, or arrow-key navigation. hidden="until-found" keeps find-in-page support.
| State x input | Observable outcome | Proven by |
|---|---|---|
| any render | Root, item, trigger, and content ids derive from the root id and item value; the trigger is a native button inside a native h3 heading. |
renders APG accordion semantics with deterministic ids and native headings |
| open item | Trigger reports aria-expanded="true" and aria-controls; content is a region labelled by the trigger and has no hidden. |
renders APG accordion semantics with deterministic ids and native headings |
type="single", not collapsible, click on the open trigger |
State is unchanged; the open trigger reports aria-disabled="true" and data-locked. |
single accordion switches items and locks the open item unless collapsible |
type="single", click on a closed trigger |
The clicked item opens, the previous one closes, and update:modelValue plus value-change report the scalar model and native event. |
single accordion switches items and locks the open item unless collapsible |
type="single", collapsible, click on the open trigger |
The item closes and the model becomes null. |
collapsible single accordion collapses the open item to null |
type="multiple", clicks |
Items open and close independently; the model is a readonly list in opening order. | multiple accordion keeps independent items open and emits readonly lists |
controlled modelValue |
Requests emit the next model but rendered state follows the prop until the parent accepts it. | controlled accordion waits for the parent to accept each request |
| Enter or Space on a trigger | Native button activation toggles once per key press. | native Enter and Space activation toggle the focused trigger once |
| ArrowDown / ArrowUp on a vertical trigger | Focus moves to the next or previous enabled trigger, skipping disabled items and wrapping when loop; horizontal arrows are ignored. |
arrow keys, Home, and End move focus between enabled triggers with wrapping |
| Home / End on a trigger | Focus moves to the first or last enabled trigger. | arrow keys, Home, and End move focus between enabled triggers with wrapping |
orientation="horizontal" with dir="rtl"; loop=false at edge |
ArrowLeft moves forward in RTL; at a non-looping edge the key is not consumed and focus stays. | horizontal navigation follows reading direction and loop can stop at the edges |
| disabled root or item | Triggers use native disabled, leave the tab order, and user activation emits nothing. |
disabled roots and items block user activation with native disabled buttons |
trigger click handler calls preventDefault() |
The item does not toggle and no model event is emitted. | trigger click is preventable before the item toggles |
hiddenUntilFound, closed panel after mount |
Panel carries hidden="until-found"; a beforematch event expands the item (even when disabled) and re-closing restores the keyword. |
hidden until-found keeps closed panels searchable and beforematch expands them |
hiddenUntilFound=false |
Closed panels use plain hidden. |
hidden until-found keeps closed panels searchable and beforematch expands them |
| open panel after mount | --vize-accordion-content-height and --vize-accordion-content-width publish the measured panel size for consumer animations. |
content publishes measured size variables and supports role opt-out |
AccordionContent role=null |
Panel renders without a landmark role or default label. | content publishes measured size variables and supports role opt-out |
root headingLevel, header level |
Header renders h{level} with data-level; numeric and non-id-safe values get stable, hashed id segments. |
heading level follows the root default and per-header overrides |
| root expose | expand, collapse, toggle, expandAll, collapseAll, setValue, focus, isOpen, value, and openValues drive and read state. |
root and item expose typed programmatic controls |
type="single" root expose |
expandAll returns false; collapseAll respects collapsible; setValue bypasses the collapsible guard. |
single roots refuse expandAll and keep collapseAll behind collapsible |
| Collapsible parts inside an item | CollapsibleTrigger and CollapsibleContent read the item's ids and state and drive the accordion model. |
items publish the Collapsible contract to Collapsible parts |
| parts outside their provider | Mounting throws VIZE_UI_CONTEXT_MISSING. |
accordion parts require their providers |
| SSR | Isolated requests render byte-identical markup with deterministic ids, native hidden, and no measured style variables. |
renders byte-identical accordion markup across isolated SSR requests |
hydration with hiddenUntilFound |
Hydration reuses server nodes with zero warnings, then upgrades closed panels to hidden="until-found". |
hydrates without mismatches and upgrades closed panels to hidden until-found |
| generic typing | type selects Value | null or readonly Value[]; item values are inferred from modelValue/defaultValue; invalid shapes are rejected. |
src/families/disclosure/accordion/accordion.types.test-d.ts |
| DOM/SSR/Vapor | Every part compiles in each renderer lane without handwritten render functions. | scripts/check-renderers.ts |
Components
| Component | State x input | Outcome |
|---|---|---|
accordion-root.vue |
controlled or uncontrolled model | Owns open items, deterministic ids, keyboard navigation, and emits update:modelValue/value-change. |
accordion-item.vue |
registered value |
Publishes item and Collapsible contexts, data-state, and data-disabled. |
accordion-header.vue |
root headingLevel or level |
Renders the native heading around the trigger. |
accordion-trigger.vue |
click, Enter/Space, arrows, Home/End | Toggles its item through native button activation and moves focus between enabled triggers. |
accordion-content.vue |
open, closed, hiddenUntilFound |
Shows or hides the labelled panel, applies hidden="until-found" after mount, and expands on beforematch. |
Public Root Props
| Prop | Type | Default | Contract |
|---|---|---|---|
type |
"single" | "multiple" |
required | Selection mode and model shape. |
id |
string | null |
undefined |
Consumer-owned base id; null/undefined use the deterministic fallback. |
modelValue |
Value | null or readonly Value[] |
undefined |
Controlled open item(s). |
defaultValue |
Value | null or readonly Value[] |
undefined |
Initial uncontrolled open item(s). |
disabled |
boolean |
false |
Disables user activation of every item. |
collapsible |
boolean |
false |
Lets a single accordion collapse its open item. |
orientation |
"vertical" | "horizontal" |
"vertical" |
Arrow-key axis. |
dir |
"ltr" | "rtl" |
undefined |
Reading direction; inherits DirectionProvider/LocaleProvider, then "ltr". |
loop |
boolean |
true |
Wrap arrow-key navigation. |
hiddenUntilFound |
boolean |
false |
Closed panels use hidden="until-found" after mount. |
headingLevel |
1 | 2 | 3 | 4 | 5 | 6 |
3 |
Default AccordionHeader level. |
Events
| Event | Payload | Contract |
|---|---|---|
update:modelValue |
[value] |
Emitted for every distinct model request. |
value-change |
[value, previous, nativeEvent | null] |
Emitted after every distinct model request. |
click (trigger) |
[nativeEvent: MouseEvent] |
Preventable before the trigger toggles its item. |
Parts And Data
| Target | Public contract |
|---|---|
| Root | part="root", data-vize-ui="accordion-root", data-type, data-orientation, data-disabled, dir |
| Item | part="item", data-vize-ui="accordion-item", data-state, data-orientation, data-disabled |
| Header | part="header", data-vize-ui="accordion-header", data-level, data-state, data-orientation, data-disabled |
| Trigger | part="trigger", data-vize-ui="accordion-trigger", data-state, data-orientation, data-disabled, data-locked |
| Content | part="content", data-vize-ui="accordion-content", data-state, data-hidden-until-found, --vize-accordion-content-height/-width |
Accordion ships no stylesheet. Consumers own all visual styling.