Docs
Accordion

Accordion

A vertically stacked set of interactive headings that each reveal a section of content.

Installation

Copy and paste the following code into your project.

"use client"

import * as React from "react"

import { Accordion as AccordionPrimitive } from "@ark-ui/react/accordion"
import { ChevronDown } from "lucide-react"

import { cn } from "@/lib/utils"

const Accordion = AccordionPrimitive.Root

const AccordionRootProvider = AccordionPrimitive.RootProvider

const AccordionContext = AccordionPrimitive.Context

const AccordionItemContext = AccordionPrimitive.ItemContext

const AccordionItem = React.forwardRef<
  React.ElementRef<typeof AccordionPrimitive.Item>,
  React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
>(({ className, ...props }, ref) => (
  <AccordionPrimitive.Item
    ref={ref}
    className={cn("border-b", className)}
    {...props}
  />
))
AccordionItem.displayName = "AccordionItem"

const AccordionItemTrigger = React.forwardRef<
  React.ElementRef<typeof AccordionPrimitive.ItemTrigger>,
  React.ComponentPropsWithoutRef<typeof AccordionPrimitive.ItemTrigger>
>(({ className, children, ...props }, ref) => (
  <AccordionPrimitive.ItemTrigger
    ref={ref}
    className={cn(
      "flex w-full flex-1 items-center justify-between py-4 font-medium transition-all hover:underline data[-state=open]:rotate-180",
      className
    )}
    {...props}
  >
    {children}
    <AccordionPrimitive.ItemIndicator>
      <ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
    </AccordionPrimitive.ItemIndicator>
  </AccordionPrimitive.ItemTrigger>
))

const AccordionItemContent = React.forwardRef<
  React.ElementRef<typeof AccordionPrimitive.ItemContent>,
  React.ComponentPropsWithoutRef<typeof AccordionPrimitive.ItemContent>
>(({ className, children, ...props }, ref) => (
  <AccordionPrimitive.ItemContent
    ref={ref}
    className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
    {...props}
  >
    <div className={cn("pt-0 pb-4", className)}>{children}</div>
  </AccordionPrimitive.ItemContent>
))

export {
  Accordion,
  AccordionContext,
  AccordionItem,
  AccordionItemContent,
  AccordionItemContext,
  AccordionItemTrigger,
  AccordionRootProvider,
}

AccordionItem.vue

<script setup lang="ts">
import { cn } from "@/lib/utils"
import { type HTMLAttributes, computed } from "vue"

import { AccordionItem, type AccordionItemProps } from "@ark-ui/vue/accordion"

const props = defineProps<
  AccordionItemProps & { class?: HTMLAttributes["class"] }
>()

const delegatedProps = computed(() => {
  const { class: _, ...delegated } = props

  return delegated
})
</script>

<template>
  <AccordionItem
    v-bind="delegatedProps"
    :class="cn('border-b', props.class)"
  >
    <slot />
  </AccordionItem>
</template>

AccordionItemContent.vue

<script setup lang="ts">
import { cn } from "@/lib/utils"
import type { HTMLAttributes } from "vue"

import {
  AccordionItemContent,
  type AccordionItemContentProps,
} from "@ark-ui/vue/accordion"

const props = defineProps<
  AccordionItemContentProps & { class?: HTMLAttributes["class"] }
>()
</script>

<template>
  <AccordionItemContent
    v-bind="props"
    :class="cn('overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down', props.class)"
  >
    <div :class="cn('pt-0 pb-4', props.class)">
      <slot />
    </div>
  </AccordionItemContent>
</template>

AccordionItemTrigger.vue

<script setup lang="ts">
import { cn } from "@/lib/utils"
import { ChevronDown } from "lucide-vue-next"
import type { HTMLAttributes } from "vue"

import {
  AccordionItemIndicator,
  AccordionItemTrigger,
  type AccordionItemTriggerProps,
} from "@ark-ui/vue/accordion"

const props = defineProps<
  AccordionItemTriggerProps & { class?: HTMLAttributes["class"] }
>()
</script>

<template>
  <AccordionItemTrigger
    v-bind="props"
    :class="cn('flex w-full flex-1 items-center justify-between py-4 font-medium transition-all hover:underline data[-state=open]:rotate-180', props.class)"
  >
    <slot />
    <AccordionItemIndicator>
      <ChevronDown class="h-4 w-4 shrink-0 transition-transform duration-200" />
    </AccordionItemIndicator>
  </AccordionItemTrigger>
</template>

index.ts

export {
  AccordionRoot as Accordion,
  AccordionContext,
  AccordionItemContext,
} from "@ark-ui/vue/accordion"
export { default as AccordionItem } from "./AccordionItem.vue"
export { default as AccordionItemContent } from "./AccordionItemContent.vue"
export { default as AccordionItemTrigger } from "./AccordionItemTrigger.vue"

Update the import paths to match your project setup.

Update tailwind.config.js

Add the following animations to your tailwind.config.js file:

/** @type {import('tailwindcss').Config} */
module.exports = {
  theme: {
    extend: {
      keyframes: {
        "collapse-down": {
          from: { height: "0" },
          to: { height: "var(--height)" },
        },
        "collapse-up": {
          from: { height: "var(--height)" },
          to: { height: "0" },
        },
      },
      animation: {
        "accordion-down": "collapse-down 0.2s ease-out",
        "accordion-up": "collapse-up 0.2s ease-out",
      },
    },
  },
}

Usage

import {
  Accordion,
  AccordionContent,
  AccordionItem,
  AccordionTrigger,
} from "@/components/ui/accordion"
<Accordion type="single" collapsible>
  <AccordionItem value="item-1">
    <AccordionTrigger>Is it accessible?</AccordionTrigger>
    <AccordionContent>
      Yes. It adheres to the WAI-ARIA design pattern.
    </AccordionContent>
  </AccordionItem>
</Accordion>