Skip to content

Early previewhamur is not on npm yet. The components are being built in the open, follow along on GitHub.

DisclosureReact

Accordion

A set of collapsible panels with headings.

Rendered by React 19.3.1

Loading React…
import { Accordion } from '@hamur/react/accordion'

const questions = [
  {
    value: 'what',
    question: 'What is Base UI?',
    answer: 'A library of unstyled components for design systems and web apps.',
  },
  {
    value: 'start',
    question: 'How do I get started?',
    answer: 'Install the package for your framework and import the parts you need.',
  },
  {
    value: 'use',
    question: 'Can I use it for my project?',
    answer: 'Of course. It is free and open source.',
  },
]

export default function AccordionExample() {
  return (
    <Accordion.Root className="accordion" defaultValue={['what']}>
      {questions.map((item) => (
        <Accordion.Item
          className="accordion-item"
          value={item.value}
          key={item.value}
        >
          <Accordion.Header className="accordion-header">
            <Accordion.Trigger className="accordion-trigger">
              {item.question}
            </Accordion.Trigger>
          </Accordion.Header>
          <Accordion.Panel className="accordion-panel">
            <p className="accordion-content">{item.answer}</p>
          </Accordion.Panel>
        </Accordion.Item>
      ))}
    </Accordion.Root>
  )
}
241Tests passing in React · 0 failing
11.7 KiBSize
1.40 msOpen
0.70 msClose
16.0 msOpenMutations
13.0 msCloseMutations
0.27 MiBMemory
productdevbook and 18 upstream authors7 commits · last

Installation

npm i @hamur/react

Not on npm yet. The package goes out under the chosen name.

Anatomy

Import the component and assemble its parts:

<Accordion.Root>
  <Accordion.Item>
    <Accordion.Header>
      <Accordion.Trigger />
    </Accordion.Header>
    <Accordion.Panel />
  </Accordion.Item>
</Accordion.Root>

Examples

Multiple

multiple lets several panels stay open at once.

AccordionMultiple.tsx
import { Accordion } from '@hamur/react/accordion'

const items = [
  {
    question: 'What is Base UI?',
    answer:
      'Base UI is a library of high-quality unstyled React components for design systems and web apps.',
  },
  {
    question: 'How do I get started?',
    answer:
      'Head to the “Quick start” guide in the docs. If you’ve used unstyled libraries before, you’ll feel at home.',
  },
  {
    question: 'Can I use it for my project?',
    answer: 'Of course! Base UI is free and open source.',
  },
]

export default function AccordionMultipleExample() {
  return (
    <Accordion.Root className="accordion" multiple>
      {items.map((item) => (
        <Accordion.Item key={item.question} className="accordion-item">
          <Accordion.Header className="accordion-header">
            <Accordion.Trigger className="accordion-trigger">{item.question}</Accordion.Trigger>
          </Accordion.Header>
          <Accordion.Panel className="accordion-panel">
            <div className="accordion-content">{item.answer}</div>
          </Accordion.Panel>
        </Accordion.Item>
      ))}
    </Accordion.Root>
  )
}

Hidden Until Found

hiddenUntilFound keeps closed panels searchable; find in page opens the match.

AccordionHiddenUntilFound.tsx
import { Accordion } from '@hamur/react/accordion'

const items = [
  {
    question: 'How long does shipping take?',
    answer:
      'Standard shipping takes 3–5 business days. Express delivery arrives in 1–2 business days.',
  },
  {
    question: 'What is your return policy?',
    answer:
      'You can return any item within 30 days of delivery. Opened items may be subject to a 10% restocking fee.',
  },
  {
    question: 'Do you ship internationally?',
    answer:
      'Yes, we ship to over 40 countries. International orders typically arrive within 7–14 business days.',
  },
  {
    question: 'How can I track my order?',
    answer:
      'Once your order ships, you’ll receive a tracking link by email. Tracking updates can take up to 24 hours to appear.',
  },
]

export default function AccordionHiddenUntilFoundExample() {
  return (
    <Accordion.Root className="accordion" hiddenUntilFound>
      {items.map((item) => (
        <Accordion.Item key={item.question} className="accordion-item">
          <Accordion.Header className="accordion-header">
            <Accordion.Trigger className="accordion-trigger">{item.question}</Accordion.Trigger>
          </Accordion.Header>
          <Accordion.Panel className="accordion-panel">
            <div className="accordion-content">{item.answer}</div>
          </Accordion.Panel>
        </Accordion.Item>
      ))}
    </Accordion.Root>
  )
}

