How to build an accordion in Vue
Accordions organize large amounts of content into collapsible sections, improving page scannability and reducing initial visual complexity. As the creator of CoreUI with over 12 years of Vue.js experience since 2014, I’ve implemented accordion components in countless enterprise applications. A Vue accordion requires reactive state to track which items are open, methods to toggle items, and transitions for smooth expand/collapse animations. This approach creates a reusable accordion without external dependencies.
Build an accordion using reactive state, v-for for items, and transitions for smooth animations.
<template>
<div class='accordion'>
<div
v-for='(item, index) in items'
:key='index'
class='accordion-item'
>
<div
class='accordion-header'
@click='toggle(index)'
>
<span>{{ item.title }}</span>
<span class='accordion-icon'>{{ isOpen(index) ? '−' : '+' }}</span>
</div>
<Transition name='accordion'>
<div v-if='isOpen(index)' class='accordion-content'>
<div class='accordion-body'>
{{ item.content }}
</div>
</div>
</Transition>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const props = defineProps({
items: {
type: Array,
required: true
},
multiple: {
type: Boolean,
default: false
}
})
const openItems = ref([0])
const toggle = (index) => {
if (props.multiple) {
const idx = openItems.value.indexOf(index)
if (idx > -1) {
openItems.value.splice(idx, 1)
} else {
openItems.value.push(index)
}
} else {
openItems.value = openItems.value.includes(index) ? [] : [index]
}
}
const isOpen = (index) => {
return openItems.value.includes(index)
}
</script>
<style scoped>
.accordion {
border: 1px solid #ddd;
border-radius: 4px;
overflow: hidden;
}
.accordion-item {
border-bottom: 1px solid #ddd;
}
.accordion-item:last-child {
border-bottom: none;
}
.accordion-header {
display: flex;
justify-content: space-between;
padding: 15px;
background: #f5f5f5;
cursor: pointer;
user-select: none;
}
.accordion-header:hover {
background: #e9e9e9;
}
.accordion-icon {
font-weight: bold;
font-size: 20px;
}
.accordion-content {
overflow: hidden;
}
.accordion-body {
padding: 15px;
}
.accordion-enter-active,
.accordion-leave-active {
transition: all 0.3s ease;
}
.accordion-enter-from,
.accordion-leave-to {
max-height: 0;
opacity: 0;
}
.accordion-enter-to,
.accordion-leave-from {
max-height: 500px;
opacity: 1;
}
</style>
Use it in your component:
<template>
<Accordion :items='faqItems' :multiple='true' />
</template>
<script setup>
import { ref } from 'vue'
import Accordion from './Accordion.vue'
const faqItems = ref([
{ title: 'What is Vue?', content: 'Vue is a progressive JavaScript framework...' },
{ title: 'How to install Vue?', content: 'You can install Vue using npm...' },
{ title: 'What are components?', content: 'Components are reusable Vue instances...' }
])
</script>
Best Practice Note
The multiple prop controls whether multiple items can be open simultaneously. For better animations, consider using height transitions with actual calculated heights instead of max-height. Add keyboard navigation (Enter/Space to toggle, Arrow keys to navigate) for accessibility. This is the pattern we use in CoreUI for Vue—building accessible accordion components with smooth animations and flexible configuration options for enterprise admin panels and documentation sites.



