Skip to main content

An accordion is a vertically stacked set of interactive headings containing a title, content snippet, or thumbnail representing a section of content.

Loading...

Features

  • Full keyboard navigation
  • Supports single and multiple expanded items
  • Supports collapsible items
  • Supports horizontal and vertical orientation

Installation

Install the accordion package:

npm install @zag-js/accordion @zag-js/react # or yarn add @zag-js/accordion @zag-js/react

Anatomy

Check the accordion anatomy and part names.

Each part includes a data-part attribute to help identify them in the DOM.

Usage

Import the accordion package:

import * as accordion from "@zag-js/accordion"

The accordion package exports two key functions:

  • machine - State machine logic.
  • connect - Maps machine state to JSX props and event handlers.

Pass a unique id to useMachine so generated element ids stay predictable.

Then use the framework integration helpers:

import * as accordion from "@zag-js/accordion" import { useMachine, normalizeProps } from "@zag-js/react" import { useId } from "react" const data = [ { title: "Watercraft", content: "Sample accordion content" }, { title: "Automobiles", content: "Sample accordion content" }, { title: "Aircraft", content: "Sample accordion content" }, ] function Accordion() { const service = useMachine(accordion.machine, { id: useId() }) const api = accordion.connect(service, normalizeProps) return ( <div {...api.getRootProps()}> {data.map((item) => ( <div key={item.title} {...api.getItemProps({ value: item.title })}> <h3> <button {...api.getItemTriggerProps({ value: item.title })}> {item.title} </button> </h3> <div {...api.getItemContentProps({ value: item.title })}> {item.content} </div> </div> ))} </div> ) }

You may have noticed we wrapped each accordion trigger within an h3. This is recommended by the WAI-ARIA design pattern to ensure the accordion has the appropriate hierarchy on the page.

Opening multiple items

Set multiple to true to allow more than one expanded item at a time.

const service = useMachine(accordion.machine, { multiple: true, })

Setting the initial value

Set defaultValue to define expanded items on first render.

// multiple mode const service = useMachine(accordion.machine, { multiple: true, defaultValue: ["home", "about"], }) // single mode const service = useMachine(accordion.machine, { defaultValue: ["home"], })

Controlled accordions

Use value and onValueChange to control expanded items externally.

