Skip to content
txbrendan/ui

Accordion

beta

A stack of headings that each open and close a section below them, one at a time or several at once.

Preview

Website marketing and FAQs

Two text-led accordions for an example design studio: service details and common questions.

Design & build

Your website, from first sketch to launch.

Start with the pages people need. Shape the message, design the details, then bring it all together.

Decide what visitors need to know and what they should do next. A simple page plan keeps the project focused.

  • A clear purpose for every page
  • A route from the homepage to an enquiry
  • A list of content to keep, update, or write

Before we begin

A few practical questions about working together.

Editable settings

Edit a value, change a role, or toggle a preference. These examples stay in this preview until you reload.

Your profile

Shown beside your comments and shared work.

Name
Email

A digest of new comments and completed work.

Full project workspace

Explore an illustration, files, people, tasks, a budget, and an update form. Edits stay in this preview until you reload.

Keep the brief, reference images, and feedback together so everyone can find the latest version.

  • Name each folder for the work it holds.
  • Pin the current brief at the top.
  • Give reviewers access to that folder.

Sizes

Choose a row size to suit a short list or a longer heading.

Small

Medium

Large

Unavailable section

Explain why a section is disabled before someone tries to open it.

Organization settings are managed by an administrator. Ask them to change your access.

Install

  1. 1. Add the registry to components.json, once per app. Without it, the shadcn CLI looks up @txbrendan in a public list that txbrendan/ui does not control.

    npx shadcn@latest registry add "@txbrendan=https://txbrendan.dev/r/{name}.json"
  2. 2. Add the theme, once per app. It replaces your color tokens with ones checked for contrast.

    npx shadcn@latest add @txbrendan/theme
  3. 3. Add the component.

    npx shadcn@latest add @txbrendan/accordion

Use it for

  • Questions and answers, like an FAQ, where people read only the ones they need.
  • Long settings or details split into sections, so the page starts short.
  • Build each section from AccordionItem, AccordionTrigger, and AccordionPanel. Give each item a value when you control which ones are open.
  • Add multiple when people may want to compare two sections side by side.
  • Add hiddenUntilFound so the browser's find-in-page can search closed sections and open the match.
  • Panels can contain images, links, lists, and controls. Use keepMounted on a panel with local form state so closing it preserves edits.

Avoid it for

  • Content everyone needs to read. Show it on the page.
  • Switching between views of the same thing. Use tabs.
  • A single section that shows more. Use a details element instead.
  • Steps that must happen in order. Use a page per step, or step dots.

Props

  • Accordion

    Root. Holds which sections are open. Takes value, defaultValue, onValueChange, multiple, disabled, and hiddenUntilFound.

    { value?: string[]; defaultValue?: string[]; onValueChange?: (value: string[], details) => void; multiple?: boolean; disabled?: boolean; hiddenUntilFound?: boolean }

  • size

    Accordion. Row height and text size: 36, 44, or 56px rows at the default density.

    "sm" | "md" | "lg" · default "md"

  • multiple

    Accordion. Lets more than one section be open at once.

    boolean · default false

  • value

    Accordion. The open items' values, controlled. Use it with onValueChange.

    string[]

  • defaultValue

    Accordion. The items open at the start when you do not control it.

    string[]

  • onValueChange

    Accordion. Called with the open items' values when one opens or closes.

    (value: string[], details) => void

  • hiddenUntilFound

    Accordion or AccordionPanel. Keeps closed panels in the page with hidden='until-found', so find-in-page can open them.

    boolean · default false

  • disabled

    Accordion or AccordionItem. Blocks opening and closing. It dims.

    boolean · default false

  • AccordionItem

    Part. One section. A line separates it from the next. value names it for value and defaultValue.

    ComponentProps<"div"> & { value?: string }

  • AccordionTrigger

    Part. The heading's button. It renders inside an <h3> and adds a chevron that turns when the section opens.

    ComponentProps<"button">

  • AccordionPanel

    Part. The section's content. It grows open and shrinks closed.

    ComponentProps<"div">

  • keepMounted

    AccordionPanel. Keeps the contents mounted while closed, preserving local field values. Hidden controls stay out of the tab order.

    boolean · default false

  • className

    On any part. Later classes win. On AccordionPanel it goes on the inner box, where the padding is, so className='pb-6' works.

    string

Accessibility

  • Each trigger is a button inside an <h3>, so screen reader users can jump between sections by heading.
  • The button has aria-expanded and aria-controls. An open panel has role="region", named by its trigger.
  • Each trigger is its own tab stop. Enter and Space open and close it. Arrow keys do not move between triggers, as in the current ARIA pattern.
  • The chevron is hidden from screen readers. aria-expanded says whether a section is open.
  • The heading level is fixed at h3. If the page needs another level, pass render to the header in your copy of accordion.tsx.
  • The focus ring uses --ring, which the theme tests hold to 3:1 against the page and cards.
  • Opening grows the panel over --motion-duration-base while its contents slide into place and fade in over 1.5 times --motion-duration-slow. Closing fades the contents over --motion-duration-close. Reduced motion removes height, content, and chevron movement; Motion Off removes all transitions.

Examples

FAQ

<Accordion hiddenUntilFound>
  <AccordionItem value="refunds">
    <AccordionTrigger>Can I get a refund?</AccordionTrigger>
    <AccordionPanel>Yes, within 30 days of your order. We send the money back to the card you paid with.</AccordionPanel>
  </AccordionItem>
  <AccordionItem value="shipping">
    <AccordionTrigger>How long does shipping take?</AccordionTrigger>
    <AccordionPanel>Three to five working days in the US, and up to ten days anywhere else.</AccordionPanel>
  </AccordionItem>
</Accordion>

Editable values that survive closing a section

<Accordion multiple defaultValue={["account"]} size="lg">
  <AccordionItem value="account">
    <AccordionTrigger>Account</AccordionTrigger>
    <AccordionPanel keepMounted>
      <label>Display name <input defaultValue="Workspace owner" /></label>
    </AccordionPanel>
  </AccordionItem>
  <AccordionItem value="billing">
    <AccordionTrigger>Billing</AccordionTrigger>
    <AccordionPanel>Your plan, card, and past invoices.</AccordionPanel>
  </AccordionItem>
</Accordion>

Controlled

"use client";
import { useState } from "react";

function Sections() {
  const [open, setOpen] = useState<string[]>([]);
  return (
    <Accordion value={open} onValueChange={(value) => setOpen(value as string[])}>
      <AccordionItem value="one">
        <AccordionTrigger>First section</AccordionTrigger>
        <AccordionPanel>Shown when open includes "one".</AccordionPanel>
      </AccordionItem>
    </Accordion>
  );
}