Collapsible
A collapsible is a component which expands and collapses a panel.
@Omikorin starred 4 repositories
@chakra-ui/zag-js
@chakra-ui/ark-ui
@chakra-ui/panda
@chakra-ui/chakra-ui
Features
- Full keyboard navigation.
Installation
To use the collapsible machine in your project, run the following command in your command line:
npm install @zag-js/collapsible @zag-js/react # or yarn add @zag-js/collapsible @zag-js/react
npm install @zag-js/collapsible @zag-js/vue # or yarn add @zag-js/collapsible @zag-js/vue
npm install @zag-js/collapsible @zag-js/vue # or yarn add @zag-js/collapsible @zag-js/vue
npm install @zag-js/collapsible @zag-js/solid # or yarn add @zag-js/collapsible @zag-js/solid
This command will install the framework agnostic collapsible logic and the reactive utilities for your framework of choice.
Usage
First, import the collapsible package into your project
import * as collapsible from "@zag-js/collapsible"
The collapsible package exports two key functions:
machine
— The state machine logic for the collapsible 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 theuseMachine
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 collapsible machine in your project 🔥
import * as collapsible from "@zag-js/collapsible" import { normalizeProps, useMachine } from "@zag-js/react" import { useId } from "react" const data = ["@chakra-ui/ark-ui", "@chakra-ui/panda", "@chakra-ui/chakra-ui"] function Collapsible() { const [state, send] = useMachine(collapsible.machine({ id: useId() })) const api = collapsible.connect(state, send, normalizeProps) return ( <div {...api.rootProps}> <button {...api.triggerProps}> {api.isOpen ? "Collapse" : "Expand"} </button> <div {...api.contentProps}> {data.map((item) => ( <div key={item}>{item}</div> ))} </div> </div> ) }
import * as collapsible from "@zag-js/collapsible" import { normalizeProps, useMachine } from "@zag-js/vue" import { defineComponent, computed } from "vue" const data = ["@chakra-ui/ark-ui", "@chakra-ui/panda", "@chakra-ui/chakra-ui"] export default defineComponent({ name: "Collapsible", setup() { const [state, send] = useMachine(collapsible.machine({ id: "1" })) const apiRef = computed(() => collapsible.connect(state.value, send, normalizeProps), ) return () => { const api = apiRef.value return ( <div {...api.rootProps}> <button {...api.triggerProps}> {api.isOpen ? "Collapse" : "Expand"} </button> <div {...api.contentProps}> {data.map((item) => ( <div>{item}</div> ))} </div> </div> ) } }, })
<script setup> import * as collapsible from "@zag-js/collapsible" import { normalizeProps, useMachine } from "@zag-js/vue" import { computed } from "vue" const data = ["@chakra-ui/ark-ui", "@chakra-ui/panda", "@chakra-ui/chakra-ui"] const [state, send] = useMachine(collapsible.machine({ id: "1" })) const api = computed(() => collapsible.connect(state.value, send, normalizeProps)) </script> <template> <div ref="ref" v-bind="api.rootProps"> <button v-if="api.isOpen" v-bind="api.triggerProps">Collapse</button> <button v-if="!api.isOpen" v-bind="api.triggerProps">Expand</button> <div v-bind="api.contentProps"> <div v-for="item in data" :key="item.id" > <div>{{ item }}</div> </div> </div> </div> </template>
import * as collapsible from "@zag-js/collapsible" import { normalizeProps, useMachine } from "@zag-js/solid" import { createMemo, createUniqueId } from "solid-js" const data = ["@chakra-ui/ark-ui", "@chakra-ui/panda", "@chakra-ui/chakra-ui"] function Collapsible() { const [state, send] = useMachine( collapsible.machine({ id: createUniqueId() }), ) const api = createMemo(() => collapsible.connect(state, send, normalizeProps)) return ( <div {...api().rootProps}> <button {...api().triggerProps}> {api().isOpen ? "Collapse" : "Expand"} </button> <div {...api().contentProps}> <Index each={items}>{(item) => <div>{item()}</div>}</Index> </div> </div> ) }
You may have noticed we wrapped each collapsible trigger within an h3
. This is
recommended by the
WAI-ARIA
design pattern to ensure the collapsible has the appropriate hierarchy on the
page.
Opening multiple collapsibles 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 collapsible
can be expanded.
const [state, send] = useMachine( collapsible.machine({ multiple: true, }), )
Opening specific collapsibles
To set the value of the collapsible(s) that should be opened initially, pass the
value
property to the machine function.
// for multiple collapsibles const [state, send] = useMachine( collapsible.machine({ multiple: true, value: ["home"], }), ) // for single collapsibles const [state, send] = useMachine( collapsible.machine({ value: "home", }), )
Toggle each collapsible item
To collapse an already expanded collapsible item by clicking on it, set the
context's collapsible
property to true
.
Note: If
multiple
istrue
, we internally setcollapsible
to betrue
.
const [state, send] = useMachine( collapsible.machine({ collapsible: true, }), )
Listening for changes
When the collapsible value changes, the onValueChange
callback is invoked.
const [state, send] = useMachine( collapsible.machine({ onValueChange(details) { // details => { value: string[] } console.log("selected collapsible:", details.value) }, }), )
Disabling the entire collapsible
You can disable the entire collapsible by passing disabled
to the
machine's context.
When a collapsible is disabled, it is skipped from keyboard navigation and can't be interacted with.
const [state, send] = useMachine( collapsible.machine({ disabled: true, }), )
Styling guide
Earlier, we mentioned that each collapsible part has a data-part
attribute
added to them to select and style them in the DOM.
Open and closed state
When a collapsible is expanded or collapsed, a data-state
attribute is
set on the root, trigger and content elements. This attribute is removed when it
is closed.
[data-part="root"][data-state="open|closed"] { /* styles for the item is open or closed state */ } [data-part="trigger"][data-state="open|closed"] { /* styles for the item is open or closed state */ } [data-part="content"][data-state="open|closed"] { /* styles for the item is open or closed state */ }
Focused state
When a collapsible'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 collapsible's api
exposes the following methods and properties:
Machine Context
The collapsible machine exposes the following context properties:
ids
Partial<{ root: string; content: string; trigger: string; }>
The ids of the elements in the collapsible. Useful for composition.disabled
boolean
If `true`, the collapsible will be disabledonOpenChange
(details: OpenChangeDetails) => void
Function called when the popup is openedopen
boolean
Whether the collapsible is opendir
"ltr" | "rtl"
The document's text/writing direction.id
string
The unique identifier of the machine.getRootNode
() => ShadowRoot | Node | Document
A root node to correctly resolve document in custom environments. E.x.: Iframes, Electron.
Machine API
The collapsible api
exposes the following methods:
isOpen
boolean
Whether the collapsible is open.isDisabled
boolean
Whether the collapsible is disabledisFocused
boolean
Whether the checkbox is focusedopen
() => void
Function to open the collapsible.close
() => void
Function to close the collapsible.
Edit this page on GitHub