Accordion

Stacked sections that each show and hide their content under a heading.

BetaSince 1.0.0
On this page

Import

import { Accordion } from '@envsemble/envui';

Usage

Accordion.Root holds an Accordion.Item for each section, with its heading as title and its panel as children. One item is open at a time; multiple lets several stay open. disabled on the root or an item turns it off.

FrequentQuestions.tsx
import { Accordion } from '@envsemble/envui';

const questions = [
  {
    value: 'sharing',
    question: 'Who can see a shared plan?',
    answer: 'Everyone in the workspace can read it; only its editors can change it.',
  },
  {
    value: 'moving',
    question: 'Can I move a plan to another workspace?',
    answer: 'Yes. Open the plan’s menu, choose Move, and pick a workspace you belong to.',
  },
  {
    value: 'archive',
    question: 'What happens to archived plans?',
    answer: 'They stay readable and searchable, and you can restore them at any time.',
  },
  {
    value: 'export',
    question: 'Can I export a plan?',
    answer: 'Export it as Markdown or PDF from the plan’s menu.',
  },
];

export default function FrequentQuestions() {
  return (
    // One answer open at a time; the first starts open.
    <Accordion.Root defaultValue={['moving']} style={{ inlineSize: 'min(100%, 32rem)' }}>
      {questions.map(({ value, question, answer }) => (
        <Accordion.Item key={value} value={value} title={question}>
          {answer}
        </Accordion.Item>
      ))}
    </Accordion.Root>
  );
}

Open items

defaultValue names the items open at first, by their value; or control them with value and onValueChange, which receives the values of the open items. An item's own onOpenChange reports whether it is open.

const [open, setOpen] = useState<string[]>([]);

<Button onClick={() => setOpen(['billing'])}>Show billing help</Button>
<Accordion.Root value={open} onValueChange={setOpen}>
  <Accordion.Item value="billing" title="Billing">…</Accordion.Item>
</Accordion.Root>

Find in page

hiddenUntilFound keeps closed panels in the page, so the browser's find in page reaches their text and opens the item that holds it, where the browser supports it.

Composition

The headings are h3; set headingLevel to fit the page's outline, and they look the same at every level. Show the next level inside a panel as a List rather than another accordion.

Customize

The parts are the accordion, env-accordion; each item, env-accordion-item, which draws the line under it; its heading's button, env-accordion-trigger, with the indicator, env-accordion-indicator, and the title, env-accordion-label; and the panel, env-accordion-content.

Accessibility

You provide

  • Titles that say what each section holds.
  • headingLevel that fits the page's outline.

envUI provides

  • Each title is a button inside a heading, which reports whether its section is expanded and controls its panel.
  • Tab reaches every heading. The arrow keys, Home and End don't move between them, as the current ARIA accordion pattern advises, so they stay with the page and the panels' content.
  • A focus outline, and headings at least 2.75 rem tall on touch screens.

Props

Accordion.Root

A list of sections, each a heading that shows and hides its panel, separated by full-width hairlines. A plus at the start of a heading becomes a minus while its section is open. One item is open at a time unless `multiple` is set. Built on Base UI's Accordion.

PropTypeDescription
children
ReactNode
The items: Accordion.Item, one for each section.
value
readonly string[]
The values of the open items, for a controlled accordion: update it from onValueChange. Use defaultValue instead to leave it to the accordion.
defaultValueDefault []
readonly string[]
The values of the items open at first, for an accordion that keeps its own state.
onValueChange
(value: string[]) => void
Receives the values of the open items, each time an item opens or closes.
multipleDefault false
boolean
Lets several items be open at once; otherwise opening one closes the one that was open.
headingLevelDefault 3
Exclude<HeadingLevel, 1>
The level of each item's heading, for the page's outline; they look the same at every level.
disabledDefault false
boolean
Every item ignores presses and can't be focused.
hiddenUntilFoundDefault false
boolean
Keeps closed panels in the page, hidden until found, so the browser's find in page reaches their text and opens the item that holds it, where the browser supports it.
skeletonDefault false
boolean
Shows it and its content as a busy skeleton while that loads; turning it on or off renders the content anew.

Also accepts the native props of <div>, such as ref, className and aria-* attributes.

Accordion.Item

One section of an accordion: its heading, a button with a plus or minus at its start, and the panel below it.

PropTypeDescription
title
ReactNode
The heading that opens and closes the section, in medium weight: say what the panel holds, such as a question it answers. Given as undefined, such as over a placeholder item, a skeleton shows a bar in its place.
children
ReactNode
The panel, in muted text lined up with the title, shown while the item is open.
value
string
Names the item in the accordion's value, for an accordion that is controlled or opens items at first. Without it, the item takes a generated one.
disabledDefault false
boolean
The item ignores presses and can't be focused; it stays dimmed.
onOpenChange
(open: boolean) => void
Receives whether this item is open, each time it opens or closes.

Also accepts the native props of <div>, such as ref, className and aria-* attributes.

Part names

Each part's class and state attributes are public and versioned, for an application's CSS to style.

env-accordion
The accordion: sections separated by full-width hairlines.
env-accordion-content
The panel's content, in muted text lined up with the title.
env-accordion-header
An item's heading, which holds its button.
env-accordion-indicator
The plus that becomes a minus while the item is open.
env-accordion-item
One section, with the hairline under it.
env-accordion-label
An item's title.
env-accordion-panel
The panel that opens below the heading.
env-accordion-trigger
The button that opens and closes the item.
data-disabled
Any value, or present on env-accordion-trigger
data-ending-style
Any value, or present on env-accordion-panel
data-env-skeleton
Any value, or present
data-interactive
Present on env-accordion-trigger
data-panel-open
Any value, or present on env-accordion-trigger
data-starting-style
Any value, or present on env-accordion-panel

Changelog

1.0.0

Accordion.

1.0.0-prerelease.6

A closing panel's content fades over the panel's whole closing, --env-motion-duration-medium, so it leaves no empty band.

Colors under the pointer and while pressed change with --env-motion-ease-enter, rather than easing in slowly, so hover and press show from the first frame.

A panel's content fades in and out as it grows and shrinks, and the plus and minus fade into each other.

A heading lays the shared state layer on hover and press, inset from the hairlines, with its focus ring tracing it; the title and panel sit 8 px in from the hairlines to match.