Skip to main content
0.79.0
View Zag.js on Github
Join the Discord server

Accordion

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

Sample accordion content
Properties

Features

  • Full keyboard navigation.
  • Can expand one or multiple items.
  • Collapse each accordion item.

Installation

To use the accordion machine in your project, run the following command in your command line:

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

This command will install the framework agnostic accordion logic and the reactive utilities for your framework of choice.

Anatomy

To set up the accordion correctly, you'll need to understand its anatomy and how we name its parts.

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

Usage

First, import the accordion package into your project

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

The accordion package exports two key functions:

  • machine — The state machine logic for the accordion widget.
  • connect — The function that translates the machine's state to JSX attributes and event handlers.

You'll also need to provide a unique id to the useMachine hook. This is used to ensure that every part has a unique identifier.

Next, import the required hooks and functions for your framework and use the accordion machine in your project 🔥

import * as accordion from "@zag-js/accordion" import { useMachine, normalizeProps } from "@zag-js/react" const data = [ { title: "Watercraft", content: "Sample accordion content" }, { title: "Automobiles", content: "Sample accordion content" }, { title: "Aircraft", content: "Sample accordion content" }, ] function Accordion() { const [state, send] = useMachine(accordion.machine({ id: "1" })) const api = accordion.connect(state, send, normalizeProps) return ( <div {...api.getRootProps()}> {data.map((item) => ( <div {...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 accordions at once

To allow multiple items to be expanded at once, set multiple to true. This mode implicitly sets collapsible to true and ensures that each accordion can be expanded.

const [state, send] = useMachine( accordion.machine({ multiple: true, }), )

Opening specific accordions

To set the value of the accordion(s) that should be opened initially, pass the value property to the machine function.

// for multiple accordions const [state, send] = useMachine( accordion.machine({ multiple: true, value: ["home"], }), ) // for single accordions const [state, send] = useMachine( accordion.machine({ value: ["home"], }), )

Toggle each accordion item

To collapse an already expanded accordion item by clicking on it, set the context's collapsible property to true.

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

const [state, send] = useMachine( accordion.machine({ collapsible: true, }), )

Listening for changes

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

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

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 items by passing disabled to the machine's context.

const [state, send] = useMachine( accordion.machine({ disabled: true, }), )

Styling guide

Earlier, we mentioned that each accordion part has a data-part attribute added to them to select and style them in the DOM.

Open and closed state

When an accordion item is expanded or collapsed, a data-state attribute is set on the item, trigger and content elements. This attribute is removed when it is closed.

[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 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 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" interface Item { value: string title: React.ReactNode content: React.ReactNode } export interface AccordionProps extends Omit<accordion.Context, "id"> { defaultValue?: accordion.Context["value"] items: Item[] } export function Accordion(props: AccordionProps) { const [machineProps, localProps] = accordion.splitProps(props) const [state, send] = useMachine( accordion.machine({ id: useId(), value: defaultValue }), { context: machineProps }, ) const api = accordion.connect(state, send, 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 `value` of the accordion items that are currently being expanded.
  • 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) => ItemStateGets 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

Proudly made in🇳🇬by Segun Adebayo

Copyright © 2024
On this page