Dynamic CSS height transition from any value to auto and vice versa. Accordion-ready.
Examples and Demo - Stackblitz
npm i vue-collapsed<script setup>
import { ref } from 'vue'
import { Collapse } from 'vue-collapsed'
const isExpanded = ref(false)
</script>
<template>
<button @click="isExpanded = !isExpanded">Toggle</button>
<Collapse :when="isExpanded">
<p>{{ 'Collapsed '.repeat(100) }}</p>
</Collapse>
</template>| Name | Type | Description | Required |
|---|---|---|---|
when |
boolean | Controls the collapse/expand state | β |
baseHeight |
number | Collapsed height in px. Defaults to 0. |
β |
as |
keyof HTMLElementTagNameMap | Tag to use instead of div |
β |
| Name | Type | Description |
|---|---|---|
@expand |
() => void | Emitted when expansion starts |
@expanded |
() => void | Emitted when expansion completes |
@collapse |
() => void | Emitted when collapse starts |
@collapsed |
() => void | Emitted when collapse completes |
By default, the following transition is always added to the Collapse element:
transition: height var(--vc-auto-duration) cubic-bezier(0.33, 1, 0.68, 1);--vc-auto-duration is calculated dynamically and corresponds to the optimal transition duration based on the element's height.
To use a custom duration or easing, add a class to the Collapse component that transitions the height property:
.collapsed-area {
transition: height 300ms ease-out;
}<Collapse :when="isExpanded" class="collapsed-area">
<p>{{ 'Collapsed '.repeat(100) }}</p>
</Collapse>To transition other properties, use the data-collapse attribute:
| Transition | From | Enter | Leave |
|---|---|---|---|
| Expand | collapsed |
expanding |
expanded |
| Collapse | expanded |
collapsing |
collapsed |
.collapsed-area {
--transition-base: 300ms cubic-bezier(0.33, 1, 0.68, 1);
transition:
height var(--transition-base),
opacity var(--transition-base);
}
.collapsed-area[data-collapse='expanded'],
.collapsed-area[data-collapse='expanding'] {
opacity: 1;
}
.collapsed-area[data-collapse='collapsed'],
.collapsed-area[data-collapse='collapsing'] {
opacity: 0;
}Alternatively, to use different easings or durations for expanding and collapsing:
.collapsed-area[data-collapse='expanding'] {
transition: height 600ms ease-in-out;
}
.collapsed-area[data-collapse='collapsing'] {
transition: height 300ms ease-out;
}The values of the data-collapse attribute can be accessed using v-slot:
<Collapse :when="isExpanded" v-slot="{ state }">
{{ state === 'collapsing' ? 'Collapsing...' : null }}
</Collapse><script setup>
import { reactive } from 'vue'
import { Collapse } from 'vue-collapsed'
const questions = reactive([
{
title: 'Question one',
answer: 'Answer one',
isExpanded: false // Initial value
},
{
title: 'Question two',
answer: 'Answer two',
isExpanded: false
},
{
title: 'Question three',
answer: 'Answer three',
isExpanded: false
}
])
function onQuestionToggle(toggleIndex) {
questions.forEach((_, i) => {
questions[i].isExpanded = i === toggleIndex ? !questions[i].isExpanded : false
})
}
</script>
<template>
<div v-for="(q, i) in questions" :key="q.title">
<button @click="onQuestionToggle(i)">
{{ q.title }}
</button>
<Collapse :when="q.isExpanded">
<p>
{{ q.answer }}
</p>
</Collapse>
</div>
</template>vue-collapsed automatically detects if users prefer reduced motion and disables transitions accordingly, while maintaining the same API behavior (emitting events and applying post-transition styles).
You should add aria attributes to the Collapse element based on your specific use case.
<script setup>
import { ref, computed, useId } from 'vue'
import { Collapse } from 'vue-collapsed'
const isExpanded = ref(false)
const TOGGLE_ID = useId()
const COLLAPSE_ID = useId()
const toggleAttrs = computed(() => ({
id: TOGGLE_ID,
'aria-controls': COLLAPSE_ID,
'aria-expanded': isExpanded.value
}))
const collapseAttrs = {
role: 'region',
id: COLLAPSE_ID,
'aria-labelledby': TOGGLE_ID
}
function handleCollapse() {
isExpanded.value = !isExpanded.value
}
</script>
<template>
<div>
<button v-bind="toggleAttrs" @click="handleCollapse">Toggle panel</button>
<Collapse v-bind="collapseAttrs" :when="isExpanded">
<p>{{ 'Collapsed '.repeat(100) }}</p>
</Collapse>
</div>
</template><template>
<Collapse :when="isExpanded" class="collapsed-area">
<p>{{ 'Collapsed '.repeat(100) }}</p>
</Collapse>
</template>
<style>
.collapsed-area {
transition: none;
}
</style>MIT