Skip to content

πŸ‹οΈβ€β™‚οΈ Dynamic CSS height transition from any value to auto and vice versa for Vue. Accordion ready.

License

Notifications You must be signed in to change notification settings

smastrom/vue-collapsed

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

67 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

npm dependencies npm bundle size downloads GitHub Workflow Status

Vue Collapsed

Dynamic CSS height transition from any value to auto and vice versa. Accordion-ready.

Examples and Demo - Stackblitz

Installation

npm i vue-collapsed

Usage

<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>

Props

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 ❌

Emits

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

Automatic transition (default)

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.

Custom transition

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>

Multiple transitions

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>

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 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>

Accessibility

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>

Manually disabling transitions

<template>
  <Collapse :when="isExpanded" class="collapsed-area">
    <p>{{ 'Collapsed '.repeat(100) }}</p>
  </Collapse>
</template>

<style>
.collapsed-area {
  transition: none;
}
</style>

License

MIT

About

πŸ‹οΈβ€β™‚οΈ Dynamic CSS height transition from any value to auto and vice versa for Vue. Accordion ready.

Topics

Resources

License

Stars

Watchers

Forks

Sponsor this project

Contributors 3

  •  
  •  
  •