Accordion
<div> + Headless UI Disclosure
Expand and collapse content panels. Each item is independently toggled. Built on Headless UI Disclosure.
Overview
Preview
A dark-mode-only Tailwind CSS component library for Nuxt. Copy-paste components with zero runtime dependencies.
Single item (Disclosure)
Preview
A dark-mode-only Tailwind CSS component library for Nuxt. Copy-paste components with zero runtime dependencies.
html
<!-- Single item, open by default -->
<div class="overflow-hidden rounded-lg border border-zinc-800">
<button class="flex w-full items-center justify-between px-5 py-4 text-left hover:bg-zinc-900/40 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[#D40C37]/40">
<span class="text-sm font-medium text-zinc-100">Question title</span>
<svg class="h-4 w-4 text-zinc-500 transition-transform" :class="{ 'rotate-180': open }" ...>▾</svg>
</button>
<div v-if="open" class="border-t border-zinc-800 px-5 py-4 text-sm leading-relaxed text-zinc-400">
Answer content goes here.
</div>
</div>Full accordion (Vue)
vue
<script setup>
import { ref } from 'vue'
const items = [
{ q: 'Is it free?', a: 'Yes, all components are free forever.' },
{ q: 'Does it work with Nuxt?', a: 'Yes — built specifically for Nuxt 3 + Vue 3.' },
{ q: 'Can I use it without Headless UI?', a: 'For static components yes. Interactive ones (Dropdown, Modal etc.) use Headless UI.' },
]
const open = ref(0)
</script>
<template>
<div class="space-y-1.5">
<div
v-for="(item, i) in items"
:key="i"
class="overflow-hidden rounded-lg border transition-colors"
:class="open === i ? 'border-zinc-700' : 'border-zinc-800'"
>
<button
@click="open = open === i ? null : i"
class="flex w-full items-center justify-between px-5 py-4 text-left hover:bg-zinc-900/40 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[#D40C37]/40"
>
<span class="text-sm font-medium text-zinc-100">{{ item.q }}</span>
<svg
class="h-4 w-4 shrink-0 text-zinc-500 transition-transform duration-200"
:class="{ 'rotate-180': open === i }"
...
>▾</svg>
</button>
<Transition
enter-active-class="transition ease-out duration-150"
enter-from-class="opacity-0 -translate-y-1"
enter-to-class="opacity-100 translate-y-0"
>
<div v-if="open === i" class="border-t border-zinc-800 px-5 py-4">
<p class="text-sm leading-relaxed text-zinc-400">{{ item.a }}</p>
</div>
</Transition>
</div>
</div>
</template>With Headless UI Disclosure (recommended)
vue
<script setup>
import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/vue'
</script>
<template>
<div class="space-y-1.5">
<Disclosure v-for="item in items" :key="item.q" v-slot="{ open }" as="div"
class="overflow-hidden rounded-lg border border-zinc-800">
<DisclosureButton class="flex w-full items-center justify-between px-5 py-4 text-left hover:bg-zinc-900/40 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[#D40C37]/40">
<span class="text-sm font-medium text-zinc-100">{{ item.q }}</span>
<svg class="h-4 w-4 shrink-0 text-zinc-500 transition-transform" :class="{ 'rotate-180': open }" ...>▾</svg>
</DisclosureButton>
<DisclosurePanel class="border-t border-zinc-800 px-5 py-4">
<p class="text-sm leading-relaxed text-zinc-400">{{ item.a }}</p>
</DisclosurePanel>
</Disclosure>
</div>
</template>Accessibility notes
DisclosureButtonrenders a<button>witharia-expandedautomatically managedDisclosurePanelhasidlinked to the button'saria-controlsautomatically- Each item is independently expandable — no coordination between panels unless you implement it explicitly