A collection of framework-agnostic UI component patterns like accordion, menu, and dialog that can be used to build design systems for React, Vue and Solid.js
Powered by Statecharts
Simple, resilient component logic. Write component logic once and use anywhere.
Accessible
Built-in adapters that connects machine output to DOM semantics in a WAI-ARIA compliant way.
Framework agnostic
Component logic is largely JavaScript code and can be consumed in any JS framework.
Zag machine APIs are completely headless and unstyled. Use your favorite styling solution and get it matching your design system.
import * as numberInput from "@zag-js/number-input" import { useMachine, useSetup } from "@zag-js/react" export function NumberInput() { // 1. Consume the machine const [state, send] = useMachine(numberInput.machine) // 2. Setup the machine with a unique id const ref = useSetup({ send, id: "1" }) // 3. Grab the provided API const api = numberInput.connect(state, send) // 4. Render the component return ( <div ref={ref} {...api.rootProps}> <label {...api.labelProps}>Enter number:</label> <div> <button {...api.decrementButtonProps}>DEC</button> <input {...api.inputProps} /> <button {...api.incrementButtonProps}>INC</button> </div> </div> ) }
Finite state machines for building accessible design systems and UI components. Works with React, Vue and Solid.
import * as numberInput from "@zag-js/number-input" import { useMachine, useSetup } from "@zag-js/react" export function NumberInput() { const [state, send] = useMachine(numberInput.machine) const ref = useSetup({ send, id: "1" }) const api = numberInput.connect(state, send) return ( <div ref={ref} {...api.rootProps}> <label {...api.labelProps}>Enter number:</label> <div> <button {...api.decrementButtonProps}>DEC</button> <input {...api.inputProps} /> <button {...api.incrementButtonProps}>INC</button> </div> </div> ) }
import * as numberInput from "@zag-js/number-input" import { normalizeProps, useMachine, useSetup } from "@zag-js/vue" import { computed, defineComponent, h, Fragment } from "vue" export default defineComponent({ name: "NumberInput", setup() { const [state, send] = useMachine(numberInput.machine) const ref = useSetup({ send, id: "1" }) const apiRef = computed(() => numberInput.connect(state.value, send, normalizeProps), ) return () => { const api = apiRef.value return ( <div ref={ref} {...api.rootProps}> <label {...api.labelProps}>Enter number</label> <div> <button {...api.decrementButtonProps}>DEC</button> <input {...api.inputProps} /> <button {...api.incrementButtonProps}>INC</button> </div> </div> ) } }, })
<script setup> import * as numberInput from "@zag-js/number-input"; import { normalizeProps, useMachine, useSetup } from "@zag-js/vue"; import { computed } from "vue"; const [state, send] = useMachine(numberInput.machine); const ref = useSetup({ send, id: "1" }); const api = computed(() => numberInput.connect(state.value, send, normalizeProps) ); </script> <template> <div ref="ref" v-bind="api.rootProps"> <label v-bind="api.labelProps">Enter number</label> <div> <button v-bind="api.decrementButtonProps">DEC</button> <input v-bind="api.inputProps" /> <button v-bind="api.incrementButtonProps">INC</button> </div> </div> </template>
import * as numberInput from "@zag-js/number-input" import { normalizeProps, useMachine, useSetup } from "@zag-js/solid" import { createMemo, createUniqueId } from "solid-js" export function NumberInput() { const [state, send] = useMachine(numberInput.machine) const ref = useSetup({ send, id: createUniqueId() }) const api = createMemo(() => numberInput.connect(state, send, normalizeProps)) return ( <div ref={ref} {...api().rootProps}> <label {...api().labelProps}>Enter number:</label> <div> <button {...api().decrementButtonProps}>DEC</button> <input {...api().inputProps} /> <button {...api().incrementButtonProps}>INC</button> </div> </div> ) }
Today, design systems are becoming a very popular toolkit for companies to create a cohesive and accessible user experience for their customers.
With the rise of component-driven development, there's an endless re-implementation of common widgets (tabs, menu, etc.) in multiple frameworks. These implementations tend to grow in complexity over time and often become hard to understand, debug, improve, or test.
We need a better way to model component logic.Zag is a new approach to the component design process, designed to help you avoid re-inventing the wheel and build better UI components regardless of framework. Heavily inspired by XState, but built to make it easier to maintain, test, and enhance.
Creator of Zag.js