zag
Finite state machines for building accessible design systems and UI components. Works with modern frameworks, and even just Vanilla JS
Top Related Projects
A collection of libraries and tools that help you build adaptive, accessible, and robust user experiences.
Radix Primitives is an open-source UI component library for building high-quality, accessible design systems and web apps. Maintained by @workos.
Toolkit for building accessible web apps with React
Fluent UI web represents a collection of utilities, React components, and web components for building web applications.
Quick Overview
Zag is a set of low-level UI component primitives for building accessible and customizable UI components. It provides a collection of headless UI components that can be styled and integrated into various frontend frameworks and libraries.
Pros
- Framework-agnostic: Can be used with React, Vue, Solid, and other frontend frameworks
- Highly accessible: Built with a focus on keyboard navigation and screen reader support
- Customizable: Provides low-level primitives that can be styled and extended easily
- TypeScript support: Written in TypeScript for better type safety and developer experience
Cons
- Learning curve: Requires understanding of headless UI concepts and state machines
- Limited pre-built styles: Requires more effort to style components from scratch
- Documentation could be more comprehensive: Some advanced use cases may not be well-documented
- Relatively new project: May have fewer community resources compared to more established UI libraries
Code Examples
- Creating a simple accordion component:
import * as accordion from "@zag-js/accordion"
import { useMachine, normalizeProps } from "@zag-js/react"
function Accordion() {
const [state, send] = useMachine(accordion.machine({ id: "1" }))
const api = accordion.connect(state, send, normalizeProps)
return (
<div {...api.rootProps}>
<h3>
<button {...api.getTriggerProps({ value: "item-1" })}>Item 1</button>
</h3>
<div {...api.getContentProps({ value: "item-1" })}>Content 1</div>
{/* Add more items as needed */}
</div>
)
}
- Creating a toggle button:
import * as toggle from "@zag-js/toggle"
import { useMachine, normalizeProps } from "@zag-js/react"
function ToggleButton() {
const [state, send] = useMachine(toggle.machine({ id: "2" }))
const api = toggle.connect(state, send, normalizeProps)
return (
<button {...api.buttonProps}>
{api.isPressed ? "On" : "Off"}
</button>
)
}
- Creating a tooltip:
import * as tooltip from "@zag-js/tooltip"
import { useMachine, normalizeProps } from "@zag-js/react"
function Tooltip() {
const [state, send] = useMachine(tooltip.machine({ id: "3" }))
const api = tooltip.connect(state, send, normalizeProps)
return (
<>
<button {...api.triggerProps}>Hover me</button>
<div {...api.positionerProps}>
<div {...api.contentProps}>This is a tooltip</div>
</div>
</>
)
}
Getting Started
To start using Zag in your project:
-
Install the core package and desired component packages:
npm install @zag-js/core @zag-js/react @zag-js/accordion
-
Import and use the component in your React application:
import * as accordion from "@zag-js/accordion" import { useMachine, normalizeProps } from "@zag-js/react" function MyComponent() { const [state, send] = useMachine(accordion.machine({ id: "my-accordion" })) const api = accordion.connect(state, send, normalizeProps) // Use the api to render your component }
-
Style the component using your preferred CSS solution.
Competitor Comparisons
A collection of libraries and tools that help you build adaptive, accessible, and robust user experiences.
Pros of React Spectrum
- Comprehensive design system with a wide range of accessible components
- Robust documentation and extensive examples
- Strong support from Adobe and integration with other Adobe products
Cons of React Spectrum
- Larger bundle size due to its comprehensive nature
- Steeper learning curve for developers new to the ecosystem
- Less flexibility for customization compared to more lightweight alternatives
Code Comparison
React Spectrum:
import { Button } from '@adobe/react-spectrum';
function MyComponent() {
return <Button variant="cta">Click me</Button>;
}
Zag:
import { Button } from '@zag-js/react';
function MyComponent() {
return <Button>Click me</Button>;
}
Key Differences
- React Spectrum offers a more opinionated and complete design system, while Zag focuses on providing headless UI components
- Zag allows for greater customization and flexibility in styling and behavior
- React Spectrum has more built-in accessibility features, whereas Zag requires more manual implementation
- Zag has a smaller footprint and may be better suited for projects that require a lightweight solution
- React Spectrum benefits from Adobe's extensive resources and ecosystem support
Both libraries aim to provide accessible and reusable components, but they cater to different use cases and project requirements. The choice between them depends on factors such as project size, customization needs, and integration with existing design systems.
Radix Primitives is an open-source UI component library for building high-quality, accessible design systems and web apps. Maintained by @workos.
Pros of Primitives
- More mature and widely adopted in the React community
- Extensive documentation and examples available
- Larger ecosystem of components and plugins
Cons of Primitives
- Steeper learning curve for beginners
- Less flexibility in customization compared to Zag
- Heavier bundle size due to more comprehensive feature set
Code Comparison
Primitives:
import * as Dialog from '@radix-ui/react-dialog';
<Dialog.Root>
<Dialog.Trigger>Open</Dialog.Trigger>
<Dialog.Content>
<Dialog.Title>Dialog Title</Dialog.Title>
<Dialog.Description>Dialog content here</Dialog.Description>
</Dialog.Content>
</Dialog.Root>
Zag:
import * as dialog from "@zag-js/dialog"
import { useMachine, normalizeProps } from "@zag-js/react"
const [state, send] = useMachine(dialog.machine())
const api = dialog.connect(state, send, normalizeProps)
<div {...api.rootProps}>
<button {...api.triggerProps}>Open</button>
<div {...api.contentProps}>Dialog content here</div>
</div>
Both libraries provide unstyled, accessible components for building user interfaces. Primitives offers a more traditional React component approach, while Zag uses a state machine-based architecture. Zag's approach may offer more fine-grained control over component behavior, but Primitives' simpler API might be easier for some developers to grasp quickly.
Toolkit for building accessible web apps with React
Pros of Ariakit
- More extensive component library with a wider range of pre-built accessible components
- Stronger focus on accessibility, with built-in ARIA attributes and keyboard navigation
- More flexible styling options, allowing for easier customization of components
Cons of Ariakit
- Steeper learning curve due to its more comprehensive API and features
- Larger bundle size, which may impact performance in smaller projects
Code Comparison
Ariakit:
import { Button, Dialog } from "ariakit";
function App() {
return (
<Dialog.Root>
<Dialog.Trigger as={Button}>Open dialog</Dialog.Trigger>
<Dialog>Dialog content</Dialog>
</Dialog.Root>
);
}
Zag:
import { useDialog } from "@zag-js/dialog";
import { useMachine } from "@zag-js/react";
function App() {
const [state, send] = useMachine(useDialog);
const api = useDialog(state, send);
return (
<div>
<button onClick={api.open}>Open dialog</button>
{api.isOpen && <div>Dialog content</div>}
</div>
);
}
Both libraries provide accessible dialog components, but Ariakit offers a more declarative API, while Zag uses a machine-based approach for state management.
Fluent UI web represents a collection of utilities, React components, and web components for building web applications.
Pros of Fluent UI
- Extensive component library with a wide range of UI elements
- Strong integration with Microsoft products and services
- Robust documentation and design guidelines
Cons of Fluent UI
- Larger bundle size due to comprehensive feature set
- Steeper learning curve for developers new to the ecosystem
- Less flexibility for customization compared to more lightweight libraries
Code Comparison
Fluent UI button example:
import { PrimaryButton } from '@fluentui/react';
<PrimaryButton text="Click me" onClick={handleClick} />
Zag button example:
import * as button from "@zag-js/button"
import { useMachine, normalizeProps } from "@zag-js/react"
const [state, send] = useMachine(button.machine({ id: "1" }))
const api = button.connect(state, send, normalizeProps)
return <button {...api.buttonProps}>Click me</button>
Zag offers a more low-level approach, providing greater control over the component's behavior, while Fluent UI provides a higher-level abstraction with pre-styled components.
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual CopilotREADME
Zag
Finite state machines for accessible JavaScript components
- Write once, use everywhere ð¦: The component interactions are modelled in a framework agnostic way. We provide adapters for JS frameworks like React, Solid, or Vue.
- 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.
- 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.
Documentation
To see the documentation, visit zagjs.com/
Releases
For changelog, Check CHANGELOG.md
Problem
With the rise of design systems and component-driven development, there's an endless re-implementation of common component patterns (Tabs, Menu, Modal, etc.) in multiple frameworks.
Most of these implementations seem to be fairly similar in spirit, the differences being around the reactivity and
effects systems for the framework (e.g. useState
, useEffect
in React.js). Framework specific solutions tend to grow
in complexity over time and often become hard to understand, debug, improve or test.
Solution
Zag is a JavaScript API that implements common component patterns using the state machine methodology.
Installation
npm i --save @zag-js/{component}
# or
yarn add @zag-js/{component}
{component}
represents any component machine like dialog (@zag-js/dialog
), tooltip (@zag-js/tooltip
) , etc.
For framework specific solutions, we provide simple wrappers to help you consume the component state machines.
- âï¸
@zag-js/react
- React hooks for consuming machines in React applications - ð
@zag-js/vue
- Vue composition for consuming machines in Vue applications - ð·
@zag-js/solid
- Solid.js utilities for consuming machines in Solid.js applications
Usage
import { normalizeProps, useMachine } from "@zag-js/react"
import * as toggle from "@zag-js/toggle-group"
import { useId } from "react"
export function ToggleGroup() {
const [state, send] = useMachine(toggle.machine({ id: useId() }))
const api = toggle.connect(state, send, normalizeProps)
return (
<div {...api.getRootProps()}>
<button {...api.getItemProps({ value: "bold" })}>B</button>
<button {...api.getItemProps({ value: "italic" })}>I</button>
<button {...api.getItemProps({ value: "underline" })}>U</button>
</div>
)
}
Guiding Principles
- All component machines and tests are modelled according to the WAI-ARIA authoring practices
- Write end-to-end tests for every component based on the WAI-ARIA spec. Regardless of the framework, users expect component patterns to work the same way!
- All machines should be light-weight, simple, and easy to understand. Avoid using complex machine concepts like spawn, nested states, etc.
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" ð¥
Commands
Build commands
Our build is managed with esbuild and turborepo to provide fast, concurrent builds across the packages.
build
: Build the CJS, ESM and DTS files. This is the actual production build that we run in the CI.
Examples
Since zag is framework agnostic, we need a way to test it within a framework. The examples/
directory includes starter
projects for the frameworks we support.
start-react
: Starts the Next.js TypeScript projectstart-vue
: Starts the Vue 3 TypeScript projectstart-solid
: Starts the Solid TypeScript project
E2E Tests
We've setup end-to-end tests for every machine we built. We use Playwright for testing and we ensure that the component works the same way regardless of the framework.
e2e-react
: Starts the E2E tests for the React projecte2e-vue
: Starts the E2E tests for the Vue projecte2e-solid
: Starts the E2E tests for the Solid project
Contributing new machines/features
generate-machine
: Generates a new machine package in thepackages/
directory. It sets up the required files and structure for new machine.generate-util
: Generates a new utility package in thepackages/utilities
directory.
Other commands
test
: Run the tests for all packageslint
: Lint all packages
Website
start-website
: Starts the website
Inspirations
- Duplicate code in Chakra UI React and Vue ð
- Thoughts on Pure UI - Guillermo Rauch
- Pure UI Control - Adam Solve
- Material Components Web for inspiring my first prototype
- Radix UI for inspiring the dimissable and presence pattern
- XState for inspiring the base implementation of the state machine
- Vue.js and Lit for inspiring new patterns in the
machine (
computed
andwatch
) - Sonner for inspiring the toast machine
Contributions
Looking to contribute? Look for the Good First Issue label.
ð Bugs
Please file an issue for bugs, missing documentation, or unexpected behavior.
ð¡ Feature Requests
Please file an issue to suggest new features. Vote on feature requests by adding a ð. This helps maintainers prioritize what to work on.
License
MIT © Segun Adebayo
Top Related Projects
A collection of libraries and tools that help you build adaptive, accessible, and robust user experiences.
Radix Primitives is an open-source UI component library for building high-quality, accessible design systems and web apps. Maintained by @workos.
Toolkit for building accessible web apps with React
Fluent UI web represents a collection of utilities, React components, and web components for building web applications.
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual Copilot