import { useState } from "react" export function ControlledAccordion() { const [value, setValue] = useState(["home"]) const service = useMachine(accordion.machine, { value, onValueChange(details) { setValue(details.value) }, }) return ( // ... ) }

Making items collapsible

Set collapsible to true to allow closing an expanded item by clicking it again.

Note: If multiple is true, we internally set collapsible to be true.

const service = useMachine(accordion.machine, { collapsible: true, })

Listening for value changes

When the accordion value changes, the onValueChange callback is invoked.

const service = useMachine(accordion.machine, { onValueChange(details) { // details => { value: string[] } console.log("selected accordion:", details.value) }, })

Listening for focus changes

Use onFocusChange to react when keyboard focus moves between item triggers.

const service = useMachine(accordion.machine, { onFocusChange(details) { // details => { value: string | null } console.log("focused item:", details.value) }, })

Horizontal orientation

Set orientation to horizontal when rendering items side by side.

const service = useMachine(accordion.machine, { orientation: "horizontal", })

Disabling an accordion item

To disable a specific accordion item, pass the disabled: true property to the getItemProps, getItemTriggerProps and getItemContentProps.

When an accordion item is disabled, it is skipped from keyboard navigation and can't be interacted with.

//... <div {...api.getItemProps({ value: "item", disabled: true })}> <h3> <button {...api.getItemTriggerProps({ value: "item", disabled: true })}> Trigger </button> </h3> <div {...api.getItemContentProps({ value: "item", disabled: true })}> Content </div> </div> //...

You can also disable the entire accordion by setting disabled on the machine.

const service = useMachine(accordion.machine, { disabled: true, })

Styling guide

Each part includes a data-part attribute you can target in CSS.

Open and closed state

When an accordion item expands or collapses, data-state is set to open or closed on the item, trigger, and content elements.

[data-part="item"][data-state="open|closed"] { /* styles for the item is open or closed state */ } [data-part="item-trigger"][data-state="open|closed"] { /* styles for the item is open or closed state */ } [data-part="item-content"][data-state="open|closed"] { /* styles for the item is open or closed state */ }

Focused state

When an accordion item's trigger is focused, a data-focus attribute is set on the item and content.

[data-part="item"][data-focus] { /* styles for the item's focus state */ } [data-part="item-trigger"]:focus { /* styles for the trigger's focus state */ } [data-part="item-content"][data-focus] { /* styles for the content's focus state */ }

Creating a component

Create your accordion component by abstracting the machine into your own component.

Usage

import { Accordion } from "./your-accordion" function Demo() { return ( <Accordion defaultValue={["1"]} items={[ { value: "1", title: "Title 1", content: "Content 1" }, { value: "2", title: "Title 2", content: "Content 2" }, ]} /> ) }

Implementation

Use the splitProps utility to separate the machine's props from the component's props.

import * as accordion from "@zag-js/accordion" import { useMachine, normalizeProps } from "@zag-js/react" import { useId } from "react" interface Item { value: string title: React.ReactNode content: React.ReactNode } export interface AccordionProps extends Omit<accordion.Props, "id"> { items: Item[] } export function Accordion(props: AccordionProps) { const [machineProps, localProps] = accordion.splitProps(props) const service = useMachine(accordion.machine, { id: useId(), ...machineProps, }) const api = accordion.connect(service, normalizeProps) return ( <div {...api.getRootProps()}> {localProps.items.map((item) => ( <div {...api.getItemProps({ value: item.value })}> <h3> <button {...api.getItemTriggerProps({ value: item.value })}> {item.title} </button> </h3> <div {...api.getItemContentProps({ value: item.value })}> {item.content} </div> </div> ))} </div> ) }

Methods and Properties

The accordion's api exposes the following methods and properties:

Machine Context

The accordion machine exposes the following context properties:

  • idsPartial<{ root: string; item: (value: string) => string; itemContent: (value: string) => string; itemTrigger: (value: string) => string; }>The ids of the elements in the accordion. Useful for composition.
  • multiplebooleanWhether multiple accordion items can be expanded at the same time.
  • collapsiblebooleanWhether an accordion item can be closed after it has been expanded.
  • valuestring[]The controlled value of the expanded accordion items.
  • defaultValuestring[]The initial value of the expanded accordion items. Use when you don't need to control the value of the accordion.
  • disabledbooleanWhether the accordion items are disabled
  • onValueChange(details: ValueChangeDetails) => voidThe callback fired when the state of expanded/collapsed accordion items changes.
  • onFocusChange(details: FocusChangeDetails) => voidThe callback fired when the focused accordion item changes.
  • orientation"horizontal" | "vertical"The orientation of the accordion items.
  • dir"ltr" | "rtl"The document's text/writing direction.
  • idstringThe unique identifier of the machine.
  • getRootNode() => ShadowRoot | Node | DocumentA root node to correctly resolve document in custom environments. E.x.: Iframes, Electron.

Machine API

The accordion api exposes the following methods:

  • focusedValuestringThe value of the focused accordion item.
  • valuestring[]The value of the accordion
  • setValue(value: string[]) => voidSets the value of the accordion
  • getItemState(props: ItemProps) => ItemStateReturns the state of an accordion item.

Data Attributes

Root
data-scope
accordion
data-part
root
data-orientation
The orientation of the accordion
Item
data-scope
accordion
data-part
item
data-state
"open" | "closed"
data-focus
Present when focused
data-disabled
Present when disabled
data-orientation
The orientation of the item
ItemContent
data-scope
accordion
data-part
item-content
data-state
"open" | "closed"
data-disabled
Present when disabled
data-focus
Present when focused
data-orientation
The orientation of the item
ItemIndicator
data-scope
accordion
data-part
item-indicator
data-state
"open" | "closed"
data-disabled
Present when disabled
data-focus
Present when focused
data-orientation
The orientation of the item
ItemTrigger
data-scope
accordion
data-part
item-trigger
data-orientation
The orientation of the item
data-state
"open" | "closed"

Accessibility

Keyboard Interactions

  • Space
    When focus is on an trigger of a collapsed item, the item is expanded
  • Enter
    When focus is on an trigger of a collapsed section, expands the section.
  • Tab
    Moves focus to the next focusable element
  • Shift + Tab
    Moves focus to the previous focusable element
  • ArrowDown
    Moves focus to the next trigger
  • ArrowUp
    Moves focus to the previous trigger.
  • Home
    When focus is on an trigger, moves focus to the first trigger.
  • End
    When focus is on an trigger, moves focus to the last trigger.
Edit this page on GitHub