Skip to content
buttery

v1.0.0

Source on GitHub

Version 1.0.0

Accordion

A list of items that each open in place: a trigger row over a panel whose height follows its content.

import { Accordion } from "@butteryui/react";

Demo

Order by noon and we roast it the next morning, then ship it that afternoon. Most bags arrive in two to four days.

When to use it

Use it for

  • Questions and answers, or any list of titles whose details only some people need
  • Long details or settings in parts that people open one or a few at a time, such as an account's
  • A narrow panel with more in it than fits, where each part's title is enough to pick it

Not for

  • A whole section of questions with groups and a search: use Faq
  • Switching between views of one thing: use Tabs
  • One part that shows more: a button over a Morph
  • What everyone needs to read: show it instead of folding it away

Examples

Account sections

A customer's account in parts that open several at once, each with the controls that change it.

Your next bag roasts on Thursday morning.

Brew step by step

A pour-over guide whose page keeps the open step, so Next step opens the one after it.

Pour hot water through the paper filter, then pour that water away.

Then: Let it bloom

Four looks

The same questions as divided rows, rows in a card, a card each or plain rows, at each size.

Whole beans stay fresh longest. Without a grinder, choose your brewer and we grind for it the morning we roast.

Pickup paused

Ways to get an order, one of them paused: a disabled item stays shut and the keyboard passes over it.

Shipping questions

The shop's shipping questions, one open at a time.

Variants and states

Divided

Rows with a hairline between them, lined up with the text around them. The default.

Order by noon and we roast it the next morning, then ship it that afternoon. Most bags arrive in two to four days.

Contained

Rows in one card, divided by hairlines, tinting on hover.

Order by noon and we roast it the next morning, then ship it that afternoon. Most bags arrive in two to four days.

Cards

A card for each item, 8px apart.

Order by noon and we roast it the next morning, then ship it that afternoon. Most bags arrive in two to four days.

Plain

Rows without lines that tint on hover, for a sidebar or a panel.

Order by noon and we roast it the next morning, then ship it that afternoon. Most bags arrive in two to four days.

Sizes

sm for a panel, md (the default) for an app page, lg for a page's own questions.

size="sm"

Order by noon and we roast it the next morning, then ship it that afternoon. Most bags arrive in two to four days.

size="md"

Order by noon and we roast it the next morning, then ship it that afternoon. Most bags arrive in two to four days.

size="lg"

Order by noon and we roast it the next morning, then ship it that afternoon. Most bags arrive in two to four days.

Icons, avatars and descriptions

An icon sits on the title's first line; anything else in leading centers on the title and its description.

Disabled

A disabled item can't open and leaves the tab order; the arrow keys skip it.

Props

Accordion

PropTypeDefaultDescription
type"single" | "multiple""single""single" (the default) keeps one item open at a time; opening another closes it. "multiple" lets any number of items stay open at once.
valuestring | readonly string[]With type="single": The open item's value, or "" while none is. Pair with onValueChange to control it. With type="multiple": The open items' values. Pair with onValueChange to control them.
defaultValuestring | readonly string[]With type="single": The item open at first when uncontrolled. Defaults to none. With type="multiple": The items open at first when uncontrolled. Defaults to none.
onValueChange((value: string) => void) | ((value: readonly string[]) => void)With type="single": Called with the value of the item that opens, or "" when the open one closes. With type="multiple": Called with the open items' values each time one opens or closes.
collapsiblebooleantrueWith type="single": Whether pressing the open item's trigger closes it. Defaults to true.
variant"divided" | "contained" | "cards" | "plain""divided"How the items are drawn: "divided" (the default), rows with a hairline between them; "contained", rows in one card; "cards", a card for each item, 8px apart; or "plain", rows that tint on hover.
size"sm" | "md" | "lg"How big the rows are: "sm" (14px titles, 12px above and below), "md" (the default: 15px titles, 16px above and below) or "lg" (18px titles, 20px above and below).
disabledbooleanKeeps every item from opening or closing.
headingLevel2 | 3 | 4 | 5 | 63The heading level each trigger sits in, one below the heading over the accordion. Defaults to 3.
childrenReactNodeThe items: Accordion.Items, as direct children.

It also takes Omit<ComponentPropsWithRef<"div">, "defaultValue" | "dir">.

Accordion.Item

PropTypeDefaultDescription
value

required

stringNames the item: what the accordion's value holds while it's open.
title

required

ReactNodeThe trigger's words, such as a question. They wrap onto more lines, and they name the trigger and its panel.
descriptionReactNodeA line under the title, such as where the item comes from. Screen readers hear it after the title, as part of the trigger's name.
iconComponentType<IconComponentProps>An icon before the title, on the middle of its first line.
leadingReactNodeAnything else before the title, such as an Avatar, centered on the title and description. It takes the place of icon.
disabledbooleanKeeps the item from opening or closing, and takes its trigger out of the tab order.
childrenReactNodeThe panel: what shows under the trigger while the item is open.

It also takes Omit<ComponentPropsWithRef<"div">, "title">.

Accessibility

Each trigger is a button in a heading (an h3 unless headingLevel says otherwise), named by its title followed by its description, with aria-expanded, and aria-controls naming its panel while it's open; the panel is a region named by the title alone. Tab moves from trigger to trigger and into an open panel; the arrow keys move between triggers, wrapping at the ends, and Home and End reach the first and last. A disabled item's trigger is left out of both.

KeysWhat they do
Enter or SpaceOpens or closes the item whose trigger has focus
TabMoves to the next trigger, going through an open panel's content first
Arrow Down or Arrow UpMoves to the next or previous trigger, wrapping at the ends and skipping disabled ones
Home or EndMoves to the first or last trigger

Motion

It changes shape as one surface, on `spring.smooth`, or `spring.morph` for a big change, rather than closing and reopening.

With reduced motion: The panel opens and closes at once and the chevron turns over at once; the panel's content fades in and out in 100ms.

Layout

It fills its container's width with its items stacked: touching, with a hairline between them (divided); in one card (contained); 8px apart, a card each (cards); or 2px apart (plain). A trigger row is as tall as its title, which wraps onto more lines and never truncates, with 12, 16 or 20px above and below it by size; the whole row is the trigger's hit area. Its chevron and an icon sit on the middle of the title's first line, and the panel's content starts at the row's padding edge, under the icon. Divided rows have no side padding, so their words line up with the text around them.