Skip to content
Caseroom

Components · Layout

Accordion

Sections on native details, one open at a time if you like. Keyboard and find-in-page, no JavaScript.

29% of the 2,062 sites in The UI Index have a accordion (590 sites).

Connect a repository

Pick a GitHub repository; every push to main deploys.

Add a domain

Point a CNAME at your project and the certificate follows within minutes.

Invite your team

Teammates get edit rights on every project in the workspace.

Both previews render the same markup; the HTML tab below is that markup without a framework.

Install

React
npx shadcn@latest add https://www.themes.dev/caseroom/r/accordion.json
Astro
npx shadcn@latest add https://www.themes.dev/caseroom/r/astro/accordion.json

Or copy the files below into your project. They need clsx, tailwind-merge, and the Caseroom theme. The HTML needs only the compiled CSS.

accordion.tsx
import { cn } from '@/lib/utils';
import { accordion, CHEVRON_PATH, type AccordionItem } from './accordion.variants';

export type { AccordionItem };

export interface AccordionProps {
  items: AccordionItem[];
  /** One open at a time: the items share this name (native exclusive details). */
  single?: string;
  className?: string;
}

/** Sections on native <details>; the browser handles keyboard, find-in-page and the exclusive group. */
export function Accordion({ items, single, className }: AccordionProps) {
  return (
    <div className={cn(accordion.root, className)}>
      {items.map((it, k) => (
        <details key={k} name={single} open={it.open} className={accordion.item}>
          <summary className={accordion.summary}>
            {it.title}
            <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" className={accordion.chevron}><path d={CHEVRON_PATH} /></svg>
          </summary>
          <p className={accordion.content}>{it.content}</p>
        </details>
      ))}
    </div>
  );
}
accordion.variants.ts
/** Sections that open and close, on native <details>: keyboard, find-in-page and no JavaScript. With `single`, the
 *  items share a name, so opening one closes the others (the browser does it). Items sit on their own grounds,
 *  separated by space, not by rules; the chevron turns, and stands still under reduced motion. */
export const accordion = {
  root: 'grid w-full gap-2',
  item: 'group rounded-lg bg-muted/60 transition-colors open:bg-muted',
  summary:
    'flex min-h-12 cursor-pointer list-none items-center justify-between gap-4 rounded-lg px-4 py-3 text-sm font-medium text-pretty transition-colors hover:bg-accent active:bg-pressed group-open:rounded-b-none [&::-webkit-details-marker]:hidden',
  chevron: 'size-4 shrink-0 text-muted-foreground transition-transform duration-200 group-open:rotate-180',
  content: 'px-4 pt-1 pb-4 text-sm text-pretty text-muted-foreground',
};

export type AccordionItem = { title: string; content: string; open?: boolean };
export const CHEVRON_PATH = 'M4 6l4 4 4-4';