Skip to content
goodtabs UI
BrowseBrowse components

FAQ accordion

A single list of questions with native details and summary disclosure.

npx shadcn@latest add https://ui.goodtabs.co/r/faq-accordion.json

With the @goodtabs namespace in components.json: npx shadcn@latest add @goodtabs/faq-accordion

Is it free to use?
Yes. Every component is MIT licensed and free to copy into a commercial product.
How do I install one?
Run the shadcn add command shown on the component page. The source lands in your repo.
Does it work with my agent?
Each page has a prompt your agent can read, with props, usage and gotchas.

Three questions, the first open.

One at a time

Can I get a refund?
Within 14 days of purchase, no questions asked.
What happens when I cancel?
Your plan runs to the end of the period, then drops to Free.
Can I move seats between people?
Yes. Reassign a seat from settings at any time.

A shared name closes the others when one opens.

Usage

import { FaqAccordion } from "@/components/faq-accordion"

<FaqAccordion name="faq" items={[{ question: "Is it free?", answer: "Yes, MIT licensed." }]} />

Props

PropTypeDefaultDescription
itemsFaqItem[]question and answer, in order.
namestringShared name so opening one closes the rest.
defaultOpennumberIndex of an item that starts open.

Source

faq-accordion/faq-accordion.tsx
import * as React from "react";
import { ChevronDownIcon } from "lucide-react";
import { cn } from "@/lib/utils";

type FaqItem = {
  /** The question. Plain text, it becomes the summary. */
  question: string;
  /** The answer. A string or your own element. */
  answer: React.ReactNode;
};

type FaqAccordionProps = React.ComponentProps<"div"> & {
  /** Questions in order. */
  items: FaqItem[];
  /** Give every item the same name so opening one closes the others. */
  name?: string;
  /** Index of an item that starts open. */
  defaultOpen?: number;
};

/**
 * A single list of questions. Each item is a native details and summary pair, so the disclosure
 * semantics, keyboard and find in page come from the browser. No JavaScript, server component.
 */
export function FaqAccordion({ items, name, defaultOpen, className, ...props }: FaqAccordionProps) {
  return (
    <div data-slot="faq-accordion" className={cn("w-full divide-y rounded-lg border bg-card", className)} {...props}>
      {items.map((item, i) => (
        <details key={item.question} name={name} open={i === defaultOpen} className="group/faq">
          <summary className="flex min-h-11 cursor-pointer list-none items-center justify-between gap-4 px-4 py-3 text-sm font-medium text-foreground outline-none select-none hover:bg-accent/40 focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:ring-inset [&::-webkit-details-marker]:hidden">
            {item.question}
            <ChevronDownIcon
              className="size-4 shrink-0 text-muted-foreground transition-transform duration-150 group-open/faq:rotate-180 motion-reduce:transition-none"
              aria-hidden="true"
            />
          </summary>
          <div className="px-4 pb-4 text-sm text-muted-foreground">{item.answer}</div>
        </details>
      ))}
    </div>
  );
}

export type { FaqItem };

For AI agents

Hand your agent this page, the markdown at /components/faq-accordion/prompt.md, or the Copy AI prompt button above.

When to use

  • A short FAQ of three to eight questions
  • When find in page and no JavaScript matter
  • Pricing, signup and support pages

When not to use

  • For grouped questions, use faq-tabs
  • For app navigation with nested items
  • When panels need animated height

Agent notes

  • Server component built on details and summary, so semantics and keyboard are native
  • The name attribute gives exclusive opening in current browsers
  • Chevron rotates 150ms and is removed under reduced motion
  • Items are keyed by question text, keep questions unique