Fold Accordion
A numbered FAQ accordion that animates real content height with CSS grid. No measuring, no jank.
npx shadcn@latest add https://sundaeui.dev/r/fold-accordion.json'use client'
import { useId, useState } from 'react'
export interface FoldItem {
question: string
answer: React.ReactNode
}
export interface FoldAccordionProps {
items: FoldItem[]
/** Allow several items open at once. */
multiple?: boolean
defaultOpen?: number[]
/** Section heading level for the triggers (keeps document outline valid). */
headingLevel?: 2 | 3 | 4
className?: string
}
/**
* A numbered FAQ accordion. Panels animate their real height with
* grid-template-rows (0fr → 1fr) — no measuring, no layout jank — and the
* plus mark folds into a minus.
*/
export function FoldAccordion({ items, multiple, defaultOpen = [], headingLevel = 3, className }: FoldAccordionProps) {
const [open, setOpen] = useState<number[]>(defaultOpen)
const base = useId()
const H = `h${headingLevel}` as 'h3'
const toggle = (i: number) =>
setOpen((o) => (o.includes(i) ? o.filter((x) => x !== i) : multiple ? [...o, i] : [i]))
return (
<div className={['w-full max-w-3xl divide-y divide-neutral-200 border-y border-neutral-200 dark:divide-neutral-800 dark:border-neutral-800', className].filter(Boolean).join(' ')}>
{items.map((item, i) => {
const isOpen = open.includes(i)
return (
<div key={i} className="group">
<H className="m-0">
<button
type="button"
id={`${base}-t${i}`}
aria-expanded={isOpen}
aria-controls={`${base}-p${i}`}
onClick={() => toggle(i)}
className="flex w-full items-baseline gap-5 py-5 text-left outline-none focus-visible:bg-neutral-50 sm:gap-8 sm:py-6 dark:focus-visible:bg-neutral-900"
>
<span className="w-6 shrink-0 font-mono text-xs tabular-nums text-neutral-400 transition-colors group-hover:text-neutral-900 dark:text-neutral-500 dark:group-hover:text-neutral-100">
{String(i + 1).padStart(2, '0')}
</span>
<span className="flex-1 text-[17px] font-medium leading-snug tracking-[-0.015em] text-neutral-900 sm:text-[19px] dark:text-neutral-100">
{item.question}
</span>
<span aria-hidden className="relative mt-1 size-3.5 shrink-0 text-neutral-500 dark:text-neutral-400">
<span className="absolute left-0 top-1/2 h-[1.5px] w-full -translate-y-1/2 rounded bg-current" />
<span
className={[
'absolute left-1/2 top-0 h-full w-[1.5px] -translate-x-1/2 rounded bg-current transition-transform duration-500 ease-[cubic-bezier(.22,1,.36,1)]',
isOpen ? 'rotate-90 scale-y-0' : '',
].join(' ')}
/>
</span>
</button>
</H>
<div
id={`${base}-p${i}`}
role="region"
aria-labelledby={`${base}-t${i}`}
className={[
'grid transition-[grid-template-rows,opacity] duration-500 ease-[cubic-bezier(.22,1,.36,1)] motion-reduce:transition-none',
isOpen ? 'grid-rows-[1fr] opacity-100' : 'grid-rows-[0fr] opacity-0',
].join(' ')}
inert={!isOpen}
>
<div className="overflow-hidden">
<div
className={[
'pb-6 pl-11 pr-8 text-[15px] leading-relaxed text-neutral-600 transition-transform duration-500 ease-[cubic-bezier(.22,1,.36,1)] sm:pl-14 dark:text-neutral-400',
isOpen ? 'translate-y-0' : '-translate-y-2',
].join(' ')}
>
{item.answer}
</div>
</div>
</div>
</div>
)
})}
</div>
)
}Overview
An understated accordion for FAQs and specification lists. Questions carry small tabular numbers in a left gutter, and the plus mark folds its vertical stroke away to become a minus.
Panels animate grid-template-rows from 0fr to 1fr, so the browser interpolates the real content height. Nothing is measured in JavaScript, and it works with dynamic content, images and text reflow. The answer slides up slightly as it appears, which feels like unfolding rather than expanding.
Collapsed panels are inert, so their links can’t be tabbed into while hidden. Open one item at a time, or several with multiple.
When to use
- A single interaction you want to feel finished, dropped into an existing screen.
When not to use
- A whole page layout: look at Sections or Templates.
Installation
pnpm dlx shadcn@latest add https://sundaeui.dev/r/fold-accordion.jsonnpx shadcn@latest add https://sundaeui.dev/r/fold-accordion.jsonyarn dlx shadcn@latest add https://sundaeui.dev/r/fold-accordion.jsonbunx --bun shadcn@latest add https://sundaeui.dev/r/fold-accordion.jsonWorks in any React project. Files are written to components/sundae/. Read the source in the stage’s Code view.
- No dependencies beyond React 18 or later.
- Copy fold-accordion.tsx into components/sundae/.
- Make sure Tailwind scans that folder (v4 detects it automatically; v3 needs it in `content`).
- Import the component and use it as shown below.
Usage
import { FoldAccordion } from '@/components/sundae/fold-accordion'
<FoldAccordion
defaultOpen={[0]}
items={[
{ question: 'Can I use the free components commercially?', answer: 'Yes…' },
{ question: 'Do I need Tailwind?', answer: <>Only for assets marked <strong>Tailwind</strong>.</> },
]}
/>Props
| Prop | Type | Default |
|---|---|---|
items*{ question, answer } pairs. answer accepts any ReactNode. | FoldItem[] | — |
multipleAllow more than one open panel. | boolean | — |
defaultOpenIndexes open on first render. | number[] | [] |
headingLevelHeading element that wraps each trigger, so the page outline stays correct. | 2 | 3 | 4 | 3 |
Accessibility
- Follows the WAI-ARIA accordion pattern: button triggers inside headings, aria-expanded, aria-controls and labelled regions.
- Collapsed panels are inert, so hidden links cannot receive focus.
- motion-reduce disables the height transition.
Customization
- Needs Tailwind v3.4 or later for arbitrary grid-rows values. Tailwind v4 works without configuration.
- For a bordered card style, replace divide-y with gap-2 and add a ring to each item.
FAQ
- Is Fold Accordion free?
- Yes. Fold Accordion is free for personal and commercial projects, with full source on this page.
- How do I install Fold Accordion?
- Run npx shadcn@latest add https://sundaeui.dev/r/fold-accordion.json. The files are written to components/sundae/. It has no dependencies beyond React 18 or later.
- Does Fold Accordion support keyboard and screen readers?
- Follows the WAI-ARIA accordion pattern: button triggers inside headings, aria-expanded, aria-controls and labelled regions. Collapsed panels are inert, so hidden links cannot receive focus.
- Does Fold Accordion respect reduced motion?
- Yes. With prefers-reduced-motion set, decorative motion is removed and state changes are instant.
More like this
All components →Confirm Dialog
Components · Understated- Pro
Sheet Drawer
Components · Refined - Pro
Team Members
Components · Understated - Pro
AI Composer
Components · Understated Toast Stack
Components · RefinedAvatar Stack
Components · Refined