Accordion

A list of sections that expand and collapse in turn.

Sign in, open the creator center, pick the type of work and fill in its details. It joins the review queue once submitted.

<script setup lang="ts">
  import {
    Accordion,
    AccordionContent,
    AccordionItem,
    AccordionTrigger,
    Card,
    Text,
  } from '@hina-ui/vue'
</script>

<template>
  <Card class="w-full max-w-md">
    <Accordion default-value="submit">
      <AccordionItem value="submit">
        <AccordionTrigger>How do I submit a work?</AccordionTrigger>
        <AccordionContent>
          <Text tone="muted" size="sm">
            Sign in, open the creator center, pick the type of work and fill in its details. It
            joins the review queue once submitted.
          </Text>
        </AccordionContent>
      </AccordionItem>
      <AccordionItem value="review">
        <AccordionTrigger>How long does review take?</AccordionTrigger>
        <AccordionContent>
          <Text tone="muted" size="sm">
            Usually within three working days. Incomplete entries are returned for more details.
          </Text>
        </AccordionContent>
      </AccordionItem>
      <AccordionItem value="edit">
        <AccordionTrigger>Can published content be edited?</AccordionTrigger>
        <AccordionContent>
          <Text tone="muted" size="sm">
            Yes. An edit creates a new version, which replaces the current one after review.
          </Text>
        </AccordionContent>
      </AccordionItem>
    </Accordion>
  </Card>
</template>
vue

Usage

import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from '@hina-ui/vue'
ts

The component has four parts: Accordion holds the expanded state, AccordionItem is one section, AccordionTrigger is that section’s heading and toggle, and AccordionContent is the collapsed body. Each section is identified by value.

By default only one section is open at a time, and opening another closes the current one. The trigger carries its own indicator that rotates with the state.

<script setup lang="ts">
  import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Text } from '@hina-ui/vue'
</script>

<template>
  <Accordion class="w-full max-w-md">
    <AccordionItem value="intro">
      <AccordionTrigger>Synopsis</AccordionTrigger>
      <AccordionContent>
        <Text tone="muted" size="sm">
          On my first day at the new school I met a girl on the roof holding an old camera.
        </Text>
      </AccordionContent>
    </AccordionItem>
    <AccordionItem value="staff">
      <AccordionTrigger>Staff</AccordionTrigger>
      <AccordionContent>
        <Text tone="muted" size="sm">Story, script and art are all by the same author.</Text>
      </AccordionContent>
    </AccordionItem>
  </Accordion>
</template>
vue

Use Collapsible when only one piece of content needs to fold.

Examples

Allow closing all

By default the open section cannot be closed by clicking it again. With collapsible set the current section can be closed and the list may end up fully collapsed. defaultValue names the section open at first.

On my first day at the new school I met a girl on the roof holding an old camera.

<script setup lang="ts">
  import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Text } from '@hina-ui/vue'
</script>

<template>
  <Accordion collapsible default-value="intro" class="w-full max-w-md">
    <AccordionItem value="intro">
      <AccordionTrigger>Synopsis</AccordionTrigger>
      <AccordionContent>
        <Text tone="muted" size="sm">
          On my first day at the new school I met a girl on the roof holding an old camera.
        </Text>
      </AccordionContent>
    </AccordionItem>
    <AccordionItem value="staff">
      <AccordionTrigger>Staff</AccordionTrigger>
      <AccordionContent>
        <Text tone="muted" size="sm">Story, script and art are all by the same author.</Text>
      </AccordionContent>
    </AccordionItem>
  </Accordion>
</template>
vue

Several sections open

With type set to multiple each section opens and closes on its own, and v-model and defaultValue take arrays.

On my first day at the new school I met a girl on the roof holding an old camera.

Story, script and art are all by the same author.

<script setup lang="ts">
  import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Text } from '@hina-ui/vue'
</script>

<template>
  <Accordion type="multiple" :default-value="['intro', 'staff']" class="w-full max-w-md">
    <AccordionItem value="intro">
      <AccordionTrigger>Synopsis</AccordionTrigger>
      <AccordionContent>
        <Text tone="muted" size="sm">
          On my first day at the new school I met a girl on the roof holding an old camera.
        </Text>
      </AccordionContent>
    </AccordionItem>
    <AccordionItem value="staff">
      <AccordionTrigger>Staff</AccordionTrigger>
      <AccordionContent>
        <Text tone="muted" size="sm">Story, script and art are all by the same author.</Text>
      </AccordionContent>
    </AccordionItem>
    <AccordionItem value="release">
      <AccordionTrigger>Release</AccordionTrigger>
      <AccordionContent>
        <Text tone="muted" size="sm">
          Released in winter 2024, with Simplified Chinese support.
        </Text>
      </AccordionContent>
    </AccordionItem>
  </Accordion>
</template>
vue

Controlled

v-model hands the expanded state to the outside, so other controls on the page can switch sections too.

On my first day at the new school I met a girl on the roof holding an old camera.

<script setup lang="ts">
  import { ref } from 'vue'
  import {
    Accordion,
    AccordionContent,
    AccordionItem,
    AccordionTrigger,
    Button,
    Inline,
    Stack,
    Text,
  } from '@hina-ui/vue'

  const open = ref('intro')
</script>

<template>
  <Stack class="w-full max-w-md">
    <Inline>
      <Button size="sm" variant="soft" tone="neutral" @click="open = 'intro'">Show synopsis</Button>
      <Button size="sm" variant="soft" tone="neutral" @click="open = 'staff'">Show staff</Button>
    </Inline>
    <Accordion v-model="open">
      <AccordionItem value="intro">
        <AccordionTrigger>Synopsis</AccordionTrigger>
        <AccordionContent>
          <Text tone="muted" size="sm">
            On my first day at the new school I met a girl on the roof holding an old camera.
          </Text>
        </AccordionContent>
      </AccordionItem>
      <AccordionItem value="staff">
        <AccordionTrigger>Staff</AccordionTrigger>
        <AccordionContent>
          <Text tone="muted" size="sm">Story, script and art are all by the same author.</Text>
        </AccordionContent>
      </AccordionItem>
    </Accordion>
  </Stack>
</template>
vue

Disabled

disabled on AccordionItem disables one section; disabled on Accordion disables the whole list.

On my first day at the new school I met a girl on the roof holding an old camera.

<script setup lang="ts">
  import {
    Accordion,
    AccordionContent,
    AccordionItem,
    AccordionTrigger,
    Stack,
    Text,
  } from '@hina-ui/vue'
</script>

<template>
  <Stack gap="lg" class="w-full max-w-md">
    <Accordion default-value="intro">
      <AccordionItem value="intro">
        <AccordionTrigger>Synopsis</AccordionTrigger>
        <AccordionContent>
          <Text tone="muted" size="sm">
            On my first day at the new school I met a girl on the roof holding an old camera.
          </Text>
        </AccordionContent>
      </AccordionItem>
      <AccordionItem value="staff" disabled>
        <AccordionTrigger>Staff</AccordionTrigger>
        <AccordionContent>
          <Text tone="muted" size="sm">Story, script and art are all by the same author.</Text>
        </AccordionContent>
      </AccordionItem>
    </Accordion>
    <Accordion disabled>
      <AccordionItem value="intro">
        <AccordionTrigger>Synopsis</AccordionTrigger>
        <AccordionContent>
          <Text tone="muted" size="sm">
            On my first day at the new school I met a girl on the roof holding an old camera.
          </Text>
        </AccordionContent>
      </AccordionItem>
      <AccordionItem value="staff">
        <AccordionTrigger>Staff</AccordionTrigger>
        <AccordionContent>
          <Text tone="muted" size="sm">Story, script and art are all by the same author.</Text>
        </AccordionContent>
      </AccordionItem>
    </Accordion>
  </Stack>
</template>
vue

Heading level

The trigger renders inside a heading element, h3 by default. level changes it to fit the page’s heading structure.

Frequently asked questions

<script setup lang="ts">
  import {
    Accordion,
    AccordionContent,
    AccordionItem,
    AccordionTrigger,
    Heading,
    Stack,
    Text,
  } from '@hina-ui/vue'
</script>

<template>
  <Stack class="w-full max-w-md">
    <Heading :level="3" size="md">Frequently asked questions</Heading>
    <Accordion>
      <AccordionItem value="submit">
        <AccordionTrigger :level="4">How do I submit a work?</AccordionTrigger>
        <AccordionContent>
          <Text tone="muted" size="sm">
            Sign in, open the creator center, pick the type of work and fill in its details.
          </Text>
        </AccordionContent>
      </AccordionItem>
      <AccordionItem value="review">
        <AccordionTrigger :level="4">How long does review take?</AccordionTrigger>
        <AccordionContent>
          <Text tone="muted" size="sm">Usually within three working days.</Text>
        </AccordionContent>
      </AccordionItem>
    </Accordion>
  </Stack>
</template>
vue

Changing the indicator

The icon slot only replaces the indicator glyph, and the rotation stays with the component; icon set to false removes the indicator.

<script setup lang="ts">
  import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Text } from '@hina-ui/vue'
  import { Plus } from '@lucide/vue'
</script>

<template>
  <Accordion class="w-full max-w-md">
    <AccordionItem value="intro">
      <AccordionTrigger>
        Synopsis
        <template #icon><Plus /></template>
      </AccordionTrigger>
      <AccordionContent>
        <Text tone="muted" size="sm">
          On my first day at the new school I met a girl on the roof holding an old camera.
        </Text>
      </AccordionContent>
    </AccordionItem>
    <AccordionItem value="staff">
      <AccordionTrigger :icon="false">Staff</AccordionTrigger>
      <AccordionContent>
        <Text tone="muted" size="sm">Story, script and art are all by the same author.</Text>
      </AccordionContent>
    </AccordionItem>
  </Accordion>
</template>
vue

Behaviour

  • The content height animates when a section opens or closes, with the same transition in both directions.
  • Closed content is removed from the accessibility tree, and keyboard focus does not enter it.
  • Sections are separated by hairlines. The component has no background or border of its own; put it in a Card for a card look.

Accessibility

  • The trigger is a button inside a heading, with aria-expanded and aria-controls.
  • The up and down arrow keys move focus between triggers; Home and End jump to the first and last.
  • The content has the region role and screen readers read it as one.

API

Accordion

Prop
Type
Default
Description
type
'single' | 'multiple'
'single'
Whether one or several sections can be open at once
collapsible
boolean
false
Whether all sections may be closed in single mode
modelValue
string | string[]
—
The open sections, supports v-model
defaultValue
string | string[]
—
The sections open at first
disabled
boolean
false
Whether the whole list is disabled
class
string
—
Classes appended to the root element
Event
Payload
Description
update:modelValue
value: string | string[]
The open sections changed

AccordionItem

Prop
Type
Default
Description
value
string
—
Identifies the section, required
disabled
boolean
false
Whether the section is disabled
class
string
—
Classes appended to the section

AccordionTrigger

Prop
Type
Default
Description
level
2 | 3 | 4 | 5 | 6
3
Heading level
icon
boolean
true
Whether to show the indicator
class
string
—
Classes appended to the trigger
Slot
Description
default
The trigger’s text
icon
Replaces the indicator glyph

AccordionContent

Prop
Type
Default
Description
class
string
—
Classes appended to the content