XState v5 Strict Skill
Environment
- tsc: !
tsc --version 2>/dev/null || echo "NOT INSTALLED"
XState v5 ONLY. Requires TypeScript 5.0+. Never use v4 patterns.
Workflow
- Design first (unless user says skip): produce planning artifacts before code
- Types first:
types.context, types.events, types.input (+ types.output)
- Implement:
setup({...}).createMachine({...}) with all implementations in setup
- Validate:
tsc --noEmit, no any leaks, no string events, no v4 imports
Quick Reference
import { setup, assign, fromPromise, createActor } from "xstate"
const machine = setup({
types: {
context: {} as { count: number },
events: {} as { type: "inc" } | { type: "add"; amount: number },
input: {} as { initialCount?: number },
},
actions: {
inc: assign({ count: ({ context }) => context.count + 1 }),
add: assign({
count: ({ context }, params: { amount: number }) => context.count + params.amount,
}),
},
guards: {
isPositive: ({ context }) => context.count > 0,
},
}).createMachine({
id: "counter",
context: ({ input }) => ({ count: input.initialCount ?? 0 }),
initial: "active",
states: {
active: {
on: {
inc: { actions: "inc" },
add: {
actions: {
type: "add",
params: ({ event }) => ({ amount: event.amount }),
},
},
},
},
},
})
const actor = createActor(machine, { input: { initialCount: 0 } })
actor.subscribe((snapshot) => console.log(snapshot.context.count))
actor.start()
actor.send({ type: "inc" })
Hard Rules
setup({...}).createMachine({...}) for all machines
- All implementations in
setup(): actions, guards, actors
- Typed params: actions/guards take
(_, params: { ... }) second arg
- Event objects only:
actor.send({ type: "X" }), never strings
- No god-machine: unrelated domains = separate actors/machines
- No state-mirroring booleans: use state nodes for modes, context for data
invoke has onError: always handle failure
- No v4 patterns: no
interpret, Machine, cond, send, pure, choose
v4 to v5 Migration
| v4 (WRONG) |
v5 (CORRECT) |
createMachine() alone |
setup().createMachine() |
interpret() |
createActor() |
services: {} |
actors: {} |
cond |
guard |
send() action |
raise() or sendTo() |
pure()/choose() |
enqueueActions() |
withContext() |
input |
withConfig() |
provide() |
spawn import |
spawnChild or ({ spawn }) args |
Actor Types
| Type |
Creator |
Use Case |
| State Machine |
setup().createMachine() |
Complex state logic |
| Promise |
fromPromise() |
Async request/response |
| Callback |
fromCallback() |
Bidirectional streams, SDK bridging |
| Observable |
fromObservable() |
RxJS streams |
| Transition |
fromTransition() |
Reducer-like state |
invoke vs spawnChild
- invoke: actor lifecycle tied to state. Created on entry, stopped on exit. Use for request/response
- spawnChild: dynamic actors independent of state. Use for long-lived collaborators with
sendTo
Planning Artifacts (Design-First)
Before writing machine code, produce:
- Goal: one sentence
- Non-goals: what the machine does NOT handle
- State inventory: name, meaning, invariants per state
- Event catalog: name, payload, source (UI/network/timer/child)
- Transition table:
from -> on event -> guard? -> actions -> to
- Async/actor map: invoke vs spawnChild, onDone/onError, cancellation
- Error strategy: state vs context, retry policy, fatal vs recoverable
Detailed References
- rules.md — Complete enforcement rules, forbidden patterns, testing, React integration
- patterns.md — Actor patterns: fromCallback, fromPromise, hierarchy, parallel, delays, cleanup, SDK bridging, Promise-bridge
1---2name: aio-xstate3description: Implement XState v5 state machines with strict patterns — setup().createMachine(), actors, and TypeScript typing. Use when working with finite state machines (FSM), statecharts, state diagrams, or the actor model in TypeScript. Covers @xstate/react integration (useMachine, useActor, useSelector), parallel states, hierarchical (nested) states, XState migration from v4 to v5, and the full design-first workflow: state inventory, event catalog, transition table.4---56# XState v5 Strict Skill78## Environment9- tsc: !`tsc --version 2>/dev/null || echo "NOT INSTALLED"`1011> **XState v5 ONLY.** Requires TypeScript 5.0+. Never use v4 patterns.1213## Workflow14151. **Design first** (unless user says skip): produce planning artifacts before code162. **Types first**: `types.context`, `types.events`, `types.input` (+ `types.output`)173. **Implement**: `setup({...}).createMachine({...})` with all implementations in setup184. **Validate**: `tsc --noEmit`, no `any` leaks, no string events, no v4 imports1920## Quick Reference2122```ts23import { setup, assign, fromPromise, createActor } from "xstate"2425const machine = setup({26 types: {27 context: {} as { count: number },28 events: {} as { type: "inc" } | { type: "add"; amount: number },29 input: {} as { initialCount?: number },30 },31 actions: {32 inc: assign({ count: ({ context }) => context.count + 1 }),33 add: assign({34 count: ({ context }, params: { amount: number }) => context.count + params.amount,35 }),36 },37 guards: {38 isPositive: ({ context }) => context.count > 0,39 },40}).createMachine({41 id: "counter",42 context: ({ input }) => ({ count: input.initialCount ?? 0 }),43 initial: "active",44 states: {45 active: {46 on: {47 inc: { actions: "inc" },48 add: {49 actions: {50 type: "add",51 params: ({ event }) => ({ amount: event.amount }),52 },53 },54 },55 },56 },57})5859const actor = createActor(machine, { input: { initialCount: 0 } })60actor.subscribe((snapshot) => console.log(snapshot.context.count))61actor.start()62actor.send({ type: "inc" })63```6465## Hard Rules66671. **`setup({...}).createMachine({...})`** for all machines682. **All implementations in `setup()`**: actions, guards, actors693. **Typed params**: actions/guards take `(_, params: { ... })` second arg704. **Event objects only**: `actor.send({ type: "X" })`, never strings715. **No god-machine**: unrelated domains = separate actors/machines726. **No state-mirroring booleans**: use state nodes for modes, context for data737. **`invoke` has `onError`**: always handle failure748. **No v4 patterns**: no `interpret`, `Machine`, `cond`, `send`, `pure`, `choose`7576## v4 to v5 Migration7778| v4 (WRONG) | v5 (CORRECT) |79|---|---|80| `createMachine()` alone | `setup().createMachine()` |81| `interpret()` | `createActor()` |82| `services: {}` | `actors: {}` |83| `cond` | `guard` |84| `send()` action | `raise()` or `sendTo()` |85| `pure()`/`choose()` | `enqueueActions()` |86| `withContext()` | `input` |87| `withConfig()` | `provide()` |88| `spawn` import | `spawnChild` or `({ spawn })` args |8990## Actor Types9192| Type | Creator | Use Case |93|---|---|---|94| State Machine | `setup().createMachine()` | Complex state logic |95| Promise | `fromPromise()` | Async request/response |96| Callback | `fromCallback()` | Bidirectional streams, SDK bridging |97| Observable | `fromObservable()` | RxJS streams |98| Transition | `fromTransition()` | Reducer-like state |99100## invoke vs spawnChild101102- **invoke**: actor lifecycle tied to state. Created on entry, stopped on exit. Use for request/response103- **spawnChild**: dynamic actors independent of state. Use for long-lived collaborators with `sendTo`104105## Planning Artifacts (Design-First)106107Before writing machine code, produce:1081091. **Goal**: one sentence1102. **Non-goals**: what the machine does NOT handle1113. **State inventory**: name, meaning, invariants per state1124. **Event catalog**: name, payload, source (UI/network/timer/child)1135. **Transition table**: `from -> on event -> guard? -> actions -> to`1146. **Async/actor map**: invoke vs spawnChild, onDone/onError, cancellation1157. **Error strategy**: state vs context, retry policy, fatal vs recoverable116117## Detailed References118119- **[rules.md](references/rules.md)** — Complete enforcement rules, forbidden patterns, testing, React integration120- **[patterns.md](references/patterns.md)** — Actor patterns: fromCallback, fromPromise, hierarchy, parallel, delays, cleanup, SDK bridging, Promise-bridge