rapidUI

Accordion

Vertically stacked, collapsible sections built on Base UI Accordion.

Installation

pnpm dlx shadcn@latest add https://rdf.dev.360online.com/r/accordion.json

Installs to @/components/ui/accordion.

Manual installation

Install the dependencies:

pnpm add @base-ui/react@^1.8.0 lucide-react@^1.48.0

Copy the source into your project:

components/ui/accordion.tsx
'use client'

import { Accordion as AccordionPrimitive } from '@base-ui/react/accordion'
import { ChevronDownIcon, ChevronUpIcon } from 'lucide-react'
import { cn } from '@/lib/utils'

function Accordion({ className, ...props }: AccordionPrimitive.Root.Props) {
	return <AccordionPrimitive.Root data-slot="accordion" className={cn('flex w-full flex-col', className)} {...props} />
}

function AccordionItem({ className, ...props }: AccordionPrimitive.Item.Props) {
	return <AccordionPrimitive.Item data-slot="accordion-item" className={cn('not-last:border-b', className)} {...props} />
}

interface AccordionTriggerProps extends AccordionPrimitive.Trigger.Props {
	/** The heading level wrapping the trigger, to fit the page's outline. Defaults to 3. */
	headingLevel?: 2 | 3 | 4
}

function AccordionTrigger({ className, children, headingLevel = 3, ...props }: Readonly<AccordionTriggerProps>) {
	const Heading = `h${headingLevel}` as const
	return (
		<AccordionPrimitive.Header className="flex" render={(headerProps) => <Heading {...headerProps} />}>
			<AccordionPrimitive.Trigger
				data-slot="accordion-trigger"
				className={cn(
					'group/accordion-trigger relative flex flex-1 items-start justify-between rounded-lg border border-transparent py-2.5 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring focus-visible:after:border-ring aria-disabled:pointer-events-none aria-disabled:opacity-50 **:data-[slot=accordion-trigger-icon]:ml-auto **:data-[slot=accordion-trigger-icon]:size-4 **:data-[slot=accordion-trigger-icon]:text-muted-foreground',
					className,
				)}
				{...props}
			>
				{children}
				<ChevronDownIcon
					aria-hidden="true"
					data-slot="accordion-trigger-icon"
					className="pointer-events-none shrink-0 group-aria-expanded/accordion-trigger:hidden"
				/>
				<ChevronUpIcon
					aria-hidden="true"
					data-slot="accordion-trigger-icon"
					className="pointer-events-none hidden shrink-0 group-aria-expanded/accordion-trigger:inline"
				/>
			</AccordionPrimitive.Trigger>
		</AccordionPrimitive.Header>
	)
}

function AccordionContent({ className, children, ...props }: AccordionPrimitive.Panel.Props) {
	return (
		<AccordionPrimitive.Panel
			data-slot="accordion-content"
			className="overflow-hidden text-sm data-open:animate-accordion-down data-closed:animate-accordion-up"
			{...props}
		>
			{/* No fixed height here: the open/close keyframes on the panel use --accordion-panel-height, and at rest the
			    content keeps its natural height so it can reflow (zoom, text spacing, content that grows) */}
			<div
				className={cn(
					'pt-0 pb-2.5 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4',
					className,
				)}
			>
				{children}
			</div>
		</AccordionPrimitive.Panel>
	)
}

export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }

A vertically stacked set of interactive headings that each reveal a section of content. Built on top of Base UI Accordion, it follows WAI-ARIA design patterns and animates open and closed.

Features

  • One expanded section at a time by default, or several with multiple
  • Every open section can be closed again
  • Height animation on open/close (animate-accordion-down / animate-accordion-up)
  • Chevron indicator that switches from down to up when the item is expanded
  • Items are separated by a bottom border (except the last one)
  • Content styles for rich text: links are underlined and consecutive paragraphs are spaced
  • Disabled items
  • Keyboard navigation and screen reader support
  • data-slot attributes (accordion, accordion-item, accordion-trigger, accordion-content) for styling hooks

