npm bundle size GitHub Workflow Status dependency-count

Vue Collapsed

Dynamic CSS height transition from 0 to auto and vice versa. Accordion ready.

Examples and DemoStackblitz

? Requires Vue v3.0.0 or above.

Get Started

npm i -S vue-collapsed
# yarn add vue-collapsed
# pnpm install vue-collapsed

Register it globally:

import { Collapse } from 'vue-collapsed'

createApp(App).component('Collapse', Collapse).mount('#app')

Or import it locally:

import { Collapse } from 'vue-collapsed'

Props

name description type required
when Boolean value to control collapse boolean
as Element tag to use instead of div keyof HTMLElementTagNameMap
onExpanded Callback on expand transition completed () => void
onCollapsed Callback on collapse transition completed () => void

Usage

All you need is to pass a reactive boolean to when and add a class with an height transition-property:

<template>
  <Collapse :when="isOpen" class="my-class">
    <p>This is a paragraph.</p>
  </Collapse>
</template>

<style>
.my-class {
  transition: height 300ms cubic-bezier(0.3, 0, 0.6, 1);
}
</style>

Auto Duration

Vue Collapsed automatically calculates the optimal duration according to the content height. You can use it by referencing the variable --vc-auto-duration:

.collapse {
  transition: height var(--vc-auto-duration) ease-out;
}

Example – Single Element

<script setup>
import { ref } from 'vue'
import { Collapse } from 'vue-collapsed'

const isOpen = ref(false) // Initial value

function handleCollapse() {
  isOpen.value = !isOpen.value
}
</script>

<template>
  <div>
    <button @click="handleCollapse">This a button.</button>
    <Collapse :when="isOpen" class="collapse">
      <p>This is a paragraph.</p>
    </Collapse>
  </div>
</template>

<style>
.collapse {
  transition: height var(--vc-auto-duration) cubic-bezier(0.3, 0, 0.6, 1);
}
</style>

Example – Accordion

<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 handleAccordion(selectedIndex) {
  questions.forEach((_, index) => {
    questions[index].isExpanded = index === selectedIndex ? !questions[index].isExpanded : false
  })
}

/**
 * For individual control you might use:
 *
 * function handleMultiple(index) {
 *   questions[index].isExpanded = !questions[index].isExpanded
 * }
 */
</script>

<template>
  <div v-for="(question, index) in questions" :key="question.title">
    <button @click="() => handleAccordion(index)">
      {{ question.title }}
    </button>
    <Collapse :when="questions[index].isExpanded" class="collapse">
      <p>
        {{ question.answer }}
      </p>
    </Collapse>
  </div>
</template>

<style>
.collapse {
  transition: height 600ms cubic-bezier(0.3, 0, 0.6, 1);
}
</style>

Example – Callbacks

<script setup>
// ...

const sectionsRef = ref([])

function pushToRef(ref) {
  sectionsRef.value.push(ref)
}

function scrollIntoView(index) {
  sectionsRef.value[index].scrollIntoView({ behavior: 'smooth' })
}
</script>

<template>
  <div v-for="(question, index) in questions" :key="question.title" :ref="pushToRef">
    <button @click="() => handleAccordion(index)">
      {{ question.title }}
    </button>
    <Collapse
      :when="questions[index].isExpanded"
      :onExpanded="() => scrollIntoView(index)"
      class="collapse"
    >
      <p>
        {{ question.answer }}
      </p>
    </Collapse>
  </div>
</template>

<style>
.collapse {
  transition: height 600ms cubic-bezier(0.3, 0, 0.6, 1);
}
</style>

Make it accessible

<script setup>
import { ref, computed } from 'vue'
import { Collapse } from 'vue-collapsed'

const isOpen = ref(false)

const toggleAttrs = computed(() => ({
  'aria-expanded': isOpen.value,
  'aria-controls': 'my-collapse-id'
}))

const collapseAttrs = {
  id: 'my-collapse-id',
  role: 'region'
}

function handleCollapse() {
  isOpen.value = !isOpen.value
}
</script>

<template>
  <div>
    <button v-bind="toggleAttrs" @click="handleCollapse">This a panel.</button>
    <Collapse v-bind="collapseAttrs" :when="isOpen" class="collapse">
      <p>This is a paragraph.</p>
    </Collapse>
  </div>
</template>

<style>
.collapse {
  transition: height 600ms cubic-bezier(0.3, 0, 0.6, 1);
}
</style>

License

MIT Licensed. (c) Simone Mastromattei 2022.

GitHub

View Github