Skip to content

Accordion

An items-driven collapsible list, built on Reka UI's Accordion primitive.

Usage

vue-html
<SAccordion :items="[{ value: 'a', label: 'Question one' }]">
  <template #a>
    Answer one.
  </template>
</SAccordion>

Each item's content comes from a named slot matching its value. type defaults to 'multiple' (independent items can each be open at once) - ContentNavigation uses this internally for its collapsible nav groups.

Custom label content

The label slot replaces an item's plain-text label with anything - scoped with item, so a single template can vary per item:

vue-html
<SAccordion :items="items">
  <template #label="{ item }">
    <span class="flex items-center gap-2">
      {{ item.label }}
      <SBadge v-if="item.value === 'a'" label="New" color="primary" size="sm" />
    </span>
  </template>
  ...
</SAccordion>

chevron-icon replaces the expand/collapse chevron the same way any other icon-swap slot in this library does (default: hugeicons:arrow-down-01).

Icon in the label

An icon next to the label is just more label slot content - it's independent of the chevron on the other end of the trigger:

vue-html
<SAccordion :items="items">
  <template #label="{ item }">
    <span class="flex items-center gap-2">
      <SIcon :name="item.icon" class="size-4 text-[var(--selaras-resolved-text-muted)]" />
      {{ item.label }}
    </span>
  </template>
  ...
</SAccordion>

Custom chevron

chevron-icon isn't limited to a single swapped-in icon - here two icons are cross-faded via group-data-[state=open]: instead of the default icon's rotation, for a plus/minus toggle instead of an arrow:

vue-html
<SAccordion :items="items">
  <template #chevron-icon>
    <span class="relative size-4 shrink-0 text-[var(--selaras-resolved-text-muted)]">
      <SIcon name="hugeicons:plus-sign" class="absolute inset-0 transition-opacity group-data-[state=open]:opacity-0" />
      <SIcon name="hugeicons:minus-sign" class="absolute inset-0 opacity-0 transition-opacity group-data-[state=open]:opacity-100" />
    </span>
  </template>
  ...
</SAccordion>

Single mode

type="single" allows only one item open at a time - opening one closes whichever was open before it. collapsible (default true) additionally lets clicking the open item's own trigger close it, rather than leaving at least one item permanently forced open:

A UI component library for Nuxt, themed with tv() and CSS variables.
vue-html
<SAccordion type="single" :items="items" default-value="a">
  <template #a>...</template>
  <template #b>...</template>
</SAccordion>

Sizes

size takes sm / md / lg, scaling the trigger's padding/text and the content's text together:

vue-html
<SAccordion size="sm" :items="items">...</SAccordion>
<SAccordion size="md" :items="items">...</SAccordion>
<SAccordion size="lg" :items="items">...</SAccordion>

Variant

list (default) is a flat list with a shared divider between items. pill gives each item its own bordered, rounded card instead, with a gap between them - the same visual language as Card's own outline variant:

vue-html
<SAccordion variant="list" :items="items">...</SAccordion>
<SAccordion variant="pill" :items="items">...</SAccordion>

Chevron position

chevronPosition moves the chevron to the start of the trigger instead of the end - the label stays flush next to it rather than pushed to the opposite side, since justify-between only applies in the (default) end position:

vue-html
<SAccordion chevron-position="start" :items="items">...</SAccordion>

Disabled

An item's own disabled: true blocks just that item; the accordion's own disabled prop blocks every item at once:

vue-html
<SAccordion :items="[{ value: 'a', label: '...' }, { value: 'b', label: '...', disabled: true }]">
  ...
</SAccordion>
<SAccordion disabled :items="items">
  ...
</SAccordion>

Accessibility

Accordion renders Reka UI's Accordion primitive, so the accessibility semantics come from there rather than being reimplemented here: each trigger is a real <button> (keyboard-activatable with Enter/Space), ↑/↓ move focus between triggers, and each panel is exposed as role="region" with aria-labelledby pointing back at its own trigger - a screen reader always knows which question a given answer belongs to, even if the DOM order changes.

In markdown

Every Selaras component is already globally registered under its S-prefixed name - a bare ::s-accordion{...} block works with no extra wiring (see Callout's own "In markdown" section for how the pattern works generally). items is an array prop, not a string - bind it with a leading : so @nuxtjs/mdc parses the attribute as an expression instead of literal text. Each item's own content is a #slot-name line matching that item's value:

text
::s-accordion{:items='[{"label":"First","value":"a"},{"label":"Second","value":"b"}]'}
#a
Content for the first item.
#b
Content for the second item.
::

Custom :ui

Global and scoped recipe overrides use ui.accordion.

To see exactly what you'd be overriding - the current default classes for every slot and variant - here's Accordion's own theme file:

Theme source "accordion" not found

Props

PropTypeDefault
items{ value: string, label: string, disabled?: boolean, icon?: string }[]-
type'single' | 'multiple''multiple'
defaultValuestring | string[]-
modelValuestring | string[]-
collapsiblebooleantrue
disabledbooleanfalse
size'sm' | 'md' | 'lg'md
variant'list' | 'pill'list
chevronPosition'start' | 'end'end
uiPartial<Record<AccordionSlot, string | object>>-

Events

EventPayloadDescription
update:modelValuestring | string[]Fires when the open item(s) change

Slots

SlotPropsDescription
[item.value]-Content for that item, one named slot per item
label{ item }Replaces an item's label content
chevron-icon-Replaces the expand/collapse chevron (default: hugeicons:arrow-down-01)
Edit this page