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.
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.
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>
);
}/** 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';---
import { cn } from '@/lib/utils';
import { accordion, CHEVRON_PATH, type AccordionItem } from './accordion.variants';
interface Props {
items: AccordionItem[];
/** One open at a time: the items share this name (native exclusive details). */
single?: string;
class?: string;
}
const { items, single, class: className } = Astro.props;
---
<div class={cn(accordion.root, className)}>
{
items.map((it) => (
<details name={single} open={it.open} class={accordion.item}>
<summary class={accordion.summary}>
{it.title}
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class={accordion.chevron}><path d={CHEVRON_PATH} /></svg>
</summary>
<p class={accordion.content}>{it.content}</p>
</details>
))
}
</div>/** 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';<div class="grid w-full gap-2 max-w-lg">
<details name="ex-setup" open class="group rounded-lg bg-muted/60 transition-colors open:bg-muted">
<summary class="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">
Connect a repository
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="size-4 shrink-0 text-muted-foreground transition-transform duration-200 group-open:rotate-180"><path d="M4 6l4 4 4-4"></path></svg>
</summary>
<p class="px-4 pt-1 pb-4 text-sm text-pretty text-muted-foreground">Pick a GitHub repository; every push to main deploys.</p>
</details>
<details name="ex-setup" class="group rounded-lg bg-muted/60 transition-colors open:bg-muted">
<summary class="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">
Add a domain
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="size-4 shrink-0 text-muted-foreground transition-transform duration-200 group-open:rotate-180"><path d="M4 6l4 4 4-4"></path></svg>
</summary>
<p class="px-4 pt-1 pb-4 text-sm text-pretty text-muted-foreground">Point a CNAME at your project and the certificate follows within minutes.</p>
</details>
<details name="ex-setup" class="group rounded-lg bg-muted/60 transition-colors open:bg-muted">
<summary class="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">
Invite your team
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="size-4 shrink-0 text-muted-foreground transition-transform duration-200 group-open:rotate-180"><path d="M4 6l4 4 4-4"></path></svg>
</summary>
<p class="px-4 pt-1 pb-4 text-sm text-pretty text-muted-foreground">Teammates get edit rights on every project in the workspace.</p>
</details>
</div>