# Introduction to Zag 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 # Getting Started Zag can be used within most JS frameworks like Vue, React 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 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. # What is a state machine? 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. ## Example of 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", }, }, }, }, }) ``` The `machine` gives you access to these key information: - `state`: State is a representation of the machine at a specific point in time and contains the following properties: - `value`: the current state value - `context`: the current context or data stored in the machine - `event`: the event object that triggered the transition of this current state - `matches(...)`: the function to check whether the machine is in a specific state. It's similar to `state.value === ` - `send`: A function to send events or signals to the machine. 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) { const active = state.matches("active") return { active, getButtonProps() { type: "button", role: "switch", "aria-checked": active, onClick() { send("CLICK") }, }, } } ``` 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. # Styling Zag's API is intentionally very low level and unstyled, giving you control over how you want to style them. Unlike Chakra UI, it provides no styles or UI, just behavior or logic, accessibility and helpful methods. Each machine provides an idea of the DOM structure and styling guide to show you how to style the states and parts. ## Styling a component part Every component comprises of multiple parts that can be styled. For example, the dialog component has the following parts: content, trigger, title, and backdrop. The `data-part` attribute can be used to select and style a component part. Here's what a sample HTML for the dialog looks like: ```html

Dialog Title

Dialog Description

``` You can style each part using the CSS attribute selector. ```css [data-part="backdrop"] { background-color: #000; opacity: 0.5; } [data-part="content"] { padding: 24px; border-radius: 6px; } ``` ## Styling a state When a component or its parts can have multiple states, we automatically attach `data-*` attributes that represents the specific state. For example, an accordion's trigger can have: - `data-disabled` — When the trigger is disabled. - `data-expanded` — When the trigger is expanded. You can style the accordion's trigger using the CSS attribute selector. ```css [data-part="trigger"][data-expanded] { background: red; } [data-part="trigger"][data-disabled] { opacity: 0.5; cursor: not-allowed; } ``` You'll see this pattern across every components within the library. > Zag was designed to encapsulate logic, accessibility and interactions, while > giving you full control over styling. # Composition ## Event composition Zag encourages the use of spread props to ensure we automatically attach all the event handlers and properties to elements. Sometimes, you may want to attach your own event handlers to the elements. To do that, import the `mergeProps` utility provided by zag for your framework. ```jsx // 1. import mergeProps utility import { useMachine, mergeProps } from "@zag-js/react" import * as hoverCard from "@zag-js/hover-card" export function Toggle() { const service = useMachine(hoverCard.machine, { id: "1", }) const api = hoverCard.connect(service) // 2. write your custom event handlers const handleHover = () => { // do something } // 3. merge the props const triggerProps = mergeProps(api.getTriggerProps(), { onPointerEnter: handleHover, }) return ( // 4. spread the new props {api.open ? "Open" : "Close"} ) } ``` ## Id composition Zag depends heavily on pure DOM queries to identify elements. This means every element part needs to have a unique id. Each time you initiate the machine with the `useMachine` hook, you'll need to ensure that you provide a unique id. In most cases, you can rely on the framework providing a unique id for each machine. ### React ```jsx import * as accordion from "@zag-js/accordion" import { useMachine, normalizeProps } from "@zag-js/framework" import { useId } from "react" function Component() { const service = useMachine(accordion.machine, { id: useId() }) const api = machine.connect(service, normalizeProps) // ... } ``` See [useId](https://react.dev/reference/react/useId). ### Solid ```jsx import * as accordion from "@zag-js/accordion" import { useMachine, normalizeProps } from "@zag-js/solid" import { createUniqueId } from "solid-js" function Component() { const service = useMachine(accordion.machine, { id: createUniqueId() }) const api = machine.connect(service, normalizeProps) // ... } ``` See [createUniqueId](https://docs.solidjs.com/reference/component-apis/create-unique-id). ### Vue ```html ``` See [useId](https://vuejs.org/api/composition-api-helpers#useid). ### Svelte ```html ``` See [$props.id](https://svelte.dev/docs/svelte/$props#$props.id()). Internally, Zag maps the unique id provided to each component parts needed for the widget to work. In some cases, you might want to compose different machines together in a single component. For example, you want to use the same trigger as a popover and tooltip trigger at the same time. To achieve this, you will need to pass custom `ids` to the machine's context. This will ensure that calling `document.getElementById(...)` within the tooltip and/or popover will return the same element. ```tsx {6,10} import * as tooltip from "@zag-js/tooltip" import * as popover from "@zag-js/popover" function Example() { const tooltipService = useMachine(tooltip.machine, { ids: { trigger: "id-1" }, }) const popoverService = useMachine(popover.machine, { ids: { trigger: "id-1" }, }) // ... } ``` In the example above, you will notice that the popover and tooltip trigger share the same id. That's how to compose machines together. ## Custom window environment Internally, we use DOM query methods like `document.querySelectorAll` and `document.getElementById` to locate elements within the machine. In custom environments like iframe, Shadow DOM, Electron, etc., the machine might not work as expected because `document` may not be available. To provide the correct reference to root node or document, you can pass `getRootNode` function it to the machine's context. > In shadow DOM, the root node can be derived from calling > `element.getRootNode()` method on any element. ```jsx {12,16,42} import * as accordion from "@zag-js/accordion" import { useMachine, normalizeProps } from "@zag-js/react" import Frame, { useFrame } from "react-frame-component" const data = [ { title: "Watercraft", content: "Sample accordion content" }, { title: "Automobiles", content: "Sample accordion content" }, { title: "Aircraft", content: "Sample accordion content" }, ] function Accordion({ id }) { const { document } = useFrame() const service = useMachine(accordion.machine, { id, getRootNode: () => document, }) const api = accordion.connect(service, normalizeProps) return (
{data.map((item, index) => (

{item.content}
))}
) } export default function App() { return (

ZagJs in Iframe

) } ``` # Collection The Collection class is designed to manage a collection of items, providing functionalities such as sorting, searching, getting next or previous items, converting items to values or strings, checking if an item is disabled, and more. > **Good to know**: This is used in the select and combobox components ## List Collection A list collection is a collection that is based on an array of items. It is created by passing an array of items to the constructor. ```ts import { ListCollection } from "@zag-js/collection" const collection = new ListCollection({ items: [ { label: "Apple", value: "apple" }, { label: "Banana", value: "banana" }, ], }) ``` ### Converting value to item You can convert a value to an item by passing the value to the `find` or `findMany` method. ```ts const item = collection.find("banana") console.log(item) // { label: "Banana", value: "banana" } const items = collection.findMany(["apple", "banana"]) console.log(items) // [{ label: "Apple", value: "apple" }, { label: "Banana", value: "banana" }] ``` ### Value Traversal You can get the next or previous item based on a value by passing the value to the `getNextValue` or `getPreviousValue` method. ```ts const nextValue = collection.getNextValue("apple") console.log(nextValue) // banana const previousItem = collection.getPreviousValue("banana") console.log(previousItem) // apple ``` Likewise, you can also get the first or last item by calling the `firstValue` or `lastValue` computed properties. ```ts console.log(collection.firstValue) // apple console.log(collection.lastValue) // banana ``` ### Check for value existence You can check if a value exists in the collection by calling the `has` method. ```ts const hasValue = collection.has("apple") console.log(hasValue) // true ``` ### Working with custom objects If you are working with custom objects, you can pass a function to the `itemToString` and `getItemValue` options to specify how to convert an item to a string and a value, respectively. ```ts import { ListCollection } from "@zag-js/collection" const collection = new ListCollection({ items: [ { id: 1, name: "apple" }, { id: 2, name: "banana" }, { id: 3, name: "cherry" }, ], itemToString: (item) => item.name, itemToValue: (item) => item.id, }) ``` To disable an item, you can pass a function to the `isItemDisabled` option. ```ts import { ListCollection } from "@zag-js/collection" const collection = new ListCollection({ items: [ { id: 1, name: "apple" }, { id: 2, name: "banana" }, { id: 3, name: "cherry" }, ], isItemDisabled: (item) => item.id === 2, }) ``` ### Reorder items You can reorder items by calling the `reorder` method and passing the starting index and the ending index of the item to be moved. ```ts const fromIndex = 1 // Banana const toIndex = 0 // Apple collection.reorder(fromIndex, toIndex) console.log(collection.items) // [{ label: "Banana", value: "banana" }, { label: "Apple", value: "apple" }] ``` # Programmatic Control In some cases, you may want to control the state or context values of a machine programmatically via its `props` or based on certain conditions. This is typically known as "controlling" the components. Zag provides a number of ways to control the state of a machine programmatically. ## Setting initial context All machines support setting the controlled and uncontrolled values for context properties. For example: - `defaultOpen` and `open` for controlling the open state of disclosure components - `defaultValue` and `value` for controlling the value of input machines For example, if you want an accordion to start with a specific selected value. Here's how to achieve that: ```tsx const service = useMachine(accordion.machine, { defaultValue: ["item-1"], }) ``` ## Controlled Usage You can pass the context value to the `useMachine` hook directly and provide the `onValueChange` callback to react to the changes. ```jsx const service = useMachine(accordion.machine, { value: props.value, onValueChange(details) { console.log(details) }, }) ``` ## Using exposed methods The `connect` method of the machines provide helpful methods (APIs) to change the machine state or update its context. > This approach is the recommended approach to programmatically update a > machine. Let's say we'd like to change the expanded accordion item in an accordion group. Here's how to do that: ```jsx function Accordion() { // 1. Bind the machine in your framework const service = useMachine(accordion.machine) // 2. Call the connect function const api = accordion.connect(service) // 3. Use exposed methods api.setValue("item-1") return (...) } ``` # LLMs.txt ## 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. # Frequently Asked Questions ## 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 difference 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](/overview/composition#event-composition). ## How can I get Zag working in a custom window environment? See the approach [here](/overview/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/... ``` # Migration Guide After years of refinement and iteration, we've cemented Zag to work seamlessly across major JavaScript frameworks. Now, we're taking things to the next level by focusing on: - **Performance**: Improving the runtime and rendering performance of every component - **Bundle Size**: Reducing the gross bundle size of each component + framework adapters We achieved this by **moving from an external store to native reactive primitives** provided by each framework. Our rigorous performance testing, which involved stress-testing with 10,000 instances of the same component, revealed roughly **1.5x - 4x** performance improvements across components. [View Breakdown](#performance) ## Changed The major changes are quite simple, and are listed below: ### useMachine `useMachine` now returns a `service` object instead of a tuple of `[state, send]`. This change is the same across all components. Using "find and replace" will help you migrate faster. **Before** ```tsx const [state, send] = useMachine(avatar.machine({ id: useId() })) ``` **After** ```tsx const service = useMachine(avatar.machine, { id: useId() }) ``` > Notice that `avatar.machine` is no longer a function, it is passed directly to > `useMachine`. #### Caveats Due to the switch from `.machine()` as a function to `.machine` as an object, the TS inference is limited for generic components like combobox and select. To help with this, we've exported an equivalent `.Machine` type to help with the casting. ```ts useMachine(combobox.machine as combobox.Machine) ``` ### Controlled vs Uncontrolled value Managing controlled and uncontrolled values is a fairly common need in most component libraries. Previously, we handled this by providing initial and controlled context to the machine. ```tsx /// 👇🏻 Default value const [state, send] = useMachine(numberInput.machine({ value: "10" }), { context: { // 👇🏻 Controlled value value: "10", }, }) ``` This can be initially confusing to users and is error prone. Now, we've moved the logic to the machine itself. Allowing users to explicitly provide a default value and a controlled value. ```tsx const service = useMachine(numberInput.machine, { // 👇🏻 Default value defaultValue: "10", // 👇🏻 Controlled value value: "10", }) ``` > This change applies all component with some form of `value` prop. ### Controlled vs Uncontrolled open Previously, we handled controlled and uncontrolled open states by providing initial `open` state and an additional `open.controlled` property. ```tsx // 👇🏻 Default value const [state, send] = useMachine(dialog.machine({ open: true }), { context: { // 👇🏻 Controlled value open: true, "open.controlled": true, }, }) ``` Now, we've moved the logic to the machine itself. Allowing users to explicitly provide a default and controlled open state. ```tsx const service = useMachine(dialog.machine, { // 👇🏻 Default value defaultOpen: true, // 👇🏻 Controlled value open: true, }) ``` ### Typings `.Context` is now renamed to `.Props` **Before** ```tsx import * as accordion from "@zag-js/accordion" interface Props extends accordion.Context {} ``` **After** ```tsx import * as accordion from "@zag-js/accordion" interface Props extends accordion.Props {} ``` ### Toast The toast component new requires that you create a toast store (or manager), and pass that store to the toast group machine. This store is to be used in userland to create and manage toasts. > Refer to the [toast](/components/react/toast) documentation for more details. **Before** ```tsx const [state, send] = useMachine( toast.group.machine({ overlap: false, placement: "bottom", }), ) const toaster = toast.group.connect(state, send, normalizeProps) // propagate the `toaster` via context and use it in your app. toaster.create({ title: "Hello", description: "World", }) ``` **After** ```tsx const toaster = toast.createStore({ overlap: false, placement: "bottom", }) const service = useMachine(toast.group.machine, { store: toaster, }) // use the `toaster` store to create and manage toasts. No need for context. toaster.create({ title: "Hello", description: "World", }) ``` For Solid.js users, we recommend using `` exported from `@zag-js/solid` when mapping over the toasts, instead of the `` component. ## Fixed - **Menu**: Fix issue where context menu doesn't update positioning on subsequent right clicks. - **Avatar**: Fix issue where `api.setSrc` doesn't work. - **File Upload**: Fix issue where drag-and-drop doesn't work when `directory` is `true`. - **Carousel** - Fix issue where initial page is not working. - Fix issue where pagination sync broken after using dots indicators. ## Removed - General - Removed `useActor` hook in favor of `useMachine` everywhere. - Removed `open.controlled` in favor of `defaultOpen` and `open` props. - Pagination - `api.setCount` is removed in favor of explicitly setting the `count` prop. - Select, Combobox - `api.setCollection` is removed in favor of explicitly setting the `collection` prop. ## Performance We measured the mount performance of 10k instances of each component, and compared the before and after. ### Avatar **Result**: ~27% faster mount time and ~99% faster update time #**Before** ```sh {phase: 'mount', duration: 1007.3000000119209} {phase: 'update', duration: 890.4000000357628} ``` **#After:** ```sh {phase: 'mount', duration: 736.9999999403954} {phase: 'update', duration: 1.899999976158142} ``` ### Accordion **Result**: ~61% faster mount time and no update time **Before** ```sh {phase: 'mount', duration: 2778.4999997913837} {phase: 'update', duration: 2.3000000715255737} ``` **After** ```sh {phase: 'mount', duration: 1079.0000001490116} ``` ### Collapsible **Result**: ~65% faster mount time and no update time **Before** ```sh {phase: 'mount', duration: 834.4000000357628} {phase: 'update', duration: 2.1999999284744263} ``` **After** ```sh {phase: 'mount', duration: 290.3000001013279} ``` ### Dialog **Result**: ~80% faster mount time and no update time **Before** ```sh {phase: 'mount', duration: 688.9000000357628} {phase: 'update', duration: 2.0000000298023224} ``` **After** ```sh {phase: 'mount', duration: 135.50000008940697} ``` ### Editable **Result**: ~56% faster mount time and no update time **Before** ```sh {phase: 'mount', duration: 1679.500000089407} {phase: 'update', duration: 2.0000000298023224} ``` **After** ```sh {phase: 'mount', duration: 737.5999999940395} ``` ### Tooltip **Result**: ~82% faster mount time and no update time **Before** ```sh {phase: 'mount', duration: 797.7999999821186} {phase: 'update', duration: 2.5999999940395355} ``` **After** ```sh {phase: 'mount', duration: 139.9000000357628} ``` ### Presence **Result**: ~64% faster mount time and eliminated update time **Before** ```sh { phase: "mount", duration: 1414 } { phase: "update", duration: 0 } ``` **After** ```sh { phase: "mount", duration: 502 } ``` ### Tabs **Result**: ~6% faster mount time **Before** ```sh { phase: "mount", duration: 4120 } { phase: "update", duration: 2014 } ``` **After** ```sh { phase: "mount", duration: 3880 } { phase: "nested-update", duration: 3179 } ``` ## Bundle Size We've made significant strides in reducing the bundle size of the overall library. The core package powers all components. It is now less than 2KB minified, a whopping **98% reduction** in size. **Before**: 13.78 KB **After**: 1.52 KB ## Contributors Notes - `activities` is now renamed to `effects` - `prop`, `context` and `refs` are now explicitly passed to the machine. Prior to this everything was pass to the `context` object which was quite expensive (performance wise). - The syntax for `watch` has changed significantly, refer to the new machines to learn how it works. It is somewhat similar to how `useEffect` works in react. - `createMachine` is just an identity function, it doesn't do anything. The machine work is now moved to the framework `useMachine` hook. ## Thank you We'd like to thank the following contributors for their help in making this release possible: - [Segun Adebayo](https://github.com/segunadebayo) for leading the charge and making such engineering feats possible. - [Christian Schroeter](https://github.com/cschroeter) for providing valuable feedback and suggestions to improve the library. ## React # Accordion 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.7.0](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. - 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: ```bash 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 ```jsx 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 🔥 ```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 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. ```jsx {2} const service = 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. ```jsx {3,4,9} // for multiple accordions const service = useMachine(accordion.machine, { multiple: true, defaultValue: ["home"], }) // for single accordions const service = useMachine(accordion.machine, { defaultValue: ["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`. ```jsx {2} const service = useMachine(accordion.machine, { collapsible: true, }) ``` ## Listening for 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) }, }) ``` ## 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 items by passing `disabled` to the machine's context. ```jsx {2} const service = 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. ```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 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 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: Gets 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`** 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. # Angle Slider 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.7.0](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 track to update value. - Supports Right-to-Left directionality. ## Installation To use the angle slider machine in your project, run the following command in your command line: ```bash npm install @zag-js/angle-slider @zag-js/react # or yarn add @zag-js/angle-slider @zag-js/react ``` This command will install the framework agnostic angle slider logic and the reactive utilities for your framework of choice. ## 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 First, import the angle-slider package into your project ```jsx import * as angleSlider from "@zag-js/angle-slider" ``` The angle slider package exports two key functions: - `machine` — The state machine logic for the angle slider widget as described in the WAI-ARIA spec. - `connect` — The function that translates the machine's state to JSX attributes and event handlers. > You'll 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 angle slider machine in your project 🔥 ```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 ```jsx {2} const service = useMachine(angleSlider.machine, { defaultValue: 45, }) ``` ## Setting the value's granularity By default, the granularity, is `1`, meaning that the value is always an integer. You can change the step attribute to control the granularity. For example, If you need a value between `5` and `10`, accurate to two decimal places, you should set the value of step to `0.01`: ```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. You can use this to setup custom behaviors in your app. ```jsx {2-7} const service = useMachine(angleSlider.machine, { onValueChange(details) { console.log("value is changing to:", details) }, onValueChangeEnd(details) { console.log("value has changed to:", details) }, }) ``` ## Usage within forms To use angle slider within forms, use the exposed `hiddenInputProps` from the `connect` function and ensure you pass `name` value to the machine's context. It will render a hidden input and ensure the value changes get propagated to the form correctly. ```jsx {2} const service = useMachine(angleSlider.machine, { name: "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 Earlier, we mentioned that each angle slider part has a `data-part` attribute added to them to select and style them in the DOM. ### 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 slider machine exposes the following context properties: **`ids`** Type: `Partial<{ root: string; thumb: string; hiddenInput: string; control: string; valueText: 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. **`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 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 ### 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. # Avatar The Avatar component is a React component that represents a user avatar or profile picture. It displays an image or initials within container. Avatar provides support for fallback text or elements when the image fails to load, or when the image is not provided. ## Resources [Latest version: v1.7.0](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 To use the avatar machine in your project, run the following command in your command line: ```bash npm install @zag-js/avatar @zag-js/react # or yarn add @zag-js/avatar @zag-js/react ``` This command will install the framework agnostic avatar logic and the reactive utilities for your framework of choice. ## Anatomy To set up the avatar 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 avatar package into your project ```jsx import * as avatar from "@zag-js/avatar" ``` The avatar package exports two key functions: - `machine` — The state machine logic for the avatar 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 avatar machine in your project 🔥 ```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 has loaded or failed to load, the `onStatusChange` callback is invoked. ```jsx {2} const service = useMachine(avatar.machine, { onStatusChange(details) { // details => { status: "error" | "loaded" } }, }) ``` ## Styling guide Earlier, we mentioned that each avatar part has a `data-part` attribute added to them to select and style them in the DOM. ```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 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 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: `() => void` Description: Function to set loaded state. **`setError`** Type: `() => void` 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" # Carousel an accessible carousel component that leverages native CSS Scroll Snap for smooth, performant scrolling between slides. ## Resources [Latest version: v1.7.0](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 alignment of slides (start, center or end alignment). - Show multiple slides at a time. - Supports looping and auto-playing. - Supports custom spacing between slides. ## Installation To use the carousel machine in your project, run the following command in your command line: ```bash npm install @zag-js/carousel @zag-js/react # or yarn add @zag-js/carousel @zag-js/react ``` This command will install the framework agnostic carousel logic and the reactive utilities for your framework of choice. ## Anatomy To set up the carousel 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 carousel package into your project ```jsx import * as carousel from "@zag-js/carousel" ``` The carousel package exports two key functions: - `machine` — The state machine logic for the carousel 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 carousel machine in your project 🔥 > **Note:** The carousel requires that you provide a `slideCount` property in > the machine's context. This is the number of slides in the carousel. ```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 carousel To create a vertical carousel, set the `orientation` property in the machine's context to `vertical`. ```jsx {2} const service = useMachine(carousel.machine, { orientation: "vertical", }) ``` ## Setting the initial slide To set the initial slide of the carousel, pass the `page` property to the machine's context. The `page` 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, }) ``` ## Setting the number of slides to show at a time To customize number of slides to show at a time, set the `slidesPerPage` property in the machine's context. The value must be an integer. ```jsx {2} const service = useMachine(carousel.machine, { slidesPerPage: 2, }) ``` ## Setting the number of slides to move at a time To customize number of slides to move at a time, set the `slidesPerMove` property in the machine's context. The value must be an integer or `auto`. > If the value is `auto`, the carousel will move the number of slides equal to > the number of slides per page. ```jsx {2} const service = useMachine(carousel.machine, { slidesPerMove: 2, }) ``` > Ensure the `slidesPerMove` is less than or equal to the `slidesPerPage` to > avoid skipping slides. ## Setting the carousel should loop around To allow looping of slides, set the `loop` property in the machine's context to `true`. ```jsx {2} const service = useMachine(carousel.machine, { loop: true, }) ``` ## Setting the gap between slides To customize spacing between slides, set the `spacing` property in the machine's context to a valid CSS unit. ```jsx {2} const service = useMachine(carousel.machine, { spacing: "16px", }) ``` ## 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) }, }) ``` ## Dragging the carousel To allow dragging the carousel with the mouse, set the `allowMouseDrag` property in the machine's context to `true`. ```jsx {2} const service = useMachine(carousel.machine, { allowMouseDrag: true, }) ``` ## Autoplaying the carousel To allow the carousel to autoplay, set the `autoplay` property in the machine's context to `true`. ```jsx {2} const service = useMachine(carousel.machine, { autoplay: true, }) ``` Alternatively, you can configure the autoplay interval by setting the `delay` property in the machine's context. ```jsx {2} const service = useMachine(carousel.machine, { autoplay: { delay: 2000 }, }) ``` ## Styling guide Earlier, we mentioned that each carousel part has a `data-part` attribute added to them to select and style them in the DOM. ```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. **`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 **`play`** Type: `() => void` Description: Function to start/resume autoplay **`pause`** Type: `() => void` Description: Function to pause autoplay **`isInView`** Type: `(index: number) => boolean` Description: Whether the item is in view **`refresh`** Type: `() => void` 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 # Checkbox 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.7.0](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. i.e. `indeterminate` state - Syncs with `disabled` state of fieldset - Syncs with form `reset` events - Can be toggled programmatically ## Installation To use the checkbox machine in your project, run the following command in your command line: ```bash npm install @zag-js/checkbox @zag-js/react # or yarn add @zag-js/checkbox @zag-js/react ``` This command will install the framework agnostic checkbox logic and the reactive utilities for your framework of choice. ## Anatomy To set up the checkbox 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 checkbox package into your project ```jsx import * as checkbox from "@zag-js/checkbox" ``` The checkbox package exports two key functions: - `machine` — The state machine logic for the checkbox widget. - `connect` — The function that translates the machine's state to JSX attributes and event handlers. Next, import the required hooks and functions for your framework and use the checkbox machine in your project 🔥 ```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 (