Skip to main content
Zag is a JavaScript library that implements common UI component patterns — tooltips, dialogs, menus, sliders, and more — using the state machine methodology. It provides the interaction logic and accessibility handling for each component, leaving styling entirely up to you.
Zag is part of the next evolution of Chakra UI. Watch the talk to learn more about its origins.

Motivation

With the rise of design systems and component-driven development, there is an endless re-implementation of common component patterns (tabs, menus, modals, etc.) across multiple frameworks. Most implementations are similar in spirit, but differ in the reactivity and effects system of the framework — useState and useEffect in React, ref and watch in Vue, and so on. Framework-specific component code tends to grow in complexity over time and becomes difficult to understand, debug, improve, or test. The Chakra UI team experienced this directly while maintaining both React and Vue versions of the same component library — the same bugs would appear in both, and fixing one did not fix the other. Zag solves this by separating the interaction logic from the rendering layer. Component behavior is modeled once as a state machine, then consumed via thin framework adapters.
Most widgets should function the same way regardless of the framework they’re built with. That’s why we built Zag.

Why Zag?

Powered by state machines

Built on top of the latest ideas in Statecharts. Component behavior is modeled as explicit states and transitions, making it predictable, testable, and easy to reason about.

Write once, use everywhere

Component interactions are modeled in a framework-agnostic way. Thin adapters for React, Vue, Solid, and Svelte let you use the same machine in any framework.

Accessibility first

Zag handles keyboard interactions, focus management, ARIA roles, and attributes according to WAI-ARIA authoring practices — so you don’t have to.

Headless

The machine APIs return props and state with no opinions about styling. Use Tailwind, CSS modules, styled-components, or any other solution you prefer.

Incremental adoption

Each component machine is published as an independent npm package. Install only what you need and add machines as your design system grows.

TypeScript first

Every machine and framework adapter is fully typed. The connect function returns a strongly typed API object tailored to each component.

Architecture

Zag separates component logic into two layers: Machine packages (@zag-js/tooltip, @zag-js/dialog, etc.) contain the pure state machine logic. They define states, transitions, effects, and the connect function that maps machine state to DOM props. These packages have no dependency on any UI framework. Framework adapters (@zag-js/react, @zag-js/vue, @zag-js/solid, @zag-js/svelte) provide the useMachine hook and normalizeProps utility for each framework. useMachine starts the machine and integrates it with the framework’s reactivity system. normalizeProps converts the props returned by connect into the exact format each framework expects. The usage pattern is consistent across all frameworks:
// 1. Start the machine with useMachine
const service = useMachine(tooltip.machine, { id: "my-tooltip" })

// 2. Connect the service to get a typed component API
const api = tooltip.connect(service, normalizeProps)

// 3. Spread the returned props onto your elements
<button {...api.getTriggerProps()}>Hover me</button>

Prior art

Zag builds on ideas from several influential projects:
  • XState — inspired the base state machine implementation
  • Radix UI — inspired the dismissable and presence patterns
  • React Aria — demonstrated the value of framework-agnostic accessibility primitives
  • Material Components Web — early inspiration for separating logic from rendering
  • Vue.js and Lit — inspired the computed and watch patterns in the machine

Get started

Installation

Install the framework adapter and your first component machine.

Build docs developers (and LLMs) love