# FAQ (Caseroom block)

Questions as native details: keyboard, find-in-page and no JavaScript. Black and white, dark mode, shadcn-compatible; React, Astro and plain HTML.

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

## Install

- React: `npx shadcn@latest add https://www.themes.dev/caseroom/r/faq.json`
- Astro: `npx shadcn@latest add https://www.themes.dev/caseroom/r/astro/faq.json`
- HTML: https://www.themes.dev/caseroom/r/html/faq.html (with https://www.themes.dev/caseroom/r/html/caseroom.css)

Page: https://www.themes.dev/caseroom/blocks/faq/ · Rules for agents: https://www.themes.dev/caseroom/AGENT.md

## Code

### faq.astro

```astro
---
import { cn } from '@/lib/utils';
import { faq, type FaqContent } from './faq.classes';

type Props = FaqContent & { class?: string };
const { title, description, items, class: className } = Astro.props;
---

<section class={cn(faq.root, className)}>
  <div class={faq.inner}>
    <div>
      <h2 class={faq.title}>{title}</h2>
      {description && <p class={faq.description}>{description}</p>}
    </div>
    <div class={faq.list}>
      {
        items.map((item) => (
          <details class={faq.item}>
            <summary class={faq.summary}>
              {item.question}
              <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" aria-hidden="true" class={faq.icon}>
                <path d="M8 3v10M3 8h10" />
              </svg>
            </summary>
            <p class={faq.answer}>{item.answer}</p>
          </details>
        ))
      }
    </div>
  </div>
</section>
```

### faq.classes.ts

```tsx
export const faq = {
  root: '@container bg-background px-4 py-20 sm:px-6 sm:py-24',
  inner: 'mx-auto grid max-w-5xl gap-10 @3xl:grid-cols-[1fr_1.6fr]',
  title: 'text-3xl font-semibold tracking-[-0.03em] text-balance',
  description: 'mt-4 text-base text-pretty text-muted-foreground',
  // Questions sit on their own grounds, separated by space, not by rules; an open one deepens.
  list: 'grid gap-2',
  item: 'group rounded-lg bg-muted/60 px-4 @md:px-5 transition-colors open:bg-muted has-[summary:active]:bg-pressed',
  summary:
    '-mx-4 flex cursor-pointer list-none items-center justify-between gap-4 rounded-lg px-4 py-4 text-[15px] font-medium text-pretty @md:-mx-5 @md:px-5 transition-colors hover:bg-accent active:bg-pressed [&::-webkit-details-marker]:hidden',
  icon: 'size-4 shrink-0 text-muted-foreground transition-transform group-open:rotate-45',
  answer: 'pb-5 pr-8 text-sm text-pretty text-muted-foreground',
};

export type FaqContent = { title: string; description?: string; items: { question: string; answer: string }[] };
```

### faq.tsx

```tsx
import { cn } from '@/lib/utils';
import { faq, type FaqContent } from './faq.classes';

export type FaqProps = FaqContent & { className?: string };

/** Questions as native <details>: they open with the keyboard, in find-in-page and without JavaScript. */
export function Faq({ title, description, items, className }: FaqProps) {
  return (
    <section className={cn(faq.root, className)}>
      <div className={faq.inner}>
        <div>
          <h2 className={faq.title}>{title}</h2>
          {description && <p className={faq.description}>{description}</p>}
        </div>
        <div className={faq.list}>
          {items.map((item) => (
            <details key={item.question} className={faq.item}>
              <summary className={faq.summary}>
                {item.question}
                <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" aria-hidden="true" className={faq.icon}>
                  <path d="M8 3v10M3 8h10" />
                </svg>
              </summary>
              <p className={faq.answer}>{item.answer}</p>
            </details>
          ))}
        </div>
      </div>
    </section>
  );
}
```
