Qure UI
Components

Accordion

A vertical stack of collapsible sections, for content that would otherwise swamp a page.

8mm, right upper lobe. Unchanged from the prior study of 14 March.

'use client'import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/registry/qure/ui/accordion'export default function AccordionDemo() {  return (    <Accordion className="w-full max-w-md" defaultValue={['nodule']}>      <AccordionItem value="nodule">        <AccordionTrigger>Pulmonary nodule</AccordionTrigger>        <AccordionContent>          8mm, right upper lobe. Unchanged from the prior study of 14 March.        </AccordionContent>      </AccordionItem>      <AccordionItem value="effusion">        <AccordionTrigger>Pleural effusion</AccordionTrigger>        <AccordionContent>Not detected.</AccordionContent>      </AccordionItem>      <AccordionItem value="cardiomegaly">        <AccordionTrigger>Cardiomegaly</AccordionTrigger>        <AccordionContent>Cardiothoracic ratio within normal limits.</AccordionContent>      </AccordionItem>    </Accordion>  )}

Installation

npx shadcn@latest add https://qure-ui.qure.ai/r/accordion.json

Usage

import {
  Accordion, AccordionContent, AccordionItem, AccordionTrigger,
} from '@/components/ui/accordion'
<Accordion defaultValue={['nodule']}>
  <AccordionItem value="nodule">
    <AccordionTrigger>Pulmonary nodule</AccordionTrigger>
    <AccordionContent>8mm, right upper lobe.</AccordionContent>
  </AccordionItem>
</Accordion>

Composition

<Accordion>          {/* owns the open state */}
  <AccordionItem>    {/* one section, keyed by value */}
    <AccordionTrigger>  {/* the header button, plus the chevron */}
    <AccordionContent>  {/* the panel, height-animated */}
  </AccordionItem>
</Accordion>

AccordionTrigger renders its own AccordionPrimitive.Header, so you do not wrap it in a heading yourself. AccordionContent adds an inner div — the padding lives there rather than on the panel, because a panel with padding cannot animate to zero height.

Basic

8mm, right upper lobe. Unchanged from the prior study of 14 March.

'use client'import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/registry/qure/ui/accordion'export default function AccordionDemo() {  return (    <Accordion className="w-full max-w-md" defaultValue={['nodule']}>      <AccordionItem value="nodule">        <AccordionTrigger>Pulmonary nodule</AccordionTrigger>        <AccordionContent>          8mm, right upper lobe. Unchanged from the prior study of 14 March.        </AccordionContent>      </AccordionItem>      <AccordionItem value="effusion">        <AccordionTrigger>Pleural effusion</AccordionTrigger>        <AccordionContent>Not detected.</AccordionContent>      </AccordionItem>      <AccordionItem value="cardiomegaly">        <AccordionTrigger>Cardiomegaly</AccordionTrigger>        <AccordionContent>Cardiothoracic ratio within normal limits.</AccordionContent>      </AccordionItem>    </Accordion>  )}

One section at a time — the default, and the right one when the sections are alternatives.

Several at once

Both sections stay open — opening one does not close the other.

Non-contrast CT, 1mm slices.

'use client'import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/registry/qure/ui/accordion'export default function AccordionMultiple() {  return (    <Accordion className="w-full max-w-md" multiple defaultValue={['a', 'b']}>      <AccordionItem value="a">        <AccordionTrigger>Findings</AccordionTrigger>        <AccordionContent>Both sections stay open — opening one does not close the other.</AccordionContent>      </AccordionItem>      <AccordionItem value="b">        <AccordionTrigger>Technique</AccordionTrigger>        <AccordionContent>Non-contrast CT, 1mm slices.</AccordionContent>      </AccordionItem>      <AccordionItem value="c">        <AccordionTrigger>Comparison</AccordionTrigger>        <AccordionContent>Prior study of 14 March.</AccordionContent>      </AccordionItem>    </Accordion>  )}

Pass multiple when the sections are a list rather than a choice. A findings panel usually wants this: a radiologist comparing two findings should not have to keep reopening one of them.

