Open-source project
statelyai/xstate avatar
statelyai/xstate

XState: statecharts and actors for logic that outgrows a store

State machines, statecharts, and actors for complex logic

30,115 stars1,391 forksTypeScriptMIT

At a glance

What is it?
XState is a zero-dependency TypeScript library for modelling application behaviour as state machines, statecharts and actors, with a separate @xstate/store package for simpler event-based state. The judgement: adopt it when your logic is genuinely stateful and event-driven, not when a plain store already covers you.
Who is it for?
Adopt XState when your logic has named states, guarded transitions and long-lived processes that outlive a single render, and adopt @xstate/store instead when you only need event-based state. Do not reach for it if a plain store already models your data and the machine would be a wrapper around a boolean.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

The problem XState actually solves

Most state bugs are not data bugs. They are sequencing bugs: a request resolved after the component that issued it unmounted, a retry fired while a cancel was in flight, a form that accepts a submit in a state where submit is meaningless. Reducers and stores handle the data well and say almost nothing about which transitions are legal. XState's answer is to make the legal transitions the artifact. The README states the library is for "state management and orchestration for JavaScript and TypeScript apps" and that it has zero dependencies, which matters if you are embedding it in a backend service or a library rather than an app. The audience is narrower than the npm install count implies: teams whose logic has explicit phases, guards and side effects that need to be cancelled or restarted, and who are willing to describe that logic up front instead of discovering it through conditionals. The README also points at the SCXML specification as inspiration, which is a useful signal about the intended rigour.

Machines, actors, and what runs when you call send

The mechanism visible in the README has two layers. createMachine returns a description: an id, an initial state, a context object, and a states map where each state lists the events it accepts. In the toggle example, inactive accepts TOGGLE and targets active; active declares an entry action using assign to increment context.count, then accepts TOGGLE back. Nothing has executed yet. createActor turns that description into a running instance, which the README compares to a store. You subscribe to it, start it, and send events to it. The README's own trace shows the ordering: starting the actor logs the initial state, and each TOGGLE logs the new state value and context. Two details are worth pulling out. The first is that assign runs on entry to a state, not on the event itself, so the count increments when active is entered, which is why the second TOGGLE returns to inactive with count still at 1. The second is that the actor is the unit of lifecycle: subscribing gives you a stream of state snapshots, and the interpreter owns when transitions happen. That separation between a machine definition and its running actor is what makes the same logic reusable across React, Vue and Svelte templates, all of which the README lists with the same feedbackMachine.ts file.

Installing it and getting a first actor running

The README gives two install lines depending on which package you want. For machines and actors: npm install xstate. For the smaller store: npm install @xstate/store. The imports in the quick start are createMachine, createActor and assign from 'xstate'. The minimal sequence is to define the machine, call createActor with it, attach a subscriber, call start, then send events shaped as objects with a type field. The README's toggle example sends { type: 'TOGGLE' } and the store example sends { type: 'addDonut' } and { type: 'changeFlavor', flavor: 'strawberry' }, so events carrying payload are just extra properties on the same object. For @xstate/store the import is createStore from '@xstate/store', and the shape is different: you pass context plus an on map of event handlers that each return the next context. The README describes that package as under 1kb with first-class TypeScript inference and says it is similar in spirit to Redux or Zustand with less boilerplate. It also says the two packages work together but neither requires the other, so a migration path exists in one direction: start with the store, move to machines when the logic earns it.

The alpha release line is the first thing to check

The release list is the least comfortable part of this repository's current state. The most recent published versions are xstate@6.0.0-alpha.52 and xstate@6.0.0-alpha.51, dated early September 2026, alongside @xstate/store-react@2.1.0-alpha.1. The README, meanwhile, is written around XState v5 and the templates it links are labelled XState v5. That is not a contradiction, but it is a decision point: a plain npm install xstate resolves to whatever the registry marks as latest, and if that is an alpha of a major version, you may be reading v5 documentation while running v6 code. Nothing in the supplied material states which version the default install resolves to, so treat that as something to verify with npm view xstate dist-tags before you write any code. The existence of a long alpha series is also a maintenance signal: APIs at that stage can change between alphas, and pinning an exact version is the difference between a reproducible build and a confusing afternoon.

Where a statechart is the wrong instrument

The README is unusually direct about this, which is worth crediting: not every app needs the features of state machines and statecharts, and @xstate/store exists precisely for the cases that do not. The failure mode is a machine that models data rather than behaviour. If your "states" are really just fields on an object, and every event is allowed in every state, then you have built a store with extra ceremony and a diagram nobody reads. A second limitation is the cost of the formalism itself: statecharts reward teams who will name states and transitions precisely, and punish teams who treat the machine as a place to hide arbitrary branching. There is also the visual dependency. The README promotes Stately Studio for creating and editing machines visually and points to a VS Code extension, and while neither is required to run the library, the value proposition leans on being able to see the chart. A machine with nested states and many guards that nobody can render is harder to review than the equivalent reducer. Finally, the README does not discuss bundle size for the xstate package itself, only the under 1kb figure for @xstate/store, so if payload size is your constraint, measure rather than assume.

XState versus a plain store like Redux or Zustand

The README positions @xstate/store as similar in spirit to Redux or Zustand, and that comparison is the useful one, because it clarifies where the boundary sits. A Redux or Zustand store holds a state tree and applies reducers to it; the store does not know that a cancel event is meaningless after a request has already completed, and it will happily apply it. XState's machine does know, because the transition is not declared in that state. The difference in approach is therefore not performance or API ergonomics but enforcement: a machine rejects undefined transitions by construction, while a store accepts any action its reducer handles. The cost of that enforcement is that you must enumerate the states. In @xstate/store the README's donut example shows the store style: handlers take context and return the next context, with no notion of which handler is legal when. That is the right level of ceremony for a counter or a form field, and the wrong level for a multi-step checkout with retries and cancellation. The README frames the relationship as graduating from the store to full machines, which is a reasonable default: start lower, move up only when the missing constraint starts causing bugs.

Licence, maintenance and what upgrades cost you

XState is MIT licensed, which permits commercial use, modification and redistribution provided the copyright notice and permission notice are retained. That is the standard permissive arrangement and it does not impose copyleft obligations on your application. This is a description of the licence text, not legal advice; if your organisation has specific compliance requirements, have counsel review it. On maintenance, the repository is not archived and the last push recorded is September 2026, with releases in the days before it, so the project is active. The cost that matters is upgrade cost, and the current release pattern tells you what to expect: a v6 alpha line running alongside a v5 documentation and template set means migration is a real project, not a version bump. Machines are usually the cheapest part to migrate because the definition is declarative, but actor wiring, framework bindings and any code that reads state.value or state.context directly will need attention. The practical move is to keep machine definitions in files that do not import framework bindings, so the definition survives an upgrade even if the surrounding integration does not.

Editorial conclusion

Adopt XState when your logic has named states, guarded transitions and long-lived processes that outlive a single render, and adopt @xstate/store instead when you only need event-based state. Do not reach for it if a plain store already models your data and the machine would be a wrapper around a boolean. Before committing, verify three things against the documentation rather than this article: which release line you are installing, since the newest published versions are 6.0.0-alpha builds; whether the v5 actor API is what your framework binding expects; and how you will visualise and test the machine, because an unreadable chart is the main way this approach fails.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. statelyai/xstate on GitHub
Community notes

Community notes