Zag is a framework agnostic toolkit for implementing complex, interactive, and accessible UI components in your design system and web applications. Works for React, Solid and Vue. > Zag is part of the next evolution of Chakra UI, and one of the four arms of > the future of Chakra UI. > [**Watch the talk here**](https://www.youtube.com/watch?v=I5xEc9t-HZg) ## Motivation In [Chakra UI React](https://chakra-ui.com/), we've experienced too many hiccups and bugs in the past related to how we coordinate events, manage state, and side effects. Most these bugs are associated with the orchestration within `useEffect`, `useMemo`, `useCallback`, etc. These issues were replicated in our [Chakra UI Vue](https://vue.chakra-ui.com/) pursuit as well, and created a maintenance hell for us. We're grateful for this experience because it made us take a step back to define how we would like to build components in the future. We believe that most widgets should function the same way regardless of the framework they're built with. That's why we built Zag. > Don't re-invent the wheel, **let the machines do the work 😊** ## Why Zag? - **Powered by state machines 🌳**: Zag is built on top of the latest ideas in Statecharts. We don't follow the SCXML specifications, but we've created an API that we think will help us build more complex components fast. - **Write once, use everywhere 🦄**: The component interactions are modelled in a framework agnostic way. We provide adapters for JS frameworks so you can use it in React, Solid, or Vue 3. - **Focus on accessibility ♿️**: Zag is built with accessibility in mind. We handle many details related to keyboard interactions, focus management, aria roles and attributes. - **Headless ✨**: The machine APIs are completely unstyled and gives you the control to use any styling solution you prefer. - **Incremental adoption ✨**: Adopt the machines as you need them. Each component machine is an NPM package and can be installed individually so you can use them incrementally. ## Learn [Watch the course](https://egghead.io/courses/statechart-driven-ui-components-with-zag-js-53f85394) on Egghead to learn how to build statechart-driven UI components with Zag.js. This course will give you a deep dive into how Zag works and how you can use it to build complex UI components. ## Fun Facts **Zag** means to _take a sharp change in direction_. This clearly describes our approach of using state machines to power the logic behind UI components. ### Teasers - When you see someone using classic react, vue or solid to build an interactive UI component that exists in Zag, tell them to **"zag it!"** ⚡️ - Anyone using Zag will be called a **"zagger"** 💥 - The feeling you get when you use Zag will be called **"zagadat!"** 🚀 - The Zag community will be called **"zag nation"** 🔥 ## Community ### Discord To get involved with the Zag community, ask questions, and chat with the maintainers, join our Discord. [Join our Discord](https://zagjs.com/discord) ### Twitter To receive updates on new components, enhancements, blog posts, and tips, follow our Twitter account. [Follow us on Twitter](https://twitter.com/zag_js) ## Prior art We strongly believe in open source and the power of open collaboration. In the past, we've been inspired by other meaningful projects and amazing people who have inspire(d) us to keep improving our ideas. Some of the projects we've been inspired by include: - Chakra UI - [https://chakra-ui.com/](https://chakra-ui.com/) - Radix UI - [https://www.radix-ui.com/](https://www.radix-ui.com/) - Material Web Components - [https://github.com/material-components/material-components-web](https://github.com/material-components/material-components-web) - React Aria - [https://react-spectrum.adobe.com/react-aria](https://react-spectrum.adobe.com/react-aria) - Goldman Sachs Design System - [https://design.gs.com/](https://design.gs.com/) - Reakit - [https://reakit.io/](https://reakit.io/) - Fast - [https://fast.design/](https://fast.design/) ## Additional Thanks - [Guillermo](https://rauchg.com/2015/pure-ui) for writing a great article that sparked the idea for Zag. - [Open UI](https://open-ui.org/) for inspiring the pattern behind this library - [XState](https://xstate.js.org/) for inspiring the base implementation of the state machine - [Vue.js](https://vuejs.org/) and [Lit](https://lit.dev/) for inspiring new patterns in the machine (`computed` and `watch`) - [David Khourshid](https://twitter.com/DavidKPiano) for talking about state machines long enough to get me started on this project Zag can be used within most JS frameworks like Vue, React, Svelte and Solid. To get Zag running, you'll need to: 1. Install the machine for the component you're interested in. Let's say you want to use the `tooltip` machine. ```bash npm install @zag-js/tooltip # or yarn add @zag-js/tooltip ``` 2. Install the adapter for the framework of your choice. At the moment, Zag is available for React, Vue 3, Svelte and Solid.js. Let's say you use React. ```bash npm install @zag-js/react # or yarn add @zag-js/react ``` > Congrats! You're ready to use tooltip machine in your project. ## Using the machine Here's an example of the tooltip machine used in a React.js project. ```jsx import * as tooltip from "@zag-js/tooltip" import { useMachine, normalizeProps } from "@zag-js/react" export function Tooltip() { const service = useMachine(tooltip.machine, { id: "1" }) const api = tooltip.connect(service, normalizeProps) return ( <> {api.open && (
Tooltip
)} ) } ``` ### Usage with Vue 3 (JSX) Zag works seamlessly with Vue's JSX approach. Here's how to use the same tooltip logic in Vue: ```jsx import * as tooltip from "@zag-js/tooltip" import { normalizeProps, useMachine } from "@zag-js/vue" import { computed, defineComponent, h, Fragment } from "vue" export default defineComponent({ name: "Tooltip", setup() { const service = useMachine(tooltip.machine, { id: "1" }) const apiRef = computed(() => tooltip.connect(service, normalizeProps)) return () => { const api = apiRef.current return ( <>
{api.open && (
Tooltip
)}
) } }, }) ``` There are some extra functions that need to be used in order to make it work: - `normalizeProps` - Converts the props of the component into the format that is compatible with Vue. - `computed` - Ensures that the tooltip's `api` is always up to date with the current state of the machine. ### Usage with Solid.js We love Solid.js and we've added support for it. Here's how to use the same tooltip logic in Solid: ```jsx import * as tooltip from "@zag-js/tooltip" import { normalizeProps, useMachine } from "@zag-js/solid" import { createMemo, createUniqueId, Show } from "solid-js" export function Tooltip() { const service = useMachine(tooltip.machine, { id: createUniqueId() }) const api = createMemo(() => tooltip.connect(service, normalizeProps)) return (
Tooltip
) } ``` There are some extra functions that need to be used in order to make it work: - `normalizeProps` - Converts the props of the component into the format that is compatible with Solid. - `createMemo` - Ensures that the tooltip's `api` is always up to date with the current state of the machine. ### Usage with Svelte Here's how to use the same tooltip logic in Svelte: ```html
{#if api.open}
Tooltip
{/if}
``` There are some extra functions that need to be used in order to make it work: - `normalizeProps` - Converts the props of the component into the format that is compatible with Svelte. - `$derived` - Ensures that the tooltip's `api` is always up to date with the current state of the machine. ### About prop normalization There are subtle difference between how JSX attributes are named across frameworks like React, Solid, Vue and Svelte. Here are some examples: **Keydown listener** - React and Solid: The keydown listener property is `onKeyDown`. - Vue: The keydown listener property is `onKeydown`. **Styles** - React: Pass a numeric value for margin attributes like `{ marginBottom: 4 }`. - Solid: It has to be `{ "margin-bottom": "4px" }`. - Vue: You need to ensure the value is a string with unit. `{ marginBottom: "4px" }`. These little nuances between frameworks are handled automatically when you use `normalizeProps`. > The goal of Zag is to help you abstract the interaction and accessibility > patterns into a statechart so you never have to re-invent the wheel. Thanks for reading! If you're curious about how state machines work, the next page will give you a quick overview. A state machine is a tool for modeling stateful, reactive systems. It is useful for declaratively describing the behavior of an application or component. To model any logic as using the state machine pattern, it must have: - A finite number of states. - A finite number of transitions between states. ## Creating a state machine Consider a simple toggle or switch component that consists of two states, `active` and `inactive`. The initial state will be `active` The supported transitions that can happen here: - In the `active` state, when we click the toggle, it should transition to the `inactive` state - In the `inactive` state, when we click the toggle, it should transition to the `active` state Here's how we'll model the logic in code: ```jsx import { createMachine } from "@zag-js/core" const machine = createMachine({ // initial state initialState() { return "active" }, // the finite states states: { active: { on: { CLICK: { // go to inactive target: "inactive", }, }, }, inactive: { on: { CLICK: { // go to active target: "active", }, }, }, }, }) ``` ### TypeScript Guide For TypeScript projects, you can add type definitions to make your machine type-safe: ```tsx import { createMachine, type Service } from "@zag-js/core" import type { NormalizeProps, PropTypes } from "@zag-js/types" interface ToggleSchema { state: "active" | "inactive" event: { type: "CLICK" } } export const machine = createMachine({ // ... same as above }) ``` ## Writing the connect function Now that we've modelled the component logic, let's map that to DOM attributes and event handlers following the [WAI-ARIA](https://www.w3.org/TR/wai-aria-practices-1.1/examples/checkbox/checkbox-1/checkbox-1.html) specification for the switch component. We'll write a function called `connect` to do this. ```jsx function connect(service, normalize) { const { state, send } = service const active = state.matches("active") return { active, getButtonProps() { return normalize.button({ type: "button", role: "switch", "aria-checked": active, onClick() { send({ type: "CLICK" }) }, }) }, } } ``` ### TypeScript Guide For TypeScript projects, you can add type definitions to make your connect function type-safe: ```tsx import { type Service } from "@zag-js/core" import type { NormalizeProps, PropTypes } from "@zag-js/types" interface ToggleSchema { state: "active" | "inactive" event: { type: "CLICK" } } export function connect( service: Service, normalize: NormalizeProps, ) { const { state, send } = service const active = state.matches("active") return { active, getButtonProps() { return normalize.button({ type: "button", role: "switch", "aria-checked": active, onClick() { send({ type: "CLICK" }) }, }) }, } } ``` ## Consuming the state machine Here's how to consume the toggle machine logic and connect in React.js. ```jsx import { useMachine, normalizeProps } from "@zag-js/react" import { machine, connect } from "./toggle" function Toggle() { const service = useMachine(machine) const api = connect(service, normalizeProps) return ( ) } ``` That's it! Now you've learned the fundamentals of a component state machine. ## Next Steps Now that you understand the basics, learn about the advanced concepts that power Zag machines: - [Building Machines Guide](/guides/building-machines) - Deep dive into context (bindable), watch/track, computed, refs, and more ## Why the need for `normalizeProps`? The goal of `normalizeProps` is to convert the props of the component into the format that is compatible with the respective framework. It is also used to ensure that the returned properties are strongly typed. There are subtle differences between how element attributes are named across frameworks like React, Solid and Vue. Here are some examples: **Keydown listener** - React and Solid: The keydown listener property is `onKeyDown`. - Vue: The keydown listener property is `onKeydown`. **Styles** - React: Pass a numeric value for margin attributes like `{ marginBottom: 4 }`. - Solid: It has to be `{ "margin-bottom": "4px" }`. - Vue: You need to ensure the value is a string with unit. `{ marginBottom: "4px" }`. These little nuances between frameworks are handled automatically when you use `normalizeProps`. ## How can I attach custom extra event handlers to the elements? See the approach [here](/guides/composition#event-composition). ## How can I get Zag working in a custom window environment? See the approach [here](/guides/composition#custom-window-environment). ## What would it take to support other frameworks? We're currently interested in supporting as many frameworks as possible. The key requirements are: - **Support for "spread props"**: The framework should have support for spreading attributes and event handlers. - **Exposed Typings**: The framework should expose the typings for the attributes and event handlers. This is optional but would provide the best DX. ## How do I upgrade all zag dependencies? Since we use independent versioning for each zag package, it can sometimes be inconvenient to upgrade all dependencies individually. You can use scoped upgrades feature in your package manager to update all zag packages seamlessly. ```bash pnpm up "@zag-js/*" # or yarn upgrade --scope @zag-js # or npm up @zag-js/... ``` ## What is LLMs.txt? We support [LLMs.txt](https://llmstxt.org/) files for making the Zag JS documentation available to large language models (LLMs). This feature helps AI tools better understand our component library, its APIs, and usage patterns. ## Available Routes We provide several LLMs.txt routes to help AI tools access our documentation: - - Contains a structured overview of all components and their documentation links - - Provides comprehensive documentation including implementation details and examples - - React-specific documentation and implementation details - - SolidJS-specific documentation and implementation details - - Vue-specific documentation and implementation details - - Svelte-specific documentation and implementation details ## Usage with AI Tools ### Cursor Use the `@Docs` feature in Cursor to include the LLMs.txt files in your project. This helps Cursor provide more accurate code suggestions and documentation for Zag JS components. [Read more about @Docs in Cursor](https://docs.cursor.com/context/@-symbols/@-docs) ### Windstatic Reference the LLMs.txt files using `@` or in your `.windsurfrules` files to enhance Windstatic's understanding of Zag JS components. [Read more about Windstatic Memories](https://docs.codeium.com/windsurf/memories#memories-and-rules) ### Other AI Tools Any AI tool that supports LLMs.txt can use these routes to better understand Zag JS. Simply point your tool to any of the routes above based on your framework of choice. ## React An accordion is a vertically stacked set of interactive headings containing a title, content snippet, or thumbnail representing a section of content. ## Resources [Latest version: v1.35.2](https://www.npmjs.com/package/@zag-js/accordion) [Logic Visualizer](https://zag-visualizer.vercel.app/accordion) [Source Code](https://github.com/chakra-ui/zag/tree/main/packages/machines/accordion) **Features** - Full keyboard navigation - Supports single and multiple expanded items - Supports collapsible items - Supports horizontal and vertical orientation ## Installation Install the accordion package: ```bash 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: ```jsx 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: ```jsx 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 (
{data.map((item) => (

{item.content}
))}
) } ``` You may have noticed we wrapped each accordion trigger within an `h3`. This is recommended by the [WAI-ARIA](https://www.w3.org/TR/wai-aria-practices-1.1/#wai-aria-roles-states-and-properties) 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. ```jsx {2} const service = useMachine(accordion.machine, { multiple: true, }) ``` ### Setting the initial value Set `defaultValue` to define expanded items on first render. ```jsx // 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. ```tsx 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`. ```jsx {2} const service = useMachine(accordion.machine, { collapsible: true, }) ``` ### Listening for value changes When the accordion value changes, the `onValueChange` callback is invoked. ```jsx {2-5} 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. ```jsx 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. ```jsx {2} 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. ```jsx //...

Content
//... ``` You can also disable the entire accordion by setting `disabled` on the machine. ```jsx {2} 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. ```css [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. ```css [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 ```tsx import { Accordion } from "./your-accordion" function Demo() { return ( ) } ``` ### Implementation Use the `splitProps` utility to separate the machine's props from the component's props. ```tsx 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 { 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 (
{localProps.items.map((item) => (

{item.content}
))}
) } ``` ## Methods and Properties The accordion's `api` exposes the following methods and properties: ### Machine Context The accordion machine exposes the following context properties: **`ids`** Type: `Partial<{ root: string; item: (value: string) => string; itemContent: (value: string) => string; itemTrigger: (value: string) => string; }>` Description: The ids of the elements in the accordion. Useful for composition. **`multiple`** Type: `boolean` Description: Whether multiple accordion items can be expanded at the same time. **`collapsible`** Type: `boolean` Description: Whether an accordion item can be closed after it has been expanded. **`value`** Type: `string[]` Description: The controlled value of the expanded accordion items. **`defaultValue`** Type: `string[]` Description: The initial value of the expanded accordion items. Use when you don't need to control the value of the accordion. **`disabled`** Type: `boolean` Description: Whether the accordion items are disabled **`onValueChange`** Type: `(details: ValueChangeDetails) => void` Description: The callback fired when the state of expanded/collapsed accordion items changes. **`onFocusChange`** Type: `(details: FocusChangeDetails) => void` Description: The callback fired when the focused accordion item changes. **`orientation`** Type: `"horizontal" | "vertical"` Description: The orientation of the accordion items. **`dir`** Type: `"ltr" | "rtl"` Description: The document's text/writing direction. **`id`** Type: `string` Description: The unique identifier of the machine. **`getRootNode`** Type: `() => ShadowRoot | Node | Document` Description: A root node to correctly resolve document in custom environments. E.x.: Iframes, Electron. ### Machine API The accordion `api` exposes the following methods: **`focusedValue`** Type: `string` Description: The value of the focused accordion item. **`value`** Type: `string[]` Description: The value of the accordion **`setValue`** Type: `(value: string[]) => void` Description: Sets the value of the accordion **`getItemState`** Type: `(props: ItemProps) => ItemState` Description: Returns 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-controls`**: **`data-orientation`**: The orientation of the item **`data-state`**: "open" | "closed" ## Accessibility ### Keyboard Interactions **`Space`** Description: When focus is on an trigger of a collapsed item, the item is expanded **`Enter`** Description: When focus is on an trigger of a collapsed section, expands the section. **`Tab`** Description: Moves focus to the next focusable element **`Shift + Tab`** Description: Moves focus to the previous focusable element **`ArrowDown`** Description: Moves focus to the next trigger **`ArrowUp`** Description: Moves focus to the previous trigger. **`Home`** Description: When focus is on an trigger, moves focus to the first trigger. **`End`** Description: When focus is on an trigger, moves focus to the last trigger. An angle slider is a circular dial that allows users to select an angle, typically in degrees, within a 360° range. It provides an intuitive way to control rotations or orientations, offering accessibility features. ## Resources [Latest version: v1.35.2](https://www.npmjs.com/package/@zag-js/angle-slider) [Logic Visualizer](https://zag-visualizer.vercel.app/angle-slider) [Source Code](https://github.com/chakra-ui/zag/tree/main/packages/machines/angle-slider) **Features** - Fully managed keyboard navigation - Supports touch or click on the track to update value - Supports right-to-left direction ## Installation Install the angle slider package: ```bash npm install @zag-js/angle-slider @zag-js/react # or yarn add @zag-js/angle-slider @zag-js/react ``` ## Anatomy To set up the angle slider 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 Import the angle-slider package: ```jsx import * as angleSlider from "@zag-js/angle-slider" ``` The angle slider 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: ```jsx import * as angleSlider from "@zag-js/angle-slider" import { normalizeProps, useMachine } from "@zag-js/react" export function AngleSlider() { const service = useMachine(angleSlider.machine, { id: "1" }) const api = angleSlider.connect(service, normalizeProps) return (
{[0, 45, 90, 135, 180, 225, 270, 315].map((value) => (
))}
{api.value} degrees
) } ``` ### Setting the initial value Set `defaultValue` to define the initial slider value. ```jsx {2} const service = useMachine(angleSlider.machine, { defaultValue: 45, }) ``` ### Controlled angle slider Use `value` and `onValueChange` to control the value externally. ```tsx import { useState } from "react" export function ControlledAngleSlider() { const [value, setValue] = useState(45) const service = useMachine(angleSlider.machine, { value, onValueChange(details) { setValue(details.value) }, }) return ( // ... ) } ``` ### Setting the value's granularity By default, `step` is `1`, so values move in whole-number increments. Set `step` to control granularity. For example, set `step` to `0.01` for two-decimal precision: ```jsx {2} const service = useMachine(angleSlider.machine, { step: 0.01, }) ``` ### Listening for changes When the angle slider value changes, the `onValueChange` and `onValueChangeEnd` callbacks are invoked. ```jsx {2-7} const service = useMachine(angleSlider.machine, { onValueChange(details) { console.log("value:", details.value) console.log("as degree:", details.valueAsDegree) }, onValueChangeEnd(details) { console.log("final value:", details.value) }, }) ``` ### Read-only mode Set `readOnly` to prevent updates while preserving focus and form semantics. ```jsx {2} const service = useMachine(angleSlider.machine, { readOnly: true, }) ``` ### Usage in forms To submit the value with a form: - Set `name` on the machine. - Render the hidden input from `api.getHiddenInputProps()`. ```jsx {2} const service = useMachine(angleSlider.machine, { name: "wind-direction", }) ``` ### Labeling the thumb for assistive tech Use `aria-label` or `aria-labelledby` when you need custom labeling. ```jsx {2} const service = useMachine(angleSlider.machine, { "aria-label": "Wind direction", }) ``` ### Using angle slider marks To show marks or ticks along the angle slider track, use the exposed `api.getMarkerProps()` method to position the angle slider marks at desired angles. ```jsx {7-11} //...
{[0, 45, 90, 135, 180, 225, 270, 315].map((value) => (
))}
{api.value} degrees
//... ``` ## Styling guide Each part includes a `data-part` attribute you can target in CSS. ### Disabled State When the angle slider is disabled, the `data-disabled` attribute is added to the root, label, control, thumb and marker. ```css [data-part="root"][data-disabled] { /* styles for root disabled state */ } [data-part="label"][data-disabled] { /* styles for label disabled state */ } [data-part="control"][data-disabled] { /* styles for control disabled state */ } [data-part="thumb"][data-disabled] { /* styles for thumb disabled state */ } [data-part="range"][data-disabled] { /* styles for thumb disabled state */ } ``` ### Invalid State When the slider is invalid, the `data-invalid` attribute is added to the root, track, range, label, and thumb parts. ```css [data-part="root"][data-invalid] { /* styles for root invalid state */ } [data-part="label"][data-invalid] { /* styles for label invalid state */ } [data-part="control"][data-invalid] { /* styles for control invalid state */ } [data-part="valueText"][data-invalid] { /* styles for output invalid state */ } [data-part="thumb"][data-invalid] { /* styles for thumb invalid state */ } [data-part="marker"][data-invalid] { /* styles for marker invalid state */ } ``` ### Styling the markers ```css [data-part="marker"][data-state="(at|under|over)-value"] { /* styles for when the value exceeds the marker's value */ } ``` ## Methods and Properties ### Machine Context The angle slider machine exposes the following context properties: **`ids`** Type: `Partial<{ root: string; thumb: string; hiddenInput: string; control: string; valueText: string; label: string; }>` Description: The ids of the elements in the machine. Useful for composition. **`step`** Type: `number` Description: The step value for the slider. **`value`** Type: `number` Description: The value of the slider. **`defaultValue`** Type: `number` Description: The initial value of the slider. Use when you don't need to control the value of the slider. **`onValueChange`** Type: `(details: ValueChangeDetails) => void` Description: The callback function for when the value changes. **`onValueChangeEnd`** Type: `(details: ValueChangeDetails) => void` Description: The callback function for when the value changes ends. **`disabled`** Type: `boolean` Description: Whether the slider is disabled. **`readOnly`** Type: `boolean` Description: Whether the slider is read-only. **`invalid`** Type: `boolean` Description: Whether the slider is invalid. **`name`** Type: `string` Description: The name of the slider. Useful for form submission. **`aria-label`** Type: `string` Description: The accessible label for the slider thumb. **`aria-labelledby`** Type: `string` Description: The id of the element that labels the slider thumb. **`dir`** Type: `"ltr" | "rtl"` Description: The document's text/writing direction. **`id`** Type: `string` Description: The unique identifier of the machine. **`getRootNode`** Type: `() => ShadowRoot | Node | Document` Description: A root node to correctly resolve document in custom environments. E.x.: Iframes, Electron. ### Machine API The angle slider `api` exposes the following methods: **`value`** Type: `number` Description: The current value of the angle slider **`valueAsDegree`** Type: `string` Description: The current value as a degree string **`setValue`** Type: `(value: number) => void` Description: Sets the value of the angle slider **`dragging`** Type: `boolean` Description: Whether the slider is being dragged. ### Data Attributes **`Root`** **`data-scope`**: angle-slider **`data-part`**: root **`data-disabled`**: Present when disabled **`data-invalid`**: Present when invalid **`data-readonly`**: Present when read-only **`Label`** **`data-scope`**: angle-slider **`data-part`**: label **`data-disabled`**: Present when disabled **`data-invalid`**: Present when invalid **`data-readonly`**: Present when read-only **`Control`** **`data-scope`**: angle-slider **`data-part`**: control **`data-disabled`**: Present when disabled **`data-invalid`**: Present when invalid **`data-readonly`**: Present when read-only **`Thumb`** **`data-scope`**: angle-slider **`data-part`**: thumb **`data-disabled`**: Present when disabled **`data-invalid`**: Present when invalid **`data-readonly`**: Present when read-only **`Marker`** **`data-scope`**: angle-slider **`data-part`**: marker **`data-value`**: The value of the item **`data-state`**: **`data-disabled`**: Present when disabled ### CSS Variables ### Keyboard Interactions **`ArrowRight`** Description: Increments the angle slider based on defined step **`ArrowLeft`** Description: Decrements the angle slider based on defined step **`ArrowUp`** Description: Decreases the value by the step amount. **`ArrowDown`** Description: Increases the value by the step amount. **`Shift + ArrowUp`** Description: Decreases the value by a larger step **`Shift + ArrowDown`** Description: Increases the value by a larger step **`Home`** Description: Sets the value to 0 degrees. **`End`** Description: Sets the value to 360 degrees. An avatar represents a user profile picture. It displays an image or fallback content in a container. Avatar supports fallback text or elements when the image fails to load or when no image is provided. ## Resources [Latest version: v1.35.2](https://www.npmjs.com/package/@zag-js/avatar) [Logic Visualizer](https://zag-visualizer.vercel.app/avatar) [Source Code](https://github.com/chakra-ui/zag/tree/main/packages/machines/avatar) ## Installation Install the avatar package: ```bash npm install @zag-js/avatar @zag-js/react # or yarn add @zag-js/avatar @zag-js/react ``` ## Anatomy Check the avatar anatomy and part names. > Each part includes a `data-part` attribute to help identify them in the DOM. ## Usage Import the avatar package: ```jsx import * as avatar from "@zag-js/avatar" ``` The avatar 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: ```jsx import * as avatar from "@zag-js/avatar" import { useMachine, normalizeProps } from "@zag-js/react" function Avatar() { const service = useMachine(avatar.machine, { id: "1" }) const api = avatar.connect(service, normalizeProps) return (
PA PA
) } ``` ### Listening for loading status changes When the image loads or fails, `onStatusChange` is invoked. ```jsx {2} const service = useMachine(avatar.machine, { onStatusChange(details) { // details => { status: "error" | "loaded" } }, }) ``` ### Updating the image source programmatically Use `api.setSrc` when the image source changes after mount. ```jsx api.setSrc(nextSrc) ``` ## Styling guide Each avatar part includes a `data-part` attribute you can target in CSS. ```css [data-scope="avatar"][data-part="root"] { /* Styles for the root part */ } [data-scope="avatar"][data-part="image"] { /* Styles for the image part */ } [data-scope="avatar"][data-part="fallback"] { /* Styles for the fallback part */ } ``` ## Creating a component Create your avatar component by abstracting the machine into your own component. ### Usage ```tsx import { Avatar } from "./your-avatar" function Demo() { return ( ) } ``` ### Implementation Use the `splitProps` utility to separate the machine's props from the component's props. ```tsx import * as avatar from "@zag-js/avatar" import { useMachine, normalizeProps } from "@zag-js/react" export interface AvatarProps extends Omit { /** * The src of the avatar image */ src?: string /** * The srcSet of the avatar image */ srcSet?: string /** * The name of the avatar */ name: string } function Avatar(props: AvatarProps) { const [machineProps, localProps] = avatar.splitProps(props) const service = useMachine(avatar.machine, { id: useId(), ...machineProps, }) const api = avatar.connect(service, normalizeProps) return (
{getInitials(localProps.name)} PA
) } function getInitials(name: string) { return name .split(" ") .map((word) => word[0]) .join("") } ``` ## Methods and Properties ### Machine Context The avatar machine exposes the following context properties: **`onStatusChange`** Type: `(details: StatusChangeDetails) => void` Description: Functional called when the image loading status changes. **`ids`** Type: `Partial<{ root: string; image: string; fallback: string; }>` Description: The ids of the elements in the avatar. Useful for composition. **`id`** Type: `string` Description: The unique identifier of the machine. **`getRootNode`** Type: `() => ShadowRoot | Node | Document` Description: A root node to correctly resolve document in custom environments. E.x.: Iframes, Electron. **`dir`** Type: `"ltr" | "rtl"` Description: The document's text/writing direction. ### Machine API The avatar `api` exposes the following methods: **`loaded`** Type: `boolean` Description: Whether the image is loaded. **`setSrc`** Type: `(src: string) => void` Description: Function to set new src. **`setLoaded`** Type: `VoidFunction` Description: Function to set loaded state. **`setError`** Type: `VoidFunction` Description: Function to set error state. ### Data Attributes **`Image`** **`data-scope`**: avatar **`data-part`**: image **`data-state`**: "visible" | "hidden" **`Fallback`** **`data-scope`**: avatar **`data-part`**: fallback **`data-state`**: "hidden" | "visible" A carousel component that leverages native CSS Scroll Snap for smooth, performant scrolling between slides. ## Resources [Latest version: v1.35.2](https://www.npmjs.com/package/@zag-js/carousel) [Logic Visualizer](https://zag-visualizer.vercel.app/carousel) [Source Code](https://github.com/chakra-ui/zag/tree/main/packages/machines/carousel) **Features** - Uses native CSS Scroll Snap - Supports horizontal and vertical orientations - Supports slide alignment (`start`, `center`, `end`) - Supports showing multiple slides at a time - Supports looping and auto-playing - Supports custom spacing between slides ## Installation Install the carousel package: ```bash npm install @zag-js/carousel @zag-js/react # or yarn add @zag-js/carousel @zag-js/react ``` ## Anatomy Check the carousel anatomy and part names. > Each part includes a `data-part` attribute to help identify them in the DOM. ## Usage Import the carousel package: ```jsx import * as carousel from "@zag-js/carousel" ``` The carousel 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: > **Note:** The carousel requires that you provide a `slideCount` property in > the machine context. This is the total number of slides. ```jsx import * as carousel from "@zag-js/carousel" import { normalizeProps, useMachine } from "@zag-js/react" const items = [ "https://tinyurl.com/5b6ka8jd", "https://tinyurl.com/7rmccdn5", "https://tinyurl.com/59jxz9uu", ] export function Carousel() { const service = useMachine(carousel.machine, { id: "1", slideCount: items.length, }) const api = carousel.connect(service, normalizeProps) return (
{items.map((image, index) => (
))}
{api.pageSnapPoints.map((_, index) => (
) } ``` ### Vertical orientation Set `orientation` to `vertical` to render a vertical carousel. ```jsx {2} const service = useMachine(carousel.machine, { orientation: "vertical", }) ``` ### Setting the initial slide Set `defaultPage` to define the initial page. > The `defaultPage` corresponds to the scroll snap position index based on the > layout. It does not necessarily correspond to the index of the slide in the > carousel. ```jsx {2} const service = useMachine(carousel.machine, { defaultPage: 2, }) ``` ### Controlling the current page Use `page` and `onPageChange` for controlled navigation. ```jsx const service = useMachine(carousel.machine, { slideCount: 8, page, onPageChange(details) { setPage(details.page) }, }) ``` ### Setting slides per page Set `slidesPerPage` to control how many slides are visible per page. ```jsx {2} const service = useMachine(carousel.machine, { slidesPerPage: 2, }) ``` ### Setting slides per move Set `slidesPerMove` to control how many slides advance on next/previous. ```jsx {2} const service = useMachine(carousel.machine, { slidesPerMove: 2, }) ``` **Considerations** - If the value is `auto`, the carousel will move the number of slides equal to the number of slides per page. - Ensure the `slidesPerMove` is less than or equal to the `slidesPerPage` to avoid skipping slides. - On touch devices, `slidesPerMove` is not enforced during active swiping. The browser's native scrolling and CSS Scroll Snap determine slide movement for optimal performance and UX. ### Looping pages Set `loop` to `true` to wrap around from last to first page. ```jsx {2} const service = useMachine(carousel.machine, { loop: true, }) ``` ### Setting the gap between slides Set `spacing` to control the gap between slides. ```jsx {2} const service = useMachine(carousel.machine, { spacing: "16px", }) ``` ### Setting viewport padding Set `padding` to keep neighboring slides partially visible. ```jsx {2} const service = useMachine(carousel.machine, { padding: "16px", }) ``` ### Variable-width slides Set `autoSize` to `true` when slides have different widths. ```jsx {2} const service = useMachine(carousel.machine, { autoSize: true, }) ``` ### Listening for page changes When the carousel page changes, the `onPageChange` callback is invoked. ```jsx {2-5} const service = useMachine(carousel.machine, { onPageChange(details) { // details => { page: number } console.log("selected page:", details.page) }, }) ``` ### Listening for drag and autoplay status Use status callbacks to react to dragging and autoplay lifecycle changes. ```jsx const service = useMachine(carousel.machine, { onDragStatusChange(details) { console.log(details.type, details.isDragging) }, onAutoplayStatusChange(details) { console.log(details.type, details.isPlaying) }, }) ``` ### Dragging the carousel Set `allowMouseDrag` to `true` to drag the carousel with a mouse. ```jsx {2} const service = useMachine(carousel.machine, { allowMouseDrag: true, }) ``` ### Autoplaying the carousel Set `autoplay` to `true` to start automatic slide movement. ```jsx {2} const service = useMachine(carousel.machine, { autoplay: true, }) ``` Alternatively, you can configure the autoplay interval by setting the `delay` property. ```jsx {2} const service = useMachine(carousel.machine, { autoplay: { delay: 2000 }, }) ``` ### Customizing accessibility messages Use `translations` to customize localized trigger, item, and progress text. ```jsx const service = useMachine(carousel.machine, { slideCount: 5, translations: { nextTrigger: "Next slide", prevTrigger: "Previous slide", indicator: (index) => `Go to slide ${index + 1}`, item: (index, count) => `Slide ${index + 1} of ${count}`, autoplayStart: "Start autoplay", autoplayStop: "Stop autoplay", progressText: ({ page, totalPages }) => `Page ${page + 1} of ${totalPages}`, }, }) ``` ## Styling guide Each part includes a `data-part` attribute you can target in CSS. ```css [data-part="root"] { /* styles for the root part */ } [data-part="item-group"] { /* styles for the item-group part */ } [data-part="item"] { /* styles for the root part */ } [data-part="control"] { /* styles for the control part */ } [data-part="next-trigger"] { /* styles for the next-trigger part */ } [data-part="prev-trigger"] { /* styles for the prev-trigger part */ } [data-part="indicator-group"] { /* styles for the indicator-group part */ } [data-part="indicator"] { /* styles for the indicator part */ } [data-part="autoplay-trigger"] { /* styles for the autoplay-trigger part */ } ``` ### Active state When a carousel's indicator is active, a `data-current` attribute is set on the indicator. ```css [data-part="indicator"][data-current] { /* styles for the indicator's active state */ } ``` ## Methods and Properties The carousel's `api` exposes the following methods and properties: ### Machine Context The carousel machine exposes the following context properties: **`ids`** Type: `Partial<{ root: string; item: (index: number) => string; itemGroup: string; nextTrigger: string; prevTrigger: string; indicatorGroup: string; indicator: (index: number) => string; }>` Description: The ids of the elements in the carousel. Useful for composition. **`translations`** Type: `IntlTranslations` Description: The localized messages to use. **`slidesPerPage`** Type: `number` Description: The number of slides to show at a time. **`autoSize`** Type: `boolean` Description: Whether to enable variable width slides. **`slidesPerMove`** Type: `number | "auto"` Description: The number of slides to scroll at a time. When set to `auto`, the number of slides to scroll is determined by the `slidesPerPage` property. **`autoplay`** Type: `boolean | { delay: number; }` Description: Whether to scroll automatically. The default delay is 4000ms. **`allowMouseDrag`** Type: `boolean` Description: Whether to allow scrolling via dragging with mouse **`loop`** Type: `boolean` Description: Whether the carousel should loop around. **`page`** Type: `number` Description: The controlled page of the carousel. **`defaultPage`** Type: `number` Description: The initial page to scroll to when rendered. Use when you don't need to control the page of the carousel. **`spacing`** Type: `string` Description: The amount of space between items. **`padding`** Type: `string` Description: Defines the extra space added around the scrollable area, enabling nearby items to remain partially in view. **`onPageChange`** Type: `(details: PageChangeDetails) => void` Description: Function called when the page changes. **`inViewThreshold`** Type: `number | number[]` Description: The threshold for determining if an item is in view. **`snapType`** Type: `"proximity" | "mandatory"` Description: The snap type of the item. **`slideCount`** Type: `number` Description: The total number of slides. Useful for SSR to render the initial ating the snap points. **`onDragStatusChange`** Type: `(details: DragStatusDetails) => void` Description: Function called when the drag status changes. **`onAutoplayStatusChange`** Type: `(details: AutoplayStatusDetails) => void` Description: Function called when the autoplay status changes. **`dir`** Type: `"ltr" | "rtl"` Description: The document's text/writing direction. **`id`** Type: `string` Description: The unique identifier of the machine. **`getRootNode`** Type: `() => ShadowRoot | Node | Document` Description: A root node to correctly resolve document in custom environments. E.x.: Iframes, Electron. **`orientation`** Type: `"horizontal" | "vertical"` Description: The orientation of the element. ### Machine API The carousel `api` exposes the following methods: **`page`** Type: `number` Description: The current index of the carousel **`pageSnapPoints`** Type: `number[]` Description: The current snap points of the carousel **`isPlaying`** Type: `boolean` Description: Whether the carousel is auto playing **`isDragging`** Type: `boolean` Description: Whether the carousel is being dragged. This only works when `draggable` is true. **`canScrollNext`** Type: `boolean` Description: Whether the carousel is can scroll to the next view **`canScrollPrev`** Type: `boolean` Description: Whether the carousel is can scroll to the previous view **`scrollToIndex`** Type: `(index: number, instant?: boolean) => void` Description: Function to scroll to a specific item index **`scrollTo`** Type: `(page: number, instant?: boolean) => void` Description: Function to scroll to a specific page **`scrollNext`** Type: `(instant?: boolean) => void` Description: Function to scroll to the next page **`scrollPrev`** Type: `(instant?: boolean) => void` Description: Function to scroll to the previous page **`getProgress`** Type: `() => number` Description: Returns the current scroll progress as a percentage **`getProgressText`** Type: `() => string` Description: Returns the progress text **`play`** Type: `VoidFunction` Description: Function to start/resume autoplay **`pause`** Type: `VoidFunction` Description: Function to pause autoplay **`isInView`** Type: `(index: number) => boolean` Description: Whether the item is in view **`refresh`** Type: `VoidFunction` Description: Function to re-compute the snap points and clamp the page ### Data Attributes **`Root`** **`data-scope`**: carousel **`data-part`**: root **`data-orientation`**: The orientation of the carousel **`ItemGroup`** **`data-scope`**: carousel **`data-part`**: item-group **`data-orientation`**: The orientation of the item **`data-dragging`**: Present when in the dragging state **`Item`** **`data-scope`**: carousel **`data-part`**: item **`data-index`**: The index of the item **`data-inview`**: Present when in viewport **`data-orientation`**: The orientation of the item **`Control`** **`data-scope`**: carousel **`data-part`**: control **`data-orientation`**: The orientation of the control **`PrevTrigger`** **`data-scope`**: carousel **`data-part`**: prev-trigger **`data-orientation`**: The orientation of the prevtrigger **`NextTrigger`** **`data-scope`**: carousel **`data-part`**: next-trigger **`data-orientation`**: The orientation of the nexttrigger **`IndicatorGroup`** **`data-scope`**: carousel **`data-part`**: indicator-group **`data-orientation`**: The orientation of the indicatorgroup **`Indicator`** **`data-scope`**: carousel **`data-part`**: indicator **`data-orientation`**: The orientation of the indicator **`data-index`**: The index of the item **`data-readonly`**: Present when read-only **`data-current`**: Present when current **`AutoplayTrigger`** **`data-scope`**: carousel **`data-part`**: autoplay-trigger **`data-orientation`**: The orientation of the autoplaytrigger **`data-pressed`**: Present when pressed ### CSS Variables A cascade select component allows users to select from hierarchical data through multiple linked levels of dropdown menus. ## Resources [Latest version: v1.35.2](https://www.npmjs.com/package/@zag-js/cascade-select) [Logic Visualizer](https://zag-visualizer.vercel.app/cascade-select) [Source Code](https://github.com/chakra-ui/zag/tree/main/packages/machines/cascade-select) **Features** - Supports hierarchical data with unlimited depth levels - Full keyboard navigation across all levels with arrow keys - Supports single and multiple selections - Supports both click and hover triggering modes - Supports looping keyboard navigation - Built-in accessibility with ARIA roles and keyboard interactions - Supports disabled items and read-only state - Form integration with hidden input element - Supports right-to-left direction ## Installation Install the cascade select package: ```bash npm install @zag-js/cascade-select @zag-js/react # or yarn add @zag-js/cascade-select @zag-js/react ``` ## Anatomy Check the cascade select anatomy and part names. > Each part includes a `data-part` attribute to help identify them in the DOM. ## Usage Import the cascade select package: ```jsx import * as cascadeSelect from "@zag-js/cascade-select" ``` These are the key exports: - `machine` - State machine logic. - `connect` - Maps machine state to JSX props and event handlers. - `collection` - Creates a tree collection from your hierarchical data. ### Create the collection Use the `collection` function to create a tree collection from your hierarchical data. Pass a `rootNode` along with functions to extract each node's value, string label, and children. ```ts import * as cascadeSelect from "@zag-js/cascade-select" interface Node { label: string value: string children?: Node[] } const collection = cascadeSelect.collection({ nodeToValue: (node) => node.value, nodeToString: (node) => node.label, nodeToChildren: (node) => node.children, rootNode: { label: "ROOT", value: "ROOT", children: [ { label: "North America", value: "north-america", children: [ { label: "United States", value: "us", children: [ { label: "New York", value: "ny" }, { label: "California", value: "ca" }, ], }, { label: "Canada", value: "canada" }, ], }, { label: "Africa", value: "africa", children: [ { label: "Nigeria", value: "ng" }, { label: "Kenya", value: "ke" }, ], }, ], }, }) ``` ### Create the cascade select > Pass a unique `id` to `useMachine` so generated element ids stay predictable. Then use the framework integration helpers: ```tsx import * as cascadeSelect from "@zag-js/cascade-select" import { normalizeProps, Portal, useMachine } from "@zag-js/react" import { JSX, useId } from "react" // 1. Create the collection (see above) const collection = cascadeSelect.collection({ // ... }) // 2. Create the recursive tree node interface TreeNodeProps { node: Node indexPath?: number[] value?: string[] api: cascadeSelect.Api } const TreeNode = (props: TreeNodeProps): JSX.Element => { const { node, indexPath = [], value = [], api } = props const nodeProps = { indexPath, value, item: node } const nodeState = api.getItemState(nodeProps) const children = collection.getNodeChildren(node) return ( <>
    {children.map((item, index) => { const itemProps = { indexPath: [...indexPath, index], value: [...value, collection.getNodeValue(item)], item, } const itemState = api.getItemState(itemProps) return (
  • {item.label} {itemState.hasChildren && }
  • ) })}
{nodeState.highlightedChild && collection.isBranchNode(nodeState.highlightedChild) && ( )} ) } // 3. Create the cascade select export function CascadeSelect() { const service = useMachine(cascadeSelect.machine, { id: useId(), collection, }) const api = cascadeSelect.connect(service, normalizeProps) return (
) } ``` ### Setting the initial value Use the `defaultValue` property to set the initial value of the cascade select. > The `value` property must be an array of string paths. Each path is an array > of values from the root to the selected leaf item. ```jsx {4} const service = useMachine(cascadeSelect.machine, { id: useId(), collection, defaultValue: [["north-america", "us", "ny"]], }) ``` ### Controlled selection Use `value` and `onValueChange` for controlled selection state. ```jsx const service = useMachine(cascadeSelect.machine, { id: useId(), collection, value, onValueChange(details) { setValue(details.value) }, }) ``` ### Selecting multiple values To allow selecting multiple values, set the `multiple` property to `true`. ```jsx {4} const service = useMachine(cascadeSelect.machine, { id: useId(), collection, multiple: true, }) ``` ### Hover triggering By default, items are highlighted when clicked. To highlight items on hover instead (like a traditional cascading menu), set the `highlightTrigger` property to `"hover"`. ```jsx {4} const service = useMachine(cascadeSelect.machine, { id: useId(), collection, highlightTrigger: "hover", }) ``` ### Allowing parent selection By default, only leaf nodes (items without children) can be selected. To allow parent (branch) nodes to also be selectable, set `allowParentSelection` to `true`. ```jsx {4} const service = useMachine(cascadeSelect.machine, { id: useId(), collection, allowParentSelection: true, }) ``` ### Close on select By default, the menu closes when you select an item. Set `closeOnSelect` to `false` to keep it open. ```jsx {4} const service = useMachine(cascadeSelect.machine, { id: useId(), collection, closeOnSelect: false, }) ``` ### Looping the keyboard navigation By default, arrow-key navigation stops at the first and last items. Set `loopFocus: true` to loop back around. ```jsx {4} const service = useMachine(cascadeSelect.machine, { id: useId(), collection, loopFocus: true, }) ``` ### Listening for highlight changes When an item is highlighted with the pointer or keyboard, use the `onHighlightChange` to listen for the change and do something with it. ```jsx {3-6} const service = useMachine(cascadeSelect.machine, { id: useId(), onHighlightChange(details) { // details => { highlightedValue: string[], highlightedItems: Item[] } console.log(details) }, }) ``` ### Setting the initial highlighted path Use `defaultHighlightedValue` to set the initially highlighted path. ```jsx const service = useMachine(cascadeSelect.machine, { id: useId(), collection, defaultHighlightedValue: ["north-america", "us"], }) ``` ### Controlled highlighted path Use `highlightedValue` and `onHighlightChange` to control highlighting externally. ```jsx const service = useMachine(cascadeSelect.machine, { id: useId(), collection, highlightedValue, onHighlightChange(details) { setHighlightedValue(details.highlightedValue) }, }) ``` ### Listening for selection changes When an item is selected, use the `onValueChange` property to listen for the change and do something with it. ```jsx {4-7} const service = useMachine(cascadeSelect.machine, { id: useId(), collection, onValueChange(details) { // details => { value: string[][], items: Item[][] } console.log(details) }, }) ``` ### Listening for open and close events Use `onOpenChange` to listen for open and close events. ```jsx {4-7} const service = useMachine(cascadeSelect.machine, { id: useId(), collection, onOpenChange(details) { // details => { open: boolean, value: string[][] } console.log(details) }, }) ``` ### Controlling open state Use `open` and `onOpenChange` for controlled open state, or `defaultOpen` for an uncontrolled initial state. ```jsx const service = useMachine(cascadeSelect.machine, { id: useId(), collection, open, onOpenChange({ open }) { setOpen(open) }, }) ``` ```jsx const service = useMachine(cascadeSelect.machine, { id: useId(), collection, defaultOpen: true, }) ``` ### Positioning submenu panels Use `positioning` to configure placement and collision behavior. ```jsx const service = useMachine(cascadeSelect.machine, { id: useId(), collection, positioning: { placement: "right-start", gutter: 4, }, }) ``` ### Custom scroll behavior Use `scrollToIndexFn` to customize how each level scrolls highlighted items into view. ```jsx const service = useMachine(cascadeSelect.machine, { id: useId(), collection, scrollToIndexFn(details) { // details => { index, depth, immediate? } customScroll(details) }, }) ``` ### Customizing the trigger label Use `formatValue` to control how selected paths are rendered in the trigger. ```jsx const service = useMachine(cascadeSelect.machine, { id: useId(), collection, formatValue(selectedItems) { return selectedItems .map((path) => path.map((item) => item.label).join(" / ")) .join(", ") }, }) ``` ### Usage within a form To use cascade select in a form, pass `name`. A hidden input is rendered with `getHiddenInputProps()`. ```jsx {4} const service = useMachine(cascadeSelect.machine, { id: useId(), collection, name: "location", }) // In your JSX ``` If the hidden input belongs to a different form element, also set `form`. ```jsx const service = useMachine(cascadeSelect.machine, { id: useId(), collection, name: "location", form: "checkout-form", }) ``` ### Validation and read-only state Use `readOnly`, `required`, and `invalid` to control interaction and form state. ```jsx const service = useMachine(cascadeSelect.machine, { id: useId(), collection, readOnly: true, required: true, invalid: false, }) ``` ## Styling guide Each cascade select part includes a `data-part` attribute you can target in CSS. ### Open and closed state When the cascade select is open, the trigger and content is given a `data-state` attribute. ```css [data-part="trigger"][data-state="open|closed"] { /* styles for open or closed state */ } [data-part="content"][data-state="open|closed"] { /* styles for open or closed state */ } ``` ### Selected state Items are given a `data-state` attribute, indicating whether they are selected. ```css [data-part="item"][data-state="checked|unchecked"] { /* styles for selected or unselected state */ } ``` ### Highlighted state When an item is highlighted, via keyboard navigation or pointer, it is given a `data-highlighted` attribute. ```css [data-part="item"][data-highlighted] { /* styles for highlighted state */ } ``` ### Branch items When an item has children (is a branch node), it is given a `data-has-children` attribute. ```css [data-part="item"][data-has-children] { /* styles for items with children (branch nodes) */ } ``` ### Invalid state When the cascade select is invalid, the label and trigger is given a `data-invalid` attribute. ```css [data-part="label"][data-invalid] { /* styles for invalid state */ } [data-part="trigger"][data-invalid] { /* styles for invalid state */ } ``` ### Disabled state When the cascade select is disabled, the trigger and label is given a `data-disabled` attribute. ```css [data-part="trigger"][data-disabled] { /* styles for disabled state */ } [data-part="label"][data-disabled] { /* styles for disabled label state */ } [data-part="item"][data-disabled] { /* styles for disabled item state */ } ``` ### Empty state When no option is selected, the trigger is given a `data-placeholder-shown` attribute. ```css [data-part="trigger"][data-placeholder-shown] { /* styles for empty state */ } ``` ## Methods and Properties ### Machine Context The cascade select machine exposes the following context properties: **`collection`** Type: `TreeCollection` Description: The tree collection data **`ids`** Type: `Partial<{ root: string; label: string; control: string; trigger: string; indicator: string; clearTrigger: string; positioner: string; content: string; hiddenInput: string; list(valuePath: string): string; item(valuePath: string): string; }>` Description: The ids of the cascade-select elements. Useful for composition. **`name`** Type: `string` Description: The name attribute of the underlying input element **`form`** Type: `string` Description: The form attribute of the underlying input element **`value`** Type: `string[][]` Description: The controlled value of the cascade-select **`defaultValue`** Type: `string[][]` Description: The initial value of the cascade-select when rendered. Use when you don't need to control the value. **`highlightedValue`** Type: `string[]` Description: The controlled highlighted value of the cascade-select **`defaultHighlightedValue`** Type: `string[]` Description: The initial highlighted value of the cascade-select when rendered. **`multiple`** Type: `boolean` Description: Whether to allow multiple selections **`open`** Type: `boolean` Description: The controlled open state of the cascade-select **`defaultOpen`** Type: `boolean` Description: The initial open state of the cascade-select when rendered. Use when you don't need to control the open state. **`highlightTrigger`** Type: `"click" | "hover"` Description: What triggers highlighting of items **`closeOnSelect`** Type: `boolean` Description: Whether the cascade-select should close when an item is selected **`loopFocus`** Type: `boolean` Description: Whether the cascade-select should loop focus when navigating with keyboard **`disabled`** Type: `boolean` Description: Whether the cascade-select is disabled **`readOnly`** Type: `boolean` Description: Whether the cascade-select is read-only **`required`** Type: `boolean` Description: Whether the cascade-select is required **`invalid`** Type: `boolean` Description: Whether the cascade-select is invalid **`positioning`** Type: `PositioningOptions` Description: The positioning options for the cascade-select content **`scrollToIndexFn`** Type: `(details: ScrollToIndexDetails) => void` Description: Function to scroll to a specific index in a list **`formatValue`** Type: `(selectedItems: T[][]) => string` Description: Function to format the display value **`onValueChange`** Type: `(details: ValueChangeDetails) => void` Description: Called when the value changes **`onHighlightChange`** Type: `(details: HighlightChangeDetails) => void` Description: Called when the highlighted value changes **`onOpenChange`** Type: `(details: OpenChangeDetails) => void` Description: Called when the open state changes **`allowParentSelection`** Type: `boolean` Description: Whether parent (branch) items can be selectable **`dir`** Type: `"ltr" | "rtl"` Description: The document's text/writing direction. **`id`** Type: `string` Description: The unique identifier of the machine. **`getRootNode`** Type: `() => ShadowRoot | Node | Document` Description: A root node to correctly resolve document in custom environments. E.x.: Iframes, Electron. **`onPointerDownOutside`** Type: `(event: PointerDownOutsideEvent) => void` Description: Function called when the pointer is pressed down outside the component **`onFocusOutside`** Type: `(event: FocusOutsideEvent) => void` Description: Function called when the focus is moved outside the component **`onInteractOutside`** Type: `(event: InteractOutsideEvent) => void` Description: Function called when an interaction happens outside the component ### Machine API The cascade select `api` exposes the following methods: **`collection`** Type: `TreeCollection` Description: The tree collection data **`open`** Type: `boolean` Description: Whether the cascade-select is open **`focused`** Type: `boolean` Description: Whether the cascade-select is focused **`multiple`** Type: `boolean` Description: Whether the cascade-select allows multiple selections **`disabled`** Type: `boolean` Description: Whether the cascade-select is disabled **`highlightedValue`** Type: `string[]` Description: The value of the highlighted item **`highlightedItems`** Type: `V[]` Description: The items along the highlighted path **`selectedItems`** Type: `V[][]` Description: The selected items **`hasSelectedItems`** Type: `boolean` Description: Whether there's a selected option **`empty`** Type: `boolean` Description: Whether the cascade-select value is empty **`value`** Type: `string[][]` Description: The current value of the cascade-select **`valueAsString`** Type: `string` Description: The current value as text **`focus`** Type: `() => void` Description: Function to focus on the select input **`reposition`** Type: `(options?: Partial) => void` Description: Function to set the positioning options of the cascade-select **`setOpen`** Type: `(open: boolean) => void` Description: Function to open the cascade-select **`setHighlightValue`** Type: `(value: string | string[]) => void` Description: Function to set the highlighted value (path or single value to find) **`clearHighlightValue`** Type: `() => void` Description: Function to clear the highlighted value **`selectValue`** Type: `(value: string[]) => void` Description: Function to select a value **`setValue`** Type: `(value: string[][]) => void` Description: Function to set the value **`clearValue`** Type: `(value?: string[]) => void` Description: Function to clear the value **`getItemState`** Type: `(props: ItemProps) => ItemState` Description: Returns the state of a cascade-select item ### Data Attributes **`Root`** **`data-scope`**: cascade-select **`data-part`**: root **`data-disabled`**: Present when disabled **`data-readonly`**: Present when read-only **`data-invalid`**: Present when invalid **`data-state`**: "open" | "closed" **`Label`** **`data-scope`**: cascade-select **`data-part`**: label **`data-disabled`**: Present when disabled **`data-readonly`**: Present when read-only **`data-invalid`**: Present when invalid **`Control`** **`data-scope`**: cascade-select **`data-part`**: control **`data-disabled`**: Present when disabled **`data-focus`**: Present when focused **`data-readonly`**: Present when read-only **`data-invalid`**: Present when invalid **`data-state`**: "open" | "closed" **`Trigger`** **`data-scope`**: cascade-select **`data-part`**: trigger **`data-state`**: "open" | "closed" **`data-disabled`**: Present when disabled **`data-readonly`**: Present when read-only **`data-invalid`**: Present when invalid **`data-focus`**: Present when focused **`data-placement`**: The placement of the trigger **`data-placeholder-shown`**: Present when placeholder is shown **`ClearTrigger`** **`data-scope`**: cascade-select **`data-part`**: clear-trigger **`data-disabled`**: Present when disabled **`data-readonly`**: Present when read-only **`data-invalid`**: Present when invalid **`Content`** **`data-scope`**: cascade-select **`data-part`**: content **`data-activedescendant`**: The id the active descendant of the content **`data-state`**: "open" | "closed" **`List`** **`data-scope`**: cascade-select **`data-part`**: list **`data-depth`**: The depth of the item **`Indicator`** **`data-scope`**: cascade-select **`data-part`**: indicator **`data-state`**: "open" | "closed" **`data-disabled`**: Present when disabled **`data-readonly`**: Present when read-only **`data-invalid`**: Present when invalid **`Item`** **`data-scope`**: cascade-select **`data-part`**: item **`data-value`**: The value of the item **`data-disabled`**: Present when disabled **`data-highlighted`**: Present when highlighted **`data-selected`**: Present when selected **`data-depth`**: The depth of the item **`data-state`**: "checked" | "unchecked" **`data-type`**: The type of the item **`data-index-path`**: **`ItemText`** **`data-scope`**: cascade-select **`data-part`**: item-text **`data-value`**: The value of the item **`data-highlighted`**: Present when highlighted **`data-state`**: "checked" | "unchecked" **`data-disabled`**: Present when disabled **`ItemIndicator`** **`data-scope`**: cascade-select **`data-part`**: item-indicator **`data-value`**: The value of the item **`data-highlighted`**: Present when highlighted **`data-type`**: The type of the item **`data-state`**: "checked" | "unchecked" **`ValueText`** **`data-scope`**: cascade-select **`data-part`**: value-text **`data-disabled`**: Present when disabled **`data-invalid`**: Present when invalid **`data-focus`**: Present when focused ### CSS Variables ## Accessibility Adheres to the [ListBox WAI-ARIA design pattern](https://www.w3.org/WAI/ARIA/apg/patterns/listbox). ### Keyboard Interactions **`Space`** Description: When focus is on trigger, opens the cascade select and focuses the first item.
When focus is on the content, selects the highlighted item.
**`Enter`** Description: When focus is on trigger, opens the cascade select and focuses the first item.
When focus is on content, selects the highlighted item.
**`ArrowDown`** Description: When focus is on trigger, opens the cascade select.
When focus is on content, moves focus to the next item in the current level.
**`ArrowUp`** Description: When focus is on trigger, opens the cascade select and focuses the last item.
When focus is on content, moves focus to the previous item in the current level.
**`ArrowRight`** Description: When focus is on a branch item, expands the next level and moves focus into it. **`ArrowLeft`** Description: When focus is on a nested level, collapses it and moves focus back to the parent.
When focus is at the root level, closes the cascade select.
**`Home`** Description: Moves focus to the first item in the current level. **`End`** Description: Moves focus to the last item in the current level. **`Esc`** Description: Closes the cascade select and moves focus to trigger. A checkbox allows users to make a binary choice, i.e. a choice between one of two possible mutually exclusive options. ## Resources [Latest version: v1.35.2](https://www.npmjs.com/package/@zag-js/checkbox) [Logic Visualizer](https://zag-visualizer.vercel.app/checkbox) [Source Code](https://github.com/chakra-ui/zag/tree/main/packages/machines/checkbox) **Features** - Tri-state checkbox (`indeterminate` state) - Syncs with `disabled` state of fieldset - Syncs with form `reset` events - Can be toggled programmatically ## Installation Install the checkbox package: ```bash npm install @zag-js/checkbox @zag-js/react # or yarn add @zag-js/checkbox @zag-js/react ``` ## Anatomy Check the checkbox anatomy and part names. > Each part includes a `data-part` attribute to help identify them in the DOM. ## Usage Import the checkbox package: ```jsx import * as checkbox from "@zag-js/checkbox" ``` The checkbox package exports two key functions: - `machine` - State machine logic. - `connect` - Maps machine state to JSX props and event handlers. Then use the framework integration helpers: ```jsx import * as checkbox from "@zag-js/checkbox" import { useMachine, normalizeProps } from "@zag-js/react" import { useId } from "react" function Checkbox() { const service = useMachine(checkbox.machine, { id: useId() }) const api = checkbox.connect(service, normalizeProps) return (