Usage

import {
  Accordion,
  AccordionItem,
  AccordionTrigger,
  AccordionContent,
} from '@/components/ui/accordion'

// One section open at a time
export function PermitFaq() {
  return (
    <Accordion>
      <AccordionItem value="need-permit">
        <AccordionTrigger>Do I need a building permit for a garden shed?</AccordionTrigger>
        <AccordionContent>Not if it is smaller than 15 m² and lower than 3 metres.</AccordionContent>
      </AccordionItem>
      <AccordionItem value="processing-time">
        <AccordionTrigger>How long does it take to get a decision?</AccordionTrigger>
        <AccordionContent>Most complete applications are decided within 4 weeks.</AccordionContent>
      </AccordionItem>
    </Accordion>
  )
}

// Several sections open at once
export function CaseDetails() {
  return (
    <Accordion multiple defaultValue={['applicant']}>
      <AccordionItem value="applicant">
        <AccordionTrigger>Applicant</AccordionTrigger>
        <AccordionContent>Anna Lindqvist</AccordionContent>
      </AccordionItem>
      <AccordionItem value="property">
        <AccordionTrigger>Property</AccordionTrigger>
        <AccordionContent>Eken 4:12, Storgatan 14</AccordionContent>
      </AccordionItem>
    </Accordion>
  )
}

Examples

Default

A FAQ for a building permit e-service. By default one answer is open at a time, and defaultValue={['need-permit']} opens the first one. value and defaultValue are always arrays.

Multiple Sections

With multiple several sections can be open at once, which suits a case summary where the handling officer compares applicant and property details side by side.

Bordered

Wrap the accordion in a border and pad the items with className for a card-like list. Triggers can hold an icon next to the label.

Disabled State

Disable an AccordionItem for a step that is not available yet, here the offer in a preschool placement application.

Rich Content

Paragraphs and links inside AccordionContent get spacing and underline styles automatically, and you can add actions such as a button.

Props

Accordion

  • multiple - Whether several items can be open at once (default false)
  • defaultValue - The values of the items to open by default (an array, also when only one item can be open)
  • value - The controlled values of the open items (an array)
  • onValueChange - Event handler for when the opened items change, (value, eventDetails) => void
  • hiddenUntilFound - Keep closed panels findable with the browser's find-in-page, which opens the matching item
  • disabled - Disables every item
  • className - Additional CSS classes (the root is a full-width flex column)

AccordionItem

  • value - Unique identifier for the item
  • disabled - Whether the item is disabled
  • className - Additional CSS classes

AccordionTrigger

  • children - Content to display in the trigger. A chevron icon is appended automatically.
  • headingLevel - The heading level wrapping the trigger: 2, 3 or 4 (default 3). Pick the level that follows the heading above the accordion, so the page outline skips no level, for example 2 when it sits directly under the page's H1.
  • className - Additional CSS classes

AccordionContent

  • children - Content to display when expanded
  • className - Additional CSS classes

Accessibility

The Accordion component follows WAI-ARIA guidelines:

  • Uses proper ARIA roles (region, button)
  • Supports keyboard navigation:
    • Space/Enter: Toggle section
    • Tab: Navigate between sections
    • Home/End: Move to first/last section
  • Announces state changes to screen readers
  • Maintains focus management
  • Provides visual feedback for interaction states

Best Practices

  1. Content Organization:

    • Group related content together
    • Use clear, descriptive headers
    • Keep content concise and focused
  2. Visual Hierarchy:

    • Use consistent spacing
    • Maintain clear section boundaries
    • Provide visual feedback for state changes
  3. Accessibility:

    • Write descriptive trigger text
    • Ensure keyboard navigation
    • Maintain sufficient touch targets
  4. Performance:

    • Lazy load content when possible
    • Use appropriate animations
    • Consider content height changes
  5. User Experience:

    • Allow section collapse when appropriate
    • Provide smooth transitions
    • Consider default open states
  • Tabs
  • Collapsible