Accordion
Vue disclosureVertically stacked, collapsible panels — one or many open at a time. Use for FAQs, settings groups, and any place where space is tight but content needs to stay browsable. Built on reka-ui with smooth animation and full keyboard support.
Also available for React ->Installation
$ pnpm dlx shadcn-vue@latest add https://uipkge.dev/r/vue/accordion.json$ npx shadcn-vue@latest add https://uipkge.dev/r/vue/accordion.json$ yarn dlx shadcn-vue@latest add https://uipkge.dev/r/vue/accordion.json$ bunx shadcn-vue@latest add https://uipkge.dev/r/vue/accordion.json
Or with the named registry:
npx shadcn-vue@latest add @uipkge/accordion
Examples
Props
| Name | Type / Values | Default | Required |
|---|---|---|---|
class | HTMLAttributes['class'] | — | optional |
variant | 'default''separated''ghost' | 'default' | optional |
type | 'single''multiple' | — | optional |
modelValue | string | string[] | — | optional |
defaultValue | string | string[] | — | optional |
collapsible | boolean | — | optional |
disabled | boolean | — | optional |
dir | 'ltr''rtl' | — | optional |
orientation | 'horizontal''vertical' | — | optional |
asChild | boolean | — | optional |
as | string | object | — | optional |
Dependencies
Used by
Files (7)
-
app/components/ui/accordion/Accordion.vue 1.4 kB
<script setup lang="ts"> import { computed, provide } from 'vue' import type { AccordionRootEmits } from 'reka-ui' import type { HTMLAttributes } from 'vue' import { reactiveOmit } from '@vueuse/core' import { AccordionRoot, useForwardPropsEmits } from 'reka-ui' import { cn } from '@/lib/utils' import { accordionVariants } from './accordion.variants' // Inlined unions: SFC compiler can't extract runtime props from // `NonNullable<AccordionVariants['variant']>` or reka-ui's // `AccordionRootProps`. Inline only the surface we expose. const props = withDefaults( defineProps<{ class?: HTMLAttributes['class'] variant?: 'default' | 'separated' | 'ghost' type?: 'single' | 'multiple' modelValue?: string | string[] defaultValue?: string | string[] collapsible?: boolean disabled?: boolean dir?: 'ltr' | 'rtl' orientation?: 'horizontal' | 'vertical' asChild?: boolean as?: string | object }>(), { variant: 'default', }, ) const emits = defineEmits<AccordionRootEmits>() const delegated = reactiveOmit(props, 'class', 'variant') const forwarded = useForwardPropsEmits(delegated, emits) provide( Symbol.for('accordionVariant'), computed(() => props.variant), ) </script> <template> <AccordionRoot data-uipkge data-slot="accordion" :data-variant="variant" v-bind="forwarded" :class="cn(accordionVariants({ variant }), props.class)" > <slot /> </AccordionRoot> </template> -
app/components/ui/accordion/AccordionContent.vue 0.8 kB
<script setup lang="ts"> import type { AccordionContentProps } from 'reka-ui' import type { HTMLAttributes } from 'vue' import { reactiveOmit } from '@vueuse/core' import { AccordionContent as RkAccordionContent } from 'reka-ui' import { cn } from '@/lib/utils' const props = defineProps<AccordionContentProps & { class?: HTMLAttributes['class'] }>() const delegated = reactiveOmit(props, 'class') </script> <template> <RkAccordionContent data-uipkge data-slot="accordion-content" v-bind="delegated" :class=" cn( 'text-muted-foreground overflow-hidden text-sm', 'data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down', props.class, ) " > <div class="pt-0 pb-4"> <slot /> </div> </RkAccordionContent> </template> -
app/components/ui/accordion/AccordionHeader.vue 0.6 kB
<script setup lang="ts"> import type { AccordionHeaderProps } from 'reka-ui' import type { HTMLAttributes } from 'vue' import { reactiveOmit } from '@vueuse/core' import { AccordionHeader as RkAccordionHeader } from 'reka-ui' import { cn } from '@/lib/utils' const props = defineProps<AccordionHeaderProps & { class?: HTMLAttributes['class'] }>() const delegated = reactiveOmit(props, 'class') </script> <template> <RkAccordionHeader data-uipkge data-slot="accordion-header" v-bind="delegated" :class="cn('flex', props.class)"> <slot /> </RkAccordionHeader> </template> -
app/components/ui/accordion/AccordionItem.vue 0.9 kB
<script setup lang="ts"> import { computed, inject } from 'vue' import type { AccordionItemProps } from 'reka-ui' import type { HTMLAttributes } from 'vue' import { reactiveOmit } from '@vueuse/core' import { AccordionItem as RkAccordionItem } from 'reka-ui' import { cn } from '@/lib/utils' import { accordionItemVariants } from './accordion.variants' const props = defineProps<AccordionItemProps & { class?: HTMLAttributes['class'] }>() const delegated = reactiveOmit(props, 'class') const variantRef = inject<{ value: 'default' | 'separated' | 'ghost' } | undefined>( Symbol.for('accordionVariant'), undefined, ) const variant = computed(() => variantRef?.value ?? 'default') </script> <template> <RkAccordionItem data-uipkge data-slot="accordion-item" v-bind="delegated" :class="cn(accordionItemVariants({ variant }), props.class)" > <slot /> </RkAccordionItem> </template> -
app/components/ui/accordion/AccordionTrigger.vue 1.1 kB
<script setup lang="ts"> import { computed, inject } from 'vue' import type { AccordionTriggerProps } from 'reka-ui' import type { HTMLAttributes } from 'vue' import { reactiveOmit } from '@vueuse/core' import { AccordionTrigger as RkAccordionTrigger } from 'reka-ui' import { ChevronDown } from 'lucide-vue-next' import { cn } from '@/lib/utils' import { accordionTriggerVariants } from './accordion.variants' const props = defineProps<AccordionTriggerProps & { class?: HTMLAttributes['class'] }>() const delegated = reactiveOmit(props, 'class') const variantRef = inject<{ value: 'default' | 'separated' | 'ghost' } | undefined>( Symbol.for('accordionVariant'), undefined, ) const variant = computed(() => variantRef?.value ?? 'default') </script> <template> <RkAccordionTrigger data-uipkge data-slot="accordion-trigger" v-bind="delegated" :class="cn(accordionTriggerVariants({ variant }), props.class)" > <slot /> <ChevronDown class="text-muted-foreground size-4 shrink-0 transition-transform duration-200 group-data-[state=open]/accordion-trigger:rotate-180" aria-hidden="true" /> </RkAccordionTrigger> </template> -
app/components/ui/accordion/accordion.variants.ts 1.8 kB
import type { VariantProps } from 'class-variance-authority' import { cva } from 'class-variance-authority' /** * Variant definitions live in their own file (rather than the package * `index.ts`) so consuming Vue SFCs can import without creating a circular * dependency through the index. See card.variants.ts for the canonical * example + the SSR symptom that motivated the split. */ export const accordionVariants = cva('w-full', { variants: { variant: { // Bottom-border between items (the classic shadcn look). default: '', // Each item is its own bordered card with a small gap between them. separated: 'space-y-2', // Borderless. Use when the parent container already provides framing. ghost: '', }, }, defaultVariants: { variant: 'default', }, }) export const accordionItemVariants = cva('', { variants: { variant: { default: 'border-b border-border last:border-b-0', separated: 'rounded-md border border-border bg-card overflow-hidden', ghost: '', }, }, defaultVariants: { variant: 'default', }, }) export const accordionTriggerVariants = cva( 'group/accordion-trigger flex w-full items-center justify-between gap-3 text-sm font-medium text-foreground outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring/40 hover:text-primary disabled:pointer-events-none disabled:opacity-50', { variants: { variant: { default: 'py-4', separated: 'px-4 py-3 hover:bg-muted/50', ghost: 'py-3', }, }, defaultVariants: { variant: 'default', }, }, ) export type AccordionVariants = VariantProps<typeof accordionVariants> export type AccordionItemVariants = VariantProps<typeof accordionItemVariants> export type AccordionTriggerVariants = VariantProps<typeof accordionTriggerVariants> -
app/components/ui/accordion/index.ts 0.7 kB
export { default as Accordion } from './Accordion.vue' export { default as AccordionContent } from './AccordionContent.vue' export { default as AccordionHeader } from './AccordionHeader.vue' export { default as AccordionItem } from './AccordionItem.vue' export { default as AccordionTrigger } from './AccordionTrigger.vue' // Re-export variant API from the sibling file (kept separate to avoid the // Component.vue <-> index.ts circular import that broke dev SSR for Card). export { accordionVariants, accordionItemVariants, accordionTriggerVariants, type AccordionVariants, type AccordionItemVariants, type AccordionTriggerVariants, } from './accordion.variants'
Raw manifest: https://uipkge.dev/r/vue/accordion.json