UIPackage

Accordion

React disclosure
Edit on GitHub

Vertically 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 Radix UI with smooth animation and full keyboard support.

Also available for Vue ->

Installation

$ npx shadcn@latest add https://react.uipkge.dev/r/react/accordion.json

Or with the named registry: npx shadcn@latest add @uipkge-react/accordion

Examples

Props

Name Type / Values Default Required
variant default optional

Dependencies

Used by

Files (3)

  • components/ui/accordion/accordion.tsx 3.7 kB
    'use client'
    
    import * as React from 'react'
    import * as AccordionPrimitive from '@radix-ui/react-accordion'
    import { ChevronDown } from 'lucide-react'
    import { cn } from '@/lib/utils'
    import {
      accordionVariants,
      accordionItemVariants,
      accordionTriggerVariants,
    } from './accordion.variants'
    
    type AccordionVariant = 'default' | 'separated' | 'ghost'
    
    // The Vue source threads the chosen variant from <Accordion> down to each
    // <AccordionItem>/<AccordionTrigger> via provide/inject. React's equivalent is
    // context — the Root sets it, Item/Trigger read it (default 'default').
    const AccordionVariantContext = React.createContext<AccordionVariant>('default')
    
    const Accordion = React.forwardRef<
      React.ElementRef<typeof AccordionPrimitive.Root>,
      React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Root> & { variant?: AccordionVariant }
    >(({ className, variant = 'default', ...props }, ref) => (
      <AccordionVariantContext.Provider value={variant}>
        <AccordionPrimitive.Root
          ref={ref}
          data-uipkge=""
          data-slot="accordion"
          data-variant={variant}
          className={cn(accordionVariants({ variant }), className)}
          {...props}
        />
      </AccordionVariantContext.Provider>
    ))
    Accordion.displayName = 'Accordion'
    
    const AccordionItem = React.forwardRef<
      React.ElementRef<typeof AccordionPrimitive.Item>,
      React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
    >(({ className, ...props }, ref) => {
      const variant = React.useContext(AccordionVariantContext)
      return (
        <AccordionPrimitive.Item
          ref={ref}
          data-uipkge=""
          data-slot="accordion-item"
          className={cn(accordionItemVariants({ variant }), className)}
          {...props}
        />
      )
    })
    AccordionItem.displayName = 'AccordionItem'
    
    const AccordionHeader = React.forwardRef<
      React.ElementRef<typeof AccordionPrimitive.Header>,
      React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Header>
    >(({ className, ...props }, ref) => (
      <AccordionPrimitive.Header
        ref={ref}
        data-uipkge=""
        data-slot="accordion-header"
        className={cn('flex', className)}
        {...props}
      />
    ))
    AccordionHeader.displayName = 'AccordionHeader'
    
    const AccordionTrigger = React.forwardRef<
      React.ElementRef<typeof AccordionPrimitive.Trigger>,
      React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
    >(({ className, children, ...props }, ref) => {
      const variant = React.useContext(AccordionVariantContext)
      return (
        <AccordionPrimitive.Header className="flex">
          <AccordionPrimitive.Trigger
            ref={ref}
            data-uipkge=""
            data-slot="accordion-trigger"
            className={cn(accordionTriggerVariants({ variant }), className)}
            {...props}
          >
            {children}
            <ChevronDown
              className="text-muted-foreground size-4 shrink-0 transition-transform duration-200 group-data-[state=open]/accordion-trigger:rotate-180"
              aria-hidden="true"
            />
          </AccordionPrimitive.Trigger>
        </AccordionPrimitive.Header>
      )
    })
    AccordionTrigger.displayName = 'AccordionTrigger'
    
    const AccordionContent = React.forwardRef<
      React.ElementRef<typeof AccordionPrimitive.Content>,
      React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
    >(({ className, children, ...props }, ref) => (
      <AccordionPrimitive.Content
        ref={ref}
        data-uipkge=""
        data-slot="accordion-content"
        className={cn(
          'text-muted-foreground overflow-hidden text-sm',
          'data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down',
          className,
        )}
        {...props}
      >
        <div className="pt-0 pb-4">{children}</div>
      </AccordionPrimitive.Content>
    ))
    AccordionContent.displayName = 'AccordionContent'
    
    export { Accordion, AccordionItem, AccordionHeader, AccordionTrigger, AccordionContent }
  • 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>
  • components/ui/accordion/index.ts 0.5 kB
    export {
      Accordion,
      AccordionItem,
      AccordionHeader,
      AccordionTrigger,
      AccordionContent,
    } from './accordion'
    
    // Re-export variant API from the sibling file (kept separate to avoid an
    // import cycle and to give consumers a stable `accordionVariants` path).
    export {
      accordionVariants,
      accordionItemVariants,
      accordionTriggerVariants,
      type AccordionVariants,
      type AccordionItemVariants,
      type AccordionTriggerVariants,
    } from './accordion.variants'

Raw manifest: https://react.uipkge.dev/r/react/accordion.json