Accordion
An accordion is a vertically stacked set of interactive headings containing a title, content snippet, or thumbnail representing a section of content.
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
npm install @zag-js/accordion @zag-js/solid # or yarn add @zag-js/accordion @zag-js/solid
npm install @zag-js/accordion @zag-js/vue # or yarn add @zag-js/accordion @zag-js/vue
npm install @zag-js/accordion @zag-js/vue # or yarn add @zag-js/accordion @zag-js/vue
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-partattribute 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
idto theuseMachinehook. 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: "Aircrafts", content: "Sample accordion content" }, ] function Accordion() { const [state, send] = useMachine(accordion.machine({ id: "1" })) const api = accordion.connect(state, send, normalizeProps) return ( <div {...api.rootProps}> {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> ) }
import * as accordion from "@zag-js/accordion" import { normalizeProps, useMachine } from "@zag-js/solid" import { createMemo, createUniqueId, For } from "solid-js" const data = [ { title: "Watercraft", content: "Sample accordion content" }, { title: "Automobiles", content: "Sample accordion content" }, { title: "Aircrafts", content: "Sample accordion content" }, ] function Accordion() { const [state, send] = useMachine(accordion.machine({ id: createUniqueId() })) const api = createMemo(() => accordion.connect(state, send, normalizeProps)) return ( <div {...api().rootProps}> <For each={data}> {(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> )} </For> </div> ) }
import * as accordion from "@zag-js/accordion" import { normalizeProps, useMachine } from "@zag-js/vue" import { defineComponent, h, Fragment, computed } from "vue" const data = [ { title: "Watercraft", content: "Sample accordion content" }, { title: "Automobiles", content: "Sample accordion content" }, { title: "Aircrafts", content: "Sample accordion content" }, ] export default defineComponent({ name: "Accordion", setup() { const [state, send] = useMachine(accordion.machine({ id: "1" })) const apiRef = computed(() => accordion.connect(state.value, send, normalizeProps), ) return () => { const api = apiRef.value return ( <div {...api.rootProps}> {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> ) } }, })
<script setup> import * as accordion from "@zag-js/accordion" import { normalizeProps, useMachine } from "@zag-js/vue" import { computed } from "vue" const data = [ { title: "Watercraft", content: "Sample accordion content" }, { title: "Automobiles", content: "Sample accordion content" }, { title: "Aircrafts", content: "Sample accordion content" }, ] const [state, send] = useMachine(accordion.machine({ id: "1" })) const api = computed(() => accordion.connect(state.value, send, normalizeProps)) </script> <template> <div ref="ref" v-bind="api.rootProps"> <div v-for="item in data" :key="item.id" v-bind="api.getItemProps({ value: item.title })" > <h3> <button v-bind="api.getItemTriggerProps({ value: item.title })"> {{ item.title }} </button> </h3> <div v-bind="api.getItemContentProps({ value: item.title })"> {{ item.content }} </div> </div> </div> </template>
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
multipleistrue, we internally setcollapsibleto betrue.
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 specific accordion items
To disable a specific accordion, 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-focused 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 */ }
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; content(value: string): string; trigger(value: string): string; }>The ids of the elements in the accordion. Useful for composition.multiplebooleanWhether multple accordion items can be open at the same time.collapsiblebooleanWhether an accordion item can be collapsed after it has been opened.valuestring[]The `id` of the accordion item that is currently being opened.disabledbooleanWhether the accordion items are disabledonValueChange(details: ValueChangeDetails) => voidThe callback fired when the state of opened/closed 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 accordionsetValue(value: string[]) => voidSets the value of the accordion.getItemState(props: ItemProps) => ItemStateGets the state of an accordion item.
Edit this page on GitHub