Skip to main content
Vuetify0 v1.0 is here
Vuetify0 Logo
Theme
Mode
Palettes
Accessibility
Vuetify One
Sign in to Vuetify One

Access premium tools across the Vuetify ecosystem — Bin, Play, Studio, and more.

Not a subscriber? See what's included

EmExpansionPanel

Edit this page
Report a Bug
Open issues
Copy Markdown

Renders elementIntermediateAug 14, 2026

An accordion of coordinated panels — one open at a time by default, several at once when asked — with the WAI-ARIA accordion shape built in.

Usage

A group wraps any number of panels, and each panel is a header, an activator, and a content region. v-model on the group holds the open panel’s value — or undefined when everything is closed — and opening one panel collapses the previous one unless multiple is set.

Give every panel an explicit value. A panel without one falls back to its registration index, which works until a v-if or a reorder shifts the indices under the model.

<script setup lang="ts">
  import {
    EmExpansionPanel,
    EmExpansionPanelActivator,
    EmExpansionPanelContent,
    EmExpansionPanelCue,
    EmExpansionPanelGroup,
    EmExpansionPanelHeader,
  } from '@paper/emerald'

  import { shallowRef } from 'vue'

  const open = shallowRef<string>()

  const items = [
    {
      value: 'shipping',
      title: 'How long does shipping take?',
      body: 'Orders leave the warehouse within two business days. Domestic delivery takes three to five days after that; international delivery depends on customs.',
    },
    {
      value: 'returns',
      title: 'What is the return policy?',
      body: 'Anything unused can come back within thirty days for a full refund. Open the order in your account and print the prepaid label.',
    },
    {
      value: 'warranty',
      title: 'Is there a warranty?',
      body: 'Every product carries a two-year warranty against manufacturing defects. Normal wear and accidental damage are not covered.',
    },
  ]
</script>

<template>
  <div class="emerald-docs-stack">
    <EmExpansionPanelGroup v-model="open">
      <EmExpansionPanel v-for="item in items" :key="item.value" :value="item.value">
        <EmExpansionPanelHeader>
          <EmExpansionPanelActivator>
            {{ item.title }}

            <EmExpansionPanelCue />
          </EmExpansionPanelActivator>
        </EmExpansionPanelHeader>

        <EmExpansionPanelContent>
          {{ item.body }}
        </EmExpansionPanelContent>
      </EmExpansionPanel>
    </EmExpansionPanelGroup>
  </div>
</template>

<style>
  .emerald-docs-stack {
    display: flex;
    flex-direction: column;
    gap: var(--emerald-spacing-m, 16px);
    max-width: 520px;
  }
</style>

Anatomy

vue
<script setup lang="ts">
  import {
    EmExpansionPanel,
    EmExpansionPanelActivator,
    EmExpansionPanelContent,
    EmExpansionPanelCue,
    EmExpansionPanelGroup,
    EmExpansionPanelHeader,
  } from '@paper/emerald'
</script>

<template>
  <EmExpansionPanelGroup>
    <EmExpansionPanel>
      <EmExpansionPanelHeader>
        <EmExpansionPanelActivator>
          <EmExpansionPanelCue />
        </EmExpansionPanelActivator>
      </EmExpansionPanelHeader>

      <EmExpansionPanelContent />
    </EmExpansionPanel>
  </EmExpansionPanelGroup>
</template>

Composed on v0

Each part is a one-to-one wrapper over v0’s ExpansionPanel compound: EmExpansionPanelGroup renders ExpansionPanel.Group, EmExpansionPanel renders ExpansionPanel.Root, and the header, activator, content and cue map the same way. v0 owns all of the behavior — the selection model behind the group, the registration of panels, every ARIA attribute, and the hidden toggling of content. Emerald owns only the classes and tokens on top, plus the default chevron: EmExpansionPanelCue fills v0’s ExpansionPanel.Cue with an EmIcon chevron-down glyph that rotates on the data-state="open" attribute v0 publishes.

Two details of the split are worth knowing. First, v0’s Group emits no disabled attribute on its own element, so EmExpansionPanelGroup binds its own data-disabled — that attribute is what Emerald’s stylesheet dims the panels off when the whole group is disabled. Second, collapsed content is hidden, not removed: v0 sets the native hidden attribute and the element stays in the DOM, so anything stateful inside a panel survives closing it.

The wrappers forward v0’s slot props selectively. The group’s default slot receives isDisabled and the select / unselect / toggle functions, the panel’s receives isSelected, isDisabled and its attrs, and the cue’s receives isSelected and attrs — useful when replacing the default chevron. EmExpansionPanelHeader, EmExpansionPanelActivator and EmExpansionPanelContent render plain slots with no slot props.

Examples

Multiple panels open

multiple lifts the one-at-a-time rule: every panel toggles independently, and the model becomes an array of the open panels’ values instead of a single value. Bind an array — an empty one means everything is closed, and seeding it with values opens those panels on mount.

Reach for it when the sections are short and comparing them matters — a spec sheet, a settings review — and stay with single mode when the sections are long enough that two open at once means scrolling between them. The mode is the group’s decision, not the panel’s: there is no per-panel override, so a group is either an accordion or a set of independent disclosures.

Keeping one panel open

mandatory prevents the last open panel from collapsing — clicking its header does nothing once it is the only one open, while switching to a sibling still works. Use it when the panels are the page’s actual content and an all-closed accordion would leave nothing on screen.

Note what mandatory does not do: it does not open anything by itself. A group that mounts with an empty model stays empty until the reader clicks, because there is nothing open yet for the rule to protect. Seed the model with a value, as this example does — or pass mandatory="force" instead, which auto-expands the first non-disabled panel on mount and saves you the seed.

Disabled panels

disabled on a panel disables just that panel; disabled on the group disables every panel at once and dims the whole surface. The two compose — a panel is inert if either flag is set.

A disabled panel’s activator is a native disabled button: it cannot be clicked, it is removed from the tab order, and a keyboard user tabbing through the accordion skips it entirely. That silence is the thing to design around. The header text is still visible, so put the reason a section is unavailable into the title itself — as this example does — rather than relying on a tooltip or a hover state a keyboard user will never reach.

A disabled panel that was already open stays open; disabling prevents interaction, it does not collapse state.

Props

EmExpansionPanelGroup

PropTypeDefaultDescription
v-modelT | T[]undefinedOpen panel’s value — an array when multiple is set
disabledbooleanfalseDisables the group and every panel in it
enrollbooleanfalseAuto-expands non-disabled panels as they register
mandatoryboolean | 'force'falsetrue prevents collapsing the last open panel; 'force' also auto-expands the first non-disabled panel
multiplebooleanfalseLets several panels stay open; changes the model to an array
namespacestringWhich v0 ExpansionPanel context to provide. Only needed when nesting

EmExpansionPanel

PropTypeDefaultDescription
idstringauto-generatedPanel id; also seeds the header and content ARIA ids
valueunknownregistration indexIdentifies the panel in the group’s model. Always set it explicitly
disabledbooleanfalseDisables this panel only
namespacestringWhich v0 ExpansionPanel context to resolve. Only needed when nesting

Parts

PartPropsNotes
EmExpansionPanelHeadernamespaceRenders a heading (h3) wrapping the activator
EmExpansionPanelActivatornamespaceRenders a native button; label and cue go in its default slot
EmExpansionPanelContentnamespaceThe collapsible region; hidden in place, never unmounted
EmExpansionPanelCuenamespaceRotating chevron; replace it through the default slot

Every one of the six parts accepts namespace, defaulting to v0’s v0:expansion-panel. Pass a distinct namespace to the group and the same one to every part inside it when nesting one accordion within another’s content.

Accessibility

The compound follows the WAI-ARIA accordion pattern↗︎, and all of it comes from v0 rather than from Emerald. EmExpansionPanelHeader renders an h3 wrapping the activator, so screen-reader users can jump between panels by heading; EmExpansionPanelActivator renders a native button type="button", so Enter and Space activation, focusability and the implicit role are the platform’s.

ARIA attributes

AttributeValueElement
idHeader idActivator
aria-expandedtrue / falseActivator
aria-controlsContent region idActivator
aria-disabledtrue / falseActivator
disabledPresent when disabledActivator
roleregionContent
aria-labelledbyActivator idContent
hiddenPresent when collapsedContent
aria-hiddentrueCue

The ids are paired: aria-controls on the activator points at the content, and aria-labelledby on the content points back at the activator. Both derive from the panel’s id, so supplying your own gives you stable, inspectable ids. The cue is decorative and invisible to assistive technology — if you replace the default chevron, whatever you put in the slot inherits that, so never make the cue carry meaning the header text does not.

Keyboard

KeyAction
Tab / Shift+TabMoves between panel headers
Enter, SpaceToggles the focused panel — native, since the activator is a real button

There is no arrow-key navigation between headers: each activator is an ordinary tab stop, not part of a roving-focus group. A disabled activator carries the native disabled attribute and drops out of the tab order entirely, so keyboard users pass over it with no announcement — which is why a disabled panel’s reason belongs in its visible title.

Hidden, not gone

Collapsed content keeps its element in the DOM with the hidden attribute set. Screen readers skip it, it is unreachable by keyboard, and form state inside it survives the collapse. If a panel contains focus when it closes — a button inside was clicked and the model changed elsewhere — focus falls back to the document, so avoid collapsing a panel out from under its own controls.

Was this page helpful?

© 2016-1970 Vuetify, LLC
Services
Ctrl+/