API reference

15 props across 5 parts, in React syntax. className, style, render are listed once, under Shared props.

HeaderAccordion.Header

A heading that labels the corresponding panel. Renders a <h3>.

Header data attributes
Data attributeDescription
data-indexIndicates the index of the accordion item.
data-disabledPresent when the accordion item is disabled.
data-openPresent when the accordion item is open.

ItemAccordion.Item

Groups an accordion header with the corresponding panel. Renders a <div>.

Item props
PropTypeDefault
valueA unique value that identifies this accordion item.any—
onOpenChangeEvent handler called when the panel is opened or closed.((open: boolean, eventDetails: AccordionItemChangeEventDetails) => void)—
disabledWhether the component should ignore user interaction.booleanfalse
Item data attributes
Data attributeDescription
data-indexIndicates the index of the accordion item.
data-disabledPresent when the accordion item is disabled.
data-openPresent when the accordion item is open.

PanelAccordion.Panel

A collapsible panel with the accordion item contents. Renders a <div>.

Panel props
PropTypeDefault
hiddenUntilFoundAllows the browser's built-in page search to find and expand the panel contents.booleanfalse
keepMountedWhether to keep the element in the DOM while the panel is closed.booleanfalse
Panel data attributes
Data attributeDescription
data-indexIndicates the index of the accordion item.
data-openPresent when the accordion panel is open.
data-orientationIndicates the orientation of the accordion.
data-disabledPresent when the accordion item is disabled.
data-starting-stylePresent when the panel begins animating in.
data-ending-stylePresent when the panel is animating out.
Panel CSS variables
CSS variableDescription
--accordion-panel-heightThe accordion panel's height.
--accordion-panel-widthThe accordion panel's width.

RootAccordion.Root

Groups all parts of the accordion. Renders a <div>.

Root props
PropTypeDefault
defaultValueThe uncontrolled value of the item(s) that should be initially expanded.AccordionValue<TValue>—
valueThe controlled value of the item(s) that should be expanded.AccordionValue<TValue>—
onValueChangeEvent handler called when an accordion item is expanded or collapsed.((value: AccordionValue<TValue>, eventDetails: AccordionRootChangeEventDetails) => void)—
hiddenUntilFoundAllows the browser's built-in page search to find and expand the panel contents.booleanfalse
loopFocusDeprecated following the APG guidance updateboolean—
multipleWhether multiple items can be open at the same time.booleanfalse
disabledWhether the component should ignore user interaction.booleanfalse
orientationDeprecated following the APG guidance updateOrientation'vertical'
keepMountedWhether to keep the element in the DOM while the panel is closed.booleanfalse
Root data attributes
Data attributeDescription
data-disabledPresent when the accordion is disabled.
data-orientationIndicates the orientation of the accordion.

TriggerAccordion.Trigger

A button that opens and closes the corresponding panel. Renders a <button>.

Trigger props
PropTypeDefault
nativeButtonWhether the component renders a native <button> element when replacing itbooleantrue
Trigger data attributes
Data attributeDescription
data-indexIndicates the index of the accordion item.
data-panel-openPresent when the accordion panel is open.
data-disabledPresent when the trigger or its accordion item is disabled.

Shared props

Every part that renders an element takes these.

Shared props
PropType
classNameCSS class applied to the element, or a function that returns a class based on the component's state.string | ((state: State) => string | undefined)
styleStyle applied to the element, or a function that returns a style object based on the component's state.React.CSSProperties | ((state: State) => React.CSSProperties | undefined)
renderAllows you to replace the component's HTML element with a different tag, or compose it with another component.React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, State>

Keyboard and accessibility

Keyboard

Keyboard
KeysDoesPart
EnterSpaceOpens or closes the item's panelTrigger
SpaceToggles on key up, as a native button does, also when Trigger renders another elementTrigger

Accessibility

What the parts set
PartSetsSo that
Headerrenders an <h3>each trigger sits in the page's heading outline
Triggeraria-expanded, aria-controls to its panel while opena screen reader says whether the section is open and where it is
Panelrole="region", aria-labelledby to its triggerthe open panel is a landmark named by its heading
Panelhidden="until-found" with hiddenUntilFoundfind in page reaches text in a closed panel and opens it

Focus order

  1. Each Trigger is one tab stop, in document order.
  2. The content of an open Panel follows its Trigger; a closed Panel's content is skipped.