---
name: accordion
title: Accordion
url: https://ui.frontic.com/components/ui/accordion.md
tier: ui
install: npx @frontic/ui add accordion
files:
  - components/ui/accordion/Accordion.vue
  - components/ui/accordion/AccordionContent.vue
  - components/ui/accordion/AccordionItem.vue
  - components/ui/accordion/AccordionTrigger.vue
  - components/ui/accordion/index.ts
dependencies:
  - "@lucide/vue"
  - "@vueuse/core"
  - reka-ui
---

# Accordion

Stacked sections the shopper opens and closes, one or several at a time, such as shipping and returns on a product page.

## Install

```sh
npx @frontic/ui add accordion
```

Used by: [Filters](https://ui.frontic.com/components/sections/filters.md), [Category Grid](https://ui.frontic.com/components/templates/category-grid.md)

npm packages: `@lucide/vue`, `@vueuse/core`, `reka-ui`

## Examples

```vue
<script setup lang="ts">
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/components/ui/accordion'
</script>

<template>
  <Accordion type="single" collapsible class="max-w-md">
    <AccordionItem value="shipping">
      <AccordionTrigger>How long does shipping take?</AccordionTrigger>
      <AccordionContent>Orders ship within two working days.</AccordionContent>
    </AccordionItem>
    <AccordionItem value="returns">
      <AccordionTrigger>Can I return an item?</AccordionTrigger>
      <AccordionContent>Send anything back within 30 days.</AccordionContent>
    </AccordionItem>
  </Accordion>
</template>
```

## Anatomy

```vue
<Accordion>
  <AccordionItem>
    <AccordionTrigger />
    <AccordionContent />
  </AccordionItem>
</Accordion>
```

## API reference

### Accordion

Groups all parts of the accordion.

`data-slot="accordion"` · `Accordion.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `collapsible` | `boolean` | `false` | When type is "single", allows closing content when clicking trigger for an open item. When type is "multiple", this prop has no effect. |
| `disabled` | `boolean` | `false` | When `true`, prevents the user from interacting with the accordion and all its items |
| `dir` | `Direction`: `'ltr' \| 'rtl'` | `'ltr'` | The reading direction of the accordion when applicable. If omitted, assumes LTR (left-to-right) reading mode. |
| `orientation` | `DataOrientation`: `'vertical' \| 'horizontal'` | `'vertical'` | The orientation of the accordion. |
| `unmountOnHide` | `boolean` | `true` | When `true`, the element will be unmounted on closed state. |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` |  | The element or component this component should render as. Can be overwritten by `asChild`. |
| `type` | `SingleOrMultipleType`: `'single' \| 'multiple'` |  | Determines whether a "single" or "multiple" items can be selected at a time.<br><br>This prop will overwrite the inferred type from `modelValue` and `defaultValue`. |
| `modelValue` | `string \| string[]` |  | The controlled value of the active item(s).<br><br>Use this when you need to control the state of the items. Can be binded with `v-model` |
| `defaultValue` | `string \| string[]` |  | The default active value of the item(s).<br><br>Use when you do not need to control the state of the item(s). |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

| Event | Payload | Description |
| --- | --- | --- |
| `update:modelValue` | `value: string \| string[] \| undefined` | Event handler called when the expanded state of an item changes |

| Slot | Slot props | Description |
| --- | --- | --- |
| `default` | `{ modelValue: AcceptableValue \| AcceptableValue[] \| undefined; }` | Holds the accordion's items; gets the value of the open item or items. |

### AccordionContent

Holds what its item shows while open.

`data-slot="accordion-content"` · `AccordionContent.vue`

Styling: `accordion-body`, which extends [`link-foreground`](https://ui.frontic.com/DESIGN.md#link-foreground). Change it there and every part that extends it follows.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `forceMount` | `boolean` | `false` | Used to force mounting when more control is needed. Useful when controlling animation with Vue animation libraries. |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` |  | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-disabled` |  | Present while the part is disabled. |
| `data-orientation` | `'horizontal' \| 'vertical'` | The direction the part is laid out in. |
| `data-state` | `'open' \| 'closed'` | The state the part is in, one value per state. |

| CSS variable | Description |
| --- | --- |
| `--reka-accordion-content-height` | The content's height while open, for animating it open and closed. |
| `--reka-accordion-content-width` | The content's width while open, for animating it open and closed. |
| `--reka-collapsible-content-height` | The content's height while open, for animating it open and closed. |
| `--reka-collapsible-content-width` | The content's width while open, for animating it open and closed. |

### AccordionItem

Groups a trigger with the content it opens and closes.

`data-slot="accordion-item"` · `AccordionItem.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `disabled` | `boolean` | `false` | Whether or not an accordion item is disabled from user interaction. When `true`, prevents the user from interacting with the item. |
| `value` (required) | `string` |  | A string value for the accordion item. All items within an accordion should use a unique value. |
| `unmountOnHide` | `boolean` |  | When `true`, the element will be unmounted on closed state. |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` |  | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

| Slot | Slot props | Description |
| --- | --- | --- |
| `default` | `{ open: boolean; }` | Holds the item's trigger and content; gets whether the item is open. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-disabled` |  | Present while the part is disabled. |
| `data-orientation` | `'horizontal' \| 'vertical'` | The direction the part is laid out in. |
| `data-state` | `'open' \| 'closed'` | The state the part is in, one value per state. |

### AccordionTrigger

Opens and closes the content of its item.

`data-slot="accordion-trigger"` · `AccordionTrigger.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` |  | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

| Slot | Slot props | Description |
| --- | --- | --- |
| `icon` |  | Replaces the plus icon, which turns into a cross while the item is open. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-disabled` |  | Present while the part is disabled. |
| `data-orientation` | `'horizontal' \| 'vertical'` | The direction the part is laid out in. |
| `data-state` | `'open' \| 'closed'` | The state the part is in, one value per state. |

## Styling

| Part | Recipe |
| --- | --- |
| `default` | [`accordion`](https://ui.frontic.com/DESIGN.md#accordion) |
| `item` | [`accordion-item`](https://ui.frontic.com/DESIGN.md#accordion-item) |
| `trigger` | [`accordion-trigger`](https://ui.frontic.com/DESIGN.md#accordion-trigger) |
| `focus-ring` | [`focus-ring`](https://ui.frontic.com/DESIGN.md#focus-ring) |
| `disabled` | [`disabled`](https://ui.frontic.com/DESIGN.md#disabled) |
| `body` | [`accordion-body`](https://ui.frontic.com/DESIGN.md#accordion-body) |

States with a look of their own: hover, focus and disabled.

## Source

`components/ui/accordion/Accordion.vue`

```vue
<script lang="ts">
/** Groups all parts of the accordion. */
export default {}
</script>

<script setup lang="ts">
import type { AcceptableValue, AccordionRootEmits, AccordionRootProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { AccordionRoot, useForwardPropsEmits } from 'reka-ui'

const props = defineProps<AccordionRootProps & { class?: HTMLAttributes['class'] }>()
const emits = defineEmits<AccordionRootEmits>()

defineSlots<{
  /** Holds the accordion's items; gets the value of the open item or items. */
  default?: (props: { modelValue: AcceptableValue | AcceptableValue[] | undefined }) => unknown
}>()

const delegatedProps = reactiveOmit(props, 'class')
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>

<template>
  <AccordionRoot v-slot="slotProps" data-slot="accordion" v-bind="forwarded" :class="['fui-accordion', props.class]">
    <slot v-bind="slotProps" />
  </AccordionRoot>
</template>
```

`components/ui/accordion/AccordionContent.vue`

```vue
<script lang="ts">
/** Holds what its item shows while open. */
export default {}
</script>

<script setup lang="ts">
import type { AccordionContentProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { AccordionContent, useForwardProps } from 'reka-ui'

const props = defineProps<AccordionContentProps & { class?: HTMLAttributes['class'] }>()

const delegatedProps = reactiveOmit(props, 'class')
const forwarded = useForwardProps(delegatedProps)
</script>

<template>
  <AccordionContent data-slot="accordion-content" v-bind="forwarded" class="data-open:animate-accordion-down data-closed:animate-accordion-up overflow-hidden">
    <div :class="['fui-accordion-body [&_p:not(:last-child)]:mb-4', props.class]">
      <slot />
    </div>
  </AccordionContent>
</template>
```

`components/ui/accordion/AccordionItem.vue`

```vue
<script lang="ts">
/** Groups a trigger with the content it opens and closes. */
export default {}
</script>

<script setup lang="ts">
import type { AccordionItemProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { AccordionItem, useForwardProps } from 'reka-ui'

const props = defineProps<AccordionItemProps & { class?: HTMLAttributes['class'] }>()

defineSlots<{
  /** Holds the item's trigger and content; gets whether the item is open. */
  default?: (props: { open: boolean }) => unknown
}>()

const delegatedProps = reactiveOmit(props, 'class')

const forwardedProps = useForwardProps(delegatedProps)
</script>

<template>
  <AccordionItem v-slot="slotProps" data-slot="accordion-item" v-bind="forwardedProps" :class="['fui-accordion-item last:border-b-0', props.class]">
    <slot v-bind="slotProps" />
  </AccordionItem>
</template>
```

`components/ui/accordion/AccordionTrigger.vue`

```vue
<script lang="ts">
/** Opens and closes the content of its item. */
export default {}
</script>

<script setup lang="ts">
import { PlusIcon } from '@lucide/vue'

import type { AccordionTriggerProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { AccordionHeader, AccordionTrigger, useForwardProps } from 'reka-ui'

const props = defineProps<AccordionTriggerProps & { class?: HTMLAttributes['class'] }>()

defineSlots<{
  default?: () => unknown
  /** Replaces the plus icon, which turns into a cross while the item is open. */
  icon?: () => unknown
}>()

const delegatedProps = reactiveOmit(props, 'class')
const forwarded = useForwardProps(delegatedProps)
</script>

<template>
  <AccordionHeader class="flex">
    <AccordionTrigger
      data-slot="accordion-trigger"
      v-bind="forwarded"
      :class="[
        'fui-accordion-trigger group/accordion-trigger relative transition-all outline-none focus-visible:fui-focus-ring disabled:pointer-events-none disabled:fui-disabled **:data-icon:ms-auto',
        props.class,
      ]"
    >
      <slot />
      <slot name="icon">
        <PlusIcon
          data-slot="accordion-trigger-icon"
          data-icon
          class="pointer-events-none shrink-0 transition-transform duration-200 ease-out motion-reduce:transition-none group-aria-expanded/accordion-trigger:rotate-45"
        />
      </slot>
    </AccordionTrigger>
  </AccordionHeader>
</template>
```