Disabled

'use client'import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/registry/qure/ui/accordion'export default function AccordionDisabled() {  return (    <Accordion className="w-full max-w-md">      <AccordionItem value="available">        <AccordionTrigger>Current study</AccordionTrigger>        <AccordionContent>CT Chest, 14 series.</AccordionContent>      </AccordionItem>      <AccordionItem value="locked" disabled>        <AccordionTrigger>Prior study (not yet retrieved)</AccordionTrigger>        <AccordionContent>Unavailable.</AccordionContent>      </AccordionItem>    </Accordion>  )}

A disabled item stays in the tab order and is announced as disabled, rather than disappearing — "not yet retrieved" is information.

As a stack of cards

Solitary 14mm spiculated nodule in the right upper lobe. No cavitation.

'use client'import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/registry/qure/ui/accordion'export default function AccordionCard() {  return (    <Accordion variant="card" defaultValue={['findings']} className="max-w-md">      <AccordionItem value="findings">        <AccordionTrigger>Findings</AccordionTrigger>        <AccordionContent>          Solitary 14mm spiculated nodule in the right upper lobe. No cavitation.        </AccordionContent>      </AccordionItem>      <AccordionItem value="measurements">        <AccordionTrigger>Measurements</AccordionTrigger>        <AccordionContent>Long axis 14mm, short axis 11mm. Solid density throughout.</AccordionContent>      </AccordionItem>      <AccordionItem value="priors">        <AccordionTrigger>Prior comparison</AccordionTrigger>        <AccordionContent>Stable against the November 2025 study. No interval growth.</AccordionContent>      </AccordionItem>    </Accordion>  )}

variant="card" gives every item its own border, radius and 8px of air. Figma ships this as a separate frame — "Accordion list / with padding", next to "full width" — but they are the same accordion stacked two ways, so it is a variant rather than a second component.

Which one to reach for is a question about the content, not the look. Hairlines say one thing, subdivided: sections of a single report, an FAQ. Cards say several independent things: one per study, one per finding, each of which could stand alone on the page.

A closed card sits on base-secondary and an open one lifts to base-default. That surface change is doing real work — it marks the open section without relying on anyone noticing which way a 20px chevron is pointing, and it survives being looked at from across a reading room.

In a card

8mm, right upper lobe. Unchanged since March.

'use client'import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/registry/qure/ui/accordion'import { Card } from '@/registry/qure/ui/card'export default function AccordionInCard() {  return (    <Card className="w-full max-w-md gap-0 py-0">      <Accordion defaultValue={['nodule']}>        <AccordionItem value="nodule" className="px-5">          <AccordionTrigger>Pulmonary nodule</AccordionTrigger>          <AccordionContent>8mm, right upper lobe. Unchanged since March.</AccordionContent>        </AccordionItem>        <AccordionItem value="effusion" className="px-5">          <AccordionTrigger>Pleural effusion</AccordionTrigger>          <AccordionContent>Not detected.</AccordionContent>        </AccordionItem>      </Accordion>    </Card>  )}

Different thing: the whole accordion inside one Card. Give the card gap-0 py-0 and put the padding on each item, so the dividers reach the card's edges instead of stopping short.

How it animates

Base UI measures the panel and publishes its height as --accordion-panel-height, which is what lets content animate from nothing to its natural size with no hardcoded height:

.cn-accordion-content {
  height: var(--accordion-panel-height);
  transition: height 200ms var(--ease-qure);
}
.cn-accordion-content[data-starting-style] { height: 0; }

Disabled under prefers-reduced-motion.

Keyboard

Arrow keys move between triggers, Home and End jump to the ends, Enter and Space toggle. None of that is written here — it arrives with the primitive.

The chevron is one icon rotated rather than two swapped. Rotation is the state change, so it animates for free and there is no frame where both or neither are visible.

API Reference

Everything Base UI's Accordion accepts. The ones you will reach for:

Prop

Type

On this page