XState

A dependency-free state machine and statechart library for modeling complex application logic as actors, states, and events.

Library
npm
v5.32.6
30,095stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
86/100Excellent
Development Activity88
Maintenance92
Community64
Maturity60
Momentum40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
81/100Excellent
Architecture90
Code Quality85
Innovation85
Learning Curve65

XState is a state management and orchestration library for JavaScript and TypeScript that models application logic as finite state machines, statecharts, and actors rather than scattered conditionals and boolean flags. Instead of tracking isLoading, hasError, and isSuccess as independent booleans that can drift into impossible combinations, XState represents a process as a single machine with named states, explicit transitions, and a typed context object, so illegal states become unrepresentable by construction.

The library implements the actor model on top of statecharts (the SCXML-inspired formalism popularized by David Harel), meaning machines can spawn child actors, communicate via events, and compose into larger systems the same way a distributed system does — just running in-process. Actions, guards, delays, and invoked services are all declaratively described in the machine definition and only wired to real implementations at createActor() time, which keeps the state graph inspectable, visualizable, and testable independent of side effects.

It ships with zero runtime dependencies and is framework-agnostic — official bindings exist for React, Vue, Svelte, Solid, and Angular, and it runs identically on the frontend or backend (workflow orchestration, long-running processes, background jobs). For teams that only need lightweight event-based state without the full statechart formalism, the same monorepo ships the much smaller @xstate/store as a Redux/Zustand-style alternative that can be adopted independently or alongside xstate.

What You Get

  • A finite state machine and statechart interpreter (createMachine / createActor) with zero runtime dependencies
  • The actor model built in — machines can spawn, invoke, and message child actors, promises, callbacks, and observables
  • Deep TypeScript inference for states, events, and context without manual type annotations
  • Guards, delayed transitions, parallel/hierarchical states, and history states for modeling non-trivial control flow
  • Graph traversal and model-based testing utilities via xstate/graph for generating and verifying all reachable paths
  • A visual companion editor (Stately Studio / state.new) that can export directly to XState v5 machine definitions
  • First-party framework bindings (@xstate/react, @xstate/vue, @xstate/svelte, @xstate/solid) and a standalone lightweight store (@xstate/store) for simpler cases

Common Use Cases

  • Modeling multi-step UI flows (checkout, onboarding wizards, authentication) where a boolean-flag approach produces impossible state combinations
  • Orchestrating async data-fetching lifecycles (idle/loading/success/error/retry) with explicit, testable transitions instead of ad hoc effect chains
  • Running long-lived backend workflows and background jobs where a process needs to survive restarts and be resumed from a persisted state snapshot
  • Coordinating multiple cooperating actors (e.g. a media player with playback, buffering, and network actors) that need to message each other predictably
  • Building interactive editors or games (documented in the project’s own examples: tic-tac-toe, a tile puzzle, a snake game) where explicit states simplify input handling

Under The Hood

Architecture Execution centers on createMachine() (packages/core/src/machine.ts, StateNode.ts) building a static, introspectable graph of StateNodes, and createActor() (createActor.ts, ~900 lines) instantiating that graph into a running Interpreter/actor with its own mailbox, subscriptions, and lifecycle. Transitions are resolved by a pure transition() function (transition.ts) that takes a state and event and returns the next state plus a list of actions to execute — the core state-resolution algorithm has no side effects, which is what makes machines simulatable and diffable independent of the runtime. Actors form a supervision tree: spawnChild/stopChild (src/actions) let a running machine own child actors (other machines, promises, callbacks, or observables defined under src/actors), and a system.ts registry tracks all actors for inspection. This actor-tree design is the load-bearing abstraction — changing how child actors are addressed or messaged would ripple through spawning, inspection, and the framework bindings that all assume the same actor-ref contract.

Tech Stack Pure TypeScript (98%+ of the codebase) with zero runtime dependencies declared in packages/core/package.json — the entire statechart interpreter is self-contained. The repo is a pnpm workspace/monorepo (pnpm-workspace.yaml) built with Preconstruct for multi-format package output (CJS, ESM, UMD, plus separate development/production builds per entry point such as xstate/guards, xstate/actions, xstate/graph). Testing runs on Vitest across per-package projects (test:core, test:store), linting/formatting use oxlint/oxfmt, and releases are coordinated with Changesets. Framework integrations (React, Vue, Svelte, Solid, Angular) live as sibling packages in the same workspace rather than separate repos, all built from the same tooling.

Code Quality The core package has an extensive test suite — 75+ dedicated test files under packages/core/test covering parallel states, history, actor spawning, SCXML compatibility, delayed transitions, and dedicated .types.test.ts files that assert TypeScript inference itself, not just runtime behavior. tsconfig.json enables strict mode repo-wide, and CI-facing scripts (typecheck, lint, test) gate on all three. Comment density in core files is moderate — public entry points like createActor.ts carry substantial JSDoc, while internal resolution logic favors concise, self-describing function names over inline prose.

API Design The public API surface is deliberately small and consistent: createMachine() to describe behavior, createActor() to run it, .send() and .subscribe() to interact with it — the same four calls scale from a two-state toggle to a multi-actor workflow. Subpath exports (xstate/guards, xstate/actions, xstate/graph, xstate/actors) let consumers import only what they use rather than one monolithic module, and the type system infers state/event/context shapes from the machine config itself, so most usage needs no manual generic annotations. The tradeoff is a genuine learning curve around statechart vocabulary (actors, guards, invoked services, hierarchical/parallel states) that the companion @xstate/store package exists specifically to let simpler consumers skip.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search