Skip to content

Collapsible

Shows and hides a block of content from a button above it.

View source View as Markdown

Collapsible is a button and the content it shows and hides. Useful for detail a reader can ask for rather than has to scroll past, like an advanced settings block or the long half of a summary.

App.vue
<template>
  <Collapsible.Root v-model:open="open" class="w-80">
    <Collapsible.Trigger :class="trigger">
      <span>Show details</span>
      <svg :class="chevron" viewBox="0 0 16 16" aria-hidden="true">
        <path
          d="m4 6.5 4 4 4-4"
          fill="none"
          stroke="currentColor"
          stroke-width="1.5"
          stroke-linecap="round"
          stroke-linejoin="round"
        />
      </svg>
    </Collapsible.Trigger>

    <Collapsible.Content>
      <p :class="body">
        The content animates to the height it needs, so nothing here sets a
        height, and the text can grow or shrink without anyone measuring it.
      </p>
    </Collapsible.Content>
  </Collapsible.Root>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { Collapsible } from '@maas/mirror/vue/Collapsible'

const open = ref(false)

const trigger = [
  'group flex h-10 w-full items-center justify-between gap-2 px-3',
  'rounded-component-md border-2 border-[transparent] type-component-md',
  'text-surface transition-all duration-100 ease-linear',
  'outline-4 outline-transparent focus-visible:focus-ring',
  'hover:bg-primary-subtle',
  'active:bg-primary-subtle-active',
].join(' ')

const chevron = [
  'size-4.5 shrink-0 text-surface-muted',
  'transition-transform duration-200 ease-out',
  'group-data-[content-open=true]:rotate-180',
].join(' ')

const body = 'type-surface-body-md text-surface-muted px-3 pt-2 pb-3'
</script>

Usage guidelines

  • Put the padding on something inside the content rather than on the content itself. Content behind hidden="until-found" keeps a box of its own, so its padding is height the collapsed box never loses.should
  • The height animates whether the closed content is unmounted or kept, so reach for forceMount for what is inside rather than for the motion.should
  • For several of these that share a heading structure, or where only one should be open at a time, an accordion is the pattern you want. Mirror does not ship one; MagicAccordion in vue-equipment does.should