Accordion

A stacked set of collapsible panels.

Usage

Use the Accordion component to display a list of collapsible items.

Yes! Pohon UI is completely free and open source under the MIT license. All 125+ components are available to everyone.
<script setup lang="ts">
import type { AccordionItem } from 'pohon-ui'

const items = ref<AccordionItem[]>([
  {
    label: 'Is Pohon UI free to use?',
    content: 'Yes! Pohon UI is completely free and open source under the MIT license. All 125+ components are available to everyone.'
  },
  {
    label: 'Can I use Pohon UI with Vue without Nuxt?',
    content: 'Yes! While optimized for Nuxt, Pohon UI works perfectly with standalone Vue projects via our Vite plugin. You can follow the [installation guide](/pohon/getting-started/installation/vue) to get started.'
  },
  {
    label: 'Is Pohon UI production-ready?',
    content: 'Pohon UI is designed for production Vue applications and maintained with component tests and regular updates.'
  }
])
</script>

<template>
  <PAccordion :items="items" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { AccordionItem } from 'pohon-ui'

const items = ref<AccordionItem[]>([
  {
    label: 'Is Pohon UI free to use?',
    content: 'Yes! Pohon UI is completely free and open source under the MIT license. All 125+ components are available to everyone.'
  },
  {
    label: 'Can I use Pohon UI with Vue without Nuxt?',
    content: 'Yes! While optimized for Nuxt, Pohon UI works perfectly with standalone Vue projects via our Vite plugin. You can follow the [installation guide](/pohon/getting-started/installation/vue) to get started.'
  },
  {
    label: 'Is Pohon UI production-ready?',
    content: 'Pohon UI is designed for production Vue applications and maintained with component tests and regular updates.'
  }
])
</script>

<template>
  <PAccordion :items="items" />
</template>

Items

Use the items prop as an array of objects with the following properties:

  • label?: string
  • icon?: string
  • trailingIcon?: string
  • content?: string
  • value?: string
  • disabled?: boolean
  • slot?: string
  • class?: any
  • ui?: { item?: ClassNameValue, header?: ClassNameValue, trigger?: ClassNameValue, leadingIcon?: ClassNameValue, label?: ClassNameValue, trailingIcon?: ClassNameValue, content?: ClassNameValue, body?: ClassNameValue }
<script setup lang="ts">
import type { AccordionItem } from 'pohon-ui'

const items = ref<AccordionItem[]>([
  {
    label: 'Icons',
    icon: 'i-lucide-smile',
    content: 'You have nothing to do, @nuxt/icon will handle it automatically.'
  },
  {
    label: 'Colors',
    icon: 'i-lucide-swatch-book',
    content: 'Choose a primary and a neutral color from your UnoCSS theme.'
  },
  {
    label: 'Components',
    icon: 'i-lucide-box',
    content: 'You can customize components by using the `class` / `ui` props or in your app.config.ts.'
  }
])
</script>

<template>
  <PAccordion :items="items" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { AccordionItem } from 'pohon-ui'

const items = ref<AccordionItem[]>([
  {
    label: 'Icons',
    icon: 'i-lucide-smile',
    content: 'You have nothing to do, @nuxt/icon will handle it automatically.'
  },
  {
    label: 'Colors',
    icon: 'i-lucide-swatch-book',
    content: 'Choose a primary and a neutral color from your UnoCSS theme.'
  },
  {
    label: 'Components',
    icon: 'i-lucide-box',
    content: 'You can customize components by using the `class` / `ui` props or in your app.config.ts.'
  }
])
</script>

<template>
  <PAccordion :items="items" />
</template>

Multiple

Set the type prop to multiple to allow multiple items to be active at the same time. Defaults to single.

<script setup lang="ts">
import type { AccordionItem } from 'pohon-ui'

const items = ref<AccordionItem[]>([
  {
    label: 'Icons',
    icon: 'i-lucide-smile',
    content: 'You have nothing to do, @nuxt/icon will handle it automatically.'
  },
  {
    label: 'Colors',
    icon: 'i-lucide-swatch-book',
    content: 'Choose a primary and a neutral color from your UnoCSS theme.'
  },
  {
    label: 'Components',
    icon: 'i-lucide-box',
    content: 'You can customize components by using the `class` / `ui` props or in your app.config.ts.'
  }
])
</script>

<template>
  <PAccordion type="multiple" :items="items" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { AccordionItem } from 'pohon-ui'

const items = ref<AccordionItem[]>([
  {
    label: 'Icons',
    icon: 'i-lucide-smile',
    content: 'You have nothing to do, @nuxt/icon will handle it automatically.'
  },
  {
    label: 'Colors',
    icon: 'i-lucide-swatch-book',
    content: 'Choose a primary and a neutral color from your UnoCSS theme.'
  },
  {
    label: 'Components',
    icon: 'i-lucide-box',
    content: 'You can customize components by using the `class` / `ui` props or in your app.config.ts.'
  }
])
</script>

<template>
  <PAccordion type="multiple" :items="items" />
</template>

Collapsible

When type is single, you can set the collapsible prop to false to prevent the active item from collapsing.

<script setup lang="ts">
import type { AccordionItem } from 'pohon-ui'

const items = ref<AccordionItem[]>([
  {
    label: 'Icons',
    icon: 'i-lucide-smile',
    content: 'You have nothing to do, @nuxt/icon will handle it automatically.'
  },
  {
    label: 'Colors',
    icon: 'i-lucide-swatch-book',
    content: 'Choose a primary and a neutral color from your UnoCSS theme.'
  },
  {
    label: 'Components',
    icon: 'i-lucide-box',
    content: 'You can customize components by using the `class` / `ui` props or in your app.config.ts.'
  }
])
</script>

<template>
  <PAccordion :items="items" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { AccordionItem } from 'pohon-ui'

const items = ref<AccordionItem[]>([
  {
    label: 'Icons',
    icon: 'i-lucide-smile',
    content: 'You have nothing to do, @nuxt/icon will handle it automatically.'
  },
  {
    label: 'Colors',
    icon: 'i-lucide-swatch-book',
    content: 'Choose a primary and a neutral color from your UnoCSS theme.'
  },
  {
    label: 'Components',
    icon: 'i-lucide-box',
    content: 'You can customize components by using the `class` / `ui` props or in your app.config.ts.'
  }
])
</script>

<template>
  <PAccordion :items="items" />
</template>

Unmount

Use the unmount-on-hide prop to prevent the content from being unmounted when the accordion is collapsed. Defaults to true.

<script setup lang="ts">
import type { AccordionItem } from 'pohon-ui'

const items = ref<AccordionItem[]>([
  {
    label: 'Icons',
    icon: 'i-lucide-smile',
    content: 'You have nothing to do, @nuxt/icon will handle it automatically.'
  },
  {
    label: 'Colors',
    icon: 'i-lucide-swatch-book',
    content: 'Choose a primary and a neutral color from your UnoCSS theme.'
  },
  {
    label: 'Components',
    icon: 'i-lucide-box',
    content: 'You can customize components by using the `class` / `ui` props or in your app.config.ts.'
  }
])
</script>

<template>
  <PAccordion :items="items" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { AccordionItem } from 'pohon-ui'

const items = ref<AccordionItem[]>([
  {
    label: 'Icons',
    icon: 'i-lucide-smile',
    content: 'You have nothing to do, @nuxt/icon will handle it automatically.'
  },
  {
    label: 'Colors',
    icon: 'i-lucide-swatch-book',
    content: 'Choose a primary and a neutral color from your UnoCSS theme.'
  },
  {
    label: 'Components',
    icon: 'i-lucide-box',
    content: 'You can customize components by using the `class` / `ui` props or in your app.config.ts.'
  }
])
</script>

<template>
  <PAccordion :items="items" />
</template>
You can inspect the DOM to see each item's content being rendered.

Disabled

Use the disabled property to disable the Accordion.

You can also disable a specific item by using the disabled property in the item object.

<script setup lang="ts">
import type { AccordionItem } from 'pohon-ui'

const items = ref<AccordionItem[]>([
  {
    label: 'Icons',
    icon: 'i-lucide-smile',
    content: 'You have nothing to do, @nuxt/icon will handle it automatically.'
  },
  {
    label: 'Colors',
    icon: 'i-lucide-swatch-book',
    content: 'Choose a primary and a neutral color from your UnoCSS theme.',
    disabled: true
  },
  {
    label: 'Components',
    icon: 'i-lucide-box',
    content: 'You can customize components by using the `class` / `ui` props or in your app.config.ts.'
  }
])
</script>

<template>
  <PAccordion disabled :items="items" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { AccordionItem } from 'pohon-ui'

const items = ref<AccordionItem[]>([
  {
    label: 'Icons',
    icon: 'i-lucide-smile',
    content: 'You have nothing to do, @nuxt/icon will handle it automatically.'
  },
  {
    label: 'Colors',
    icon: 'i-lucide-swatch-book',
    content: 'Choose a primary and a neutral color from your UnoCSS theme.',
    disabled: true
  },
  {
    label: 'Components',
    icon: 'i-lucide-box',
    content: 'You can customize components by using the `class` / `ui` props or in your app.config.ts.'
  }
])
</script>

<template>
  <PAccordion disabled :items="items" />
</template>

Trailing Icon

Use the trailing-icon prop to customize the trailing Icon of each item. Defaults to i-lucide-chevron-down.

You can also set an icon for a specific item by using the trailingIcon property in the item object.
<script setup lang="ts">
import type { AccordionItem } from 'pohon-ui'

const items = ref<AccordionItem[]>([
  {
    label: 'Icons',
    icon: 'i-lucide-smile',
    content: 'You have nothing to do, @nuxt/icon will handle it automatically.',
    trailingIcon: 'i-lucide-plus'
  },
  {
    label: 'Colors',
    icon: 'i-lucide-swatch-book',
    content: 'Choose a primary and a neutral color from your UnoCSS theme.'
  },
  {
    label: 'Components',
    icon: 'i-lucide-box',
    content: 'You can customize components by using the `class` / `ui` props or in your app.config.ts.'
  }
])
</script>

<template>
  <PAccordion trailing-icon="i-lucide-arrow-down" :items="items" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { AccordionItem } from 'pohon-ui'

const items = ref<AccordionItem[]>([
  {
    label: 'Icons',
    icon: 'i-lucide-smile',
    content: 'You have nothing to do, @nuxt/icon will handle it automatically.',
    trailingIcon: 'i-lucide-plus'
  },
  {
    label: 'Colors',
    icon: 'i-lucide-swatch-book',
    content: 'Choose a primary and a neutral color from your UnoCSS theme.'
  },
  {
    label: 'Components',
    icon: 'i-lucide-box',
    content: 'You can customize components by using the `class` / `ui` props or in your app.config.ts.'
  }
])
</script>

<template>
  <PAccordion trailing-icon="i-lucide-arrow-down" :items="items" />
</template>
You can customize this icon globally in your app.config.ts under ui.icons.chevronDown key.
You can customize this icon globally in your vite.config.ts under ui.icons.chevronDown key.

Examples

Control active item(s)

You can control the active item by using the default-value prop or the v-model directive with the value of the item. If no value is provided, it defaults to the index as a string.

You have nothing to do, @nuxt/icon will handle it automatically.
<script setup lang="ts">
import type { AccordionItem } from 'pohon-ui';

const items: Array<AccordionItem> = [
  {
    label: 'Icons',
    icon: 'i-lucide-smile',
    content: 'You have nothing to do, @nuxt/icon will handle it automatically.',
  },
  {
    label: 'Colors',
    icon: 'i-lucide-swatch-book',
    content: 'Choose a primary and a neutral color from your UnoCSS theme.',
  },
  {
    label: 'Components',
    icon: 'i-lucide-box',
    content: 'You can customize components by using the `class` / `ui` props or in your app.config.ts.',
  },
];

const active = ref('0');

// Note: This is for demonstration purposes only. Don't do this at home.
onMounted(() => {
  setInterval(() => {
    active.value = String((Number(active.value) + 1) % items.length);
  }, 2000);
});
</script>

<template>
  <PAccordion
    v-model="active"
    :items="items"
  />
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import type { AccordionItem } from 'pohon-ui';

const items: Array<AccordionItem> = [
  {
    label: 'Icons',
    icon: 'i-lucide-smile',
    content: 'You have nothing to do, @nuxt/icon will handle it automatically.',
  },
  {
    label: 'Colors',
    icon: 'i-lucide-swatch-book',
    content: 'Choose a primary and a neutral color from your UnoCSS theme.',
  },
  {
    label: 'Components',
    icon: 'i-lucide-box',
    content: 'You can customize components by using the `class` / `ui` props or in your app.config.ts.',
  },
];

const active = ref('0');

// Note: This is for demonstration purposes only. Don't do this at home.
onMounted(() => {
  setInterval(() => {
    active.value = String((Number(active.value) + 1) % items.length);
  }, 2000);
});
</script>

<template>
  <PAccordion
    v-model="active"
    :items="items"
  />
</template>
Use the value-key prop to change the key used to match items when a v-model or default-value is provided.
When type="multiple", ensure to pass an array to the default-value prop or the v-model directive.

With drag and drop

Use the useSortable composable from @vueuse/integrations to enable drag and drop functionality on the Accordion. This integration wraps Sortable.js to provide a seamless drag and drop experience.

<script setup lang="ts">
import type { AccordionItem } from 'pohon-ui';
import { useSortable } from '@vueuse/integrations/useSortable';

const items = shallowRef<Array<AccordionItem>>([
  {
    label: 'Icons',
    icon: 'i-lucide-smile',
    content: 'You have nothing to do, @nuxt/icon will handle it automatically.',
  },
  {
    label: 'Colors',
    icon: 'i-lucide-swatch-book',
    content: 'Choose a primary and a neutral color from your UnoCSS theme.',
  },
  {
    label: 'Components',
    icon: 'i-lucide-box',
    content: 'You can customize components by using the `class` / `ui` props or in your app.config.ts.',
  },
]);

const accordion = useTemplateRef<HTMLElement>('accordion');

useSortable(accordion, items, {
  animation: 150,
});
</script>

<template>
  <PAccordion
    ref="accordion"
    :items="items"
  />
</template>
<script setup lang="ts">
import { shallowRef, useTemplateRef } from 'vue'
import type { AccordionItem } from 'pohon-ui';
import { useSortable } from '@vueuse/integrations/useSortable';

const items = shallowRef<Array<AccordionItem>>([
  {
    label: 'Icons',
    icon: 'i-lucide-smile',
    content: 'You have nothing to do, @nuxt/icon will handle it automatically.',
  },
  {
    label: 'Colors',
    icon: 'i-lucide-swatch-book',
    content: 'Choose a primary and a neutral color from your UnoCSS theme.',
  },
  {
    label: 'Components',
    icon: 'i-lucide-box',
    content: 'You can customize components by using the `class` / `ui` props or in your app.config.ts.',
  },
]);

const accordion = useTemplateRef<HTMLElement>('accordion');

useSortable(accordion, items, {
  animation: 150,
});
</script>

<template>
  <PAccordion
    ref="accordion"
    :items="items"
  />
</template>

With body slot

Use the #body slot to customize the body of each item.

<script setup lang="ts">
import type { AccordionItem } from 'pohon-ui';

const items: Array<AccordionItem> = [
  {
    label: 'Icons',
    icon: 'i-lucide-smile',
  },
  {
    label: 'Colors',
    icon: 'i-lucide-swatch-book',
  },
  {
    label: 'Components',
    icon: 'i-lucide-box',
  },
];
</script>

<template>
  <PAccordion :items="items">
    <template #body="{ item }">
      This is the {{ item.label }} panel.
    </template>
  </PAccordion>
</template>
The #body slot includes some pre-defined styles, use the #content slot if you want to start from scratch.

With content slot

Use the #content slot to customize the content of each item.

<script setup lang="ts">
import type { AccordionItem } from 'pohon-ui';

const items: Array<AccordionItem> = [
  {
    label: 'Icons',
    icon: 'i-lucide-smile',
  },
  {
    label: 'Colors',
    icon: 'i-lucide-swatch-book',
  },
  {
    label: 'Components',
    icon: 'i-lucide-box',
  },
];
</script>

<template>
  <PAccordion :items="items">
    <template #content="{ item }">
      <p class="text-sm color-text-muted pb-3.5">
        This is the {{ item.label }} panel.
      </p>
    </template>
  </PAccordion>
</template>

With custom slot

Use the slot property to customize a specific item.

You will have access to the following slots:

  • #{{ item.slot }}
  • #{{ item.slot }}-body
<script setup lang="ts">
import type { AccordionItem } from 'pohon-ui';

const items = [
  {
    label: 'Icons',
    icon: 'i-lucide-smile',
    content: 'You have nothing to do, @nuxt/icon will handle it automatically.',
  },
  {
    label: 'Colors',
    icon: 'i-lucide-swatch-book',
    slot: 'colors' as const,
    content: 'Choose a primary and a neutral color from your UnoCSS theme.',
  },
  {
    label: 'Components',
    icon: 'i-lucide-box',
    content: 'You can customize components by using the `class` / `ui` props or in your app.config.ts.',
  },
] satisfies Array<AccordionItem>;
</script>

<template>
  <PAccordion :items="items">
    <template #colors="{ item }">
      <p class="text-sm color-primary pb-3.5">
        {{ item.content }}
      </p>
    </template>
  </PAccordion>
</template>

With markdown content

You can use the Markdown component from @comark/vue to render markdown in the accordion items.

Through Akar integration, Pohon UI provides automatic ARIA attributes, keyboard navigation, focus management, and screen reader support. While offering a strong foundation, testing in your specific use case remains important.
<script setup lang="ts">
import { Markdown } from '@comark/vue';

const items = [
  {
    label: 'Is Pohon UI free to use?',
    icon: 'i-lucide-circle-help',
    content: 'Yes! Pohon UI is completely free and open source under the MIT license. All 125+ components are available to everyone.',
  },
  {
    label: 'Can I use Pohon UI with Vue without Nuxt?',
    icon: 'i-lucide-circle-help',
    content: 'Yes! While optimized for Nuxt, Pohon UI works perfectly with standalone Vue projects via our Vite plugin. You can follow the [installation guide](/pohon/getting-started/installation/vue) to get started.',
  },
  {
    label: 'Will Pohon UI work with other CSS frameworks like UnoCSS?',
    icon: 'i-lucide-circle-help',
    content: 'No. Pohon UI is designed exclusively for UnoCSS. UnoCSS support would require significant architecture changes due to different class naming conventions.',
  },
  {
    label: 'How does Pohon UI handle accessibility?',
    icon: 'i-lucide-circle-help',
    content: 'Through [Akar](https://akar.vinicunca.dev/docs/overview/accessibility) integration, Pohon UI provides **automatic ARIA attributes**, **keyboard navigation**, **focus management**, and **screen reader support**. While offering a strong foundation, testing in your specific use case remains important.',
  },
  {
    label: 'How is Pohon UI tested?',
    icon: 'i-lucide-circle-help',
    content: 'Pohon UI has component tests covering core behavior and accessibility.',
  },
  {
    label: 'Is Pohon UI production-ready?',
    icon: 'i-lucide-circle-help',
    content: 'Pohon UI is designed for production Vue applications and maintained with component tests and regular updates.',
  },
];
</script>

<template>
  <PAccordion
    type="multiple"
    :items="items"
    :unmount-on-hide="false"
    :default-value="['3']"
    :ui="{
      trigger: 'text-base',
      body: 'text-base color-text-muted',
    }"
  >
    <template #body="{ item }">
      <Markdown
        :value="item.content"
        unwrap="p"
      />
    </template>
  </PAccordion>
</template>

API

Props

Prop Default Type

Slots

Slot Type

Emits

Event Type

Theme

app.config.ts
export default defineAppConfig({
  ui: {
    accordion: {
      slots: {
        root: 'w-full',
        item: 'border-b border-border last:border-b-0',
        header: 'flex',
        trigger: 'group flex-1 flex items-center gap-1.5 font-500 text-sm py-3.5 outline-primary/25 focus-visible:outline-3 min-w-0 rounded-md',
        content: 'focus:outline-none data-[state=closed]:overflow-hidden data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down',
        body: 'text-sm pb-3.5',
        leadingIcon: 'shrink-0 size-5',
        trailingIcon: 'ms-auto shrink-0 size-5 transition-transform-280 ease-out group-data-[state=open]:rotate-180 motion-reduce:transition-none',
        label: 'text-start break-words'
      },
      variants: {
        disabled: {
          true: {
            trigger: 'cursor-not-allowed opacity-75'
          }
        }
      }
    }
  }
})
vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from 'pohon-ui/vite'

export default defineConfig({
  plugins: [
    vue(),
    ui({
      ui: {
        accordion: {
          slots: {
            root: 'w-full',
            item: 'border-b border-border last:border-b-0',
            header: 'flex',
            trigger: 'group flex-1 flex items-center gap-1.5 font-500 text-sm py-3.5 outline-primary/25 focus-visible:outline-3 min-w-0 rounded-md',
            content: 'focus:outline-none data-[state=closed]:overflow-hidden data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down',
            body: 'text-sm pb-3.5',
            leadingIcon: 'shrink-0 size-5',
            trailingIcon: 'ms-auto shrink-0 size-5 transition-transform-280 ease-out group-data-[state=open]:rotate-180 motion-reduce:transition-none',
            label: 'text-start break-words'
          },
          variants: {
            disabled: {
              true: {
                trigger: 'cursor-not-allowed opacity-75'
              }
            }
          }
        }
      }
    })
  ]
})

Changelog

No recent changes