JavaScript state machines and statecharts
JavaScript and TypeScript finite state machines and statecharts for the modern web.
💙 Explore our catalogue of examples
📑 Adheres to the SCXML specification
💬 Chat on the Stately Discord Community
- 🤖
xstate
- Core finite state machine and statecharts library + interpreter - 🔬
@xstate/fsm
- Minimal finite state machine library - 📉
@xstate/graph
- Graph traversal utilities for XState - ⚛️
@xstate/react
- React hooks and utilities for using XState in React applications - 💚
@xstate/vue
- Vue composition functions and utilities for using XState in Vue applications - 🎷
@xstate/svelte
- Svelte utilities for using XState in Svelte applications - ✅
@xstate/test
- Model-Based-Testing utilities (using XState) for testing any software - 🔍
@xstate/inspect
- Inspection utilities for XState
Get started by forking one of these templates on CodeSandbox:
- XState Template - no framework
- XState + TypeScript Template - no framework
- XState + React Template
- XState + React + TypeScript Template
- XState + Vue Template
- XState + Vue 3 Template
- XState + Svelte Template
npm install xstate
import { createMachine, interpret } from 'xstate';
// Stateless machine definition
// machine.transition(...) is a pure function used by the interpreter.
const toggleMachine = createMachine({
id: 'toggle',
initial: 'inactive',
states: {
inactive: { on: { TOGGLE: 'active' } },
active: { on: { TOGGLE: 'inactive' } }
}
});
// Machine instance with internal state
const toggleService = interpret(toggleMachine)
.onTransition((state) => console.log(state.value))
.start();
// => 'inactive'
toggleService.send('TOGGLE');
// => 'active'
toggleService.send('TOGGLE');
// => 'inactive'
📉 See the visualization on xstate.js.org/viz
import { createMachine, interpret, assign } from 'xstate';
const fetchMachine = createMachine({
id: 'Dog API',
initial: 'idle',
context: {
dog: null
},
states: {
idle: {
on: {
FETCH: 'loading'
}
},
loading: {
invoke: {
id: 'fetchDog',
src: (context, event) =>
fetch('https://dog.ceo/api/breeds/image/random').then((data) =>
data.json()
),
onDone: {
target: 'resolved',
actions: assign({
dog: (_, event) => event.data
})
},
onError: 'rejected'
},
on: {
CANCEL: 'idle'
}
},
resolved: {
type: 'final'
},
rejected: {
on: {
FETCH: 'loading'
}
}
}
});
const dogService = interpret(fetchMachine)
.onTransition((state) => console.log(state.value))
.start();
dogService.send('FETCH');
- Visualizer
- Why?
- Finite State Machines
- Hierarchical (Nested) State Machines
- Parallel State Machines
- History States
- Sponsors
Visualize, simulate, and share your statecharts in XState Viz!
Statecharts are a formalism for modeling stateful, reactive systems. This is useful for declaratively describing the behavior of your application, from the individual components to the overall application logic.
Read 📽 the slides (🎥 video) or check out these resources for learning about the importance of finite state machines and statecharts in user interfaces:
- Statecharts - A Visual Formalism for Complex Systems by David Harel
- The World of Statecharts by Erik Mogensen
- Pure UI by Guillermo Rauch
- Pure UI Control by Adam Solove
- Spectrum - Statecharts Community (For XState specific questions, please use the GitHub Discussions)
import { createMachine } from 'xstate';
const lightMachine = createMachine({
id: 'light',
initial: 'green',
states: {
green: {
on: {
TIMER: 'yellow'
}
},
yellow: {
on: {
TIMER: 'red'
}
},
red: {
on: {
TIMER: 'green'
}
}
}
});
const currentState = 'green';
const nextState = lightMachine.transition(currentState, 'TIMER').value;
// => 'yellow'
import { createMachine } from 'xstate';
const pedestrianStates = {
initial: 'walk',
states: {
walk: {
on: {
PED_TIMER: 'wait'
}
},
wait: {
on: {
PED_TIMER: 'stop'
}
},
stop: {}
}
};
const lightMachine = createMachine({
id: 'light',
initial: 'green',
states: {
green: {
on: {
TIMER: 'yellow'
}
},
yellow: {
on: {
TIMER: 'red'
}
},
red: {
on: {
TIMER: 'green'
},
...pedestrianStates
}
}
});
const currentState = 'yellow';
const nextState = lightMachine.transition(currentState, 'TIMER').value;
// => {
// red: 'walk'
// }
lightMachine.transition('red.walk', 'PED_TIMER').value;
// => {
// red: 'wait'
// }
Object notation for hierarchical states:
// ...
const waitState = lightMachine.transition({ red: 'walk' }, 'PED_TIMER').value;
// => { red: 'wait' }
lightMachine.transition(waitState, 'PED_TIMER').value;
// => { red: 'stop' }
lightMachine.transition({ red: 'stop' }, 'TIMER').value;
// => 'green'
const wordMachine = createMachine({
id: 'word',
type: 'parallel',
states: {
bold: {
initial: 'off',
states: {
on: {
on: { TOGGLE_BOLD: 'off' }
},
off: {
on: { TOGGLE_BOLD: 'on' }
}
}
},
underline: {
initial: 'off',
states: {
on: {
on: { TOGGLE_UNDERLINE: 'off' }
},
off: {
on: { TOGGLE_UNDERLINE: 'on' }
}
}
},
italics: {
initial: 'off',
states: {
on: {
on: { TOGGLE_ITALICS: 'off' }
},
off: {
on: { TOGGLE_ITALICS: 'on' }
}
}
},
list: {
initial: 'none',
states: {
none: {
on: { BULLETS: 'bullets', NUMBERS: 'numbers' }
},
bullets: {
on: { NONE: 'none', NUMBERS: 'numbers' }
},
numbers: {
on: { BULLETS: 'bullets', NONE: 'none' }
}
}
}
}
});
const boldState = wordMachine.transition('bold.off', 'TOGGLE_BOLD').value;
// {
// bold: 'on',
// italics: 'off',
// underline: 'off',
// list: 'none'
// }
const nextState = wordMachine.transition(
{
bold: 'off',
italics: 'off',
underline: 'on',
list: 'bullets'
},
'TOGGLE_ITALICS'
).value;
// {
// bold: 'off',
// italics: 'on',
// underline: 'on',
// list: 'bullets'
// }
const paymentMachine = createMachine({
id: 'payment',
initial: 'method',
states: {
method: {
initial: 'cash',
states: {
cash: { on: { SWITCH_CHECK: 'check' } },
check: { on: { SWITCH_CASH: 'cash' } },
hist: { type: 'history' }
},
on: { NEXT: 'review' }
},
review: {
on: { PREVIOUS: 'method.hist' }
}
}
});
const checkState = paymentMachine.transition('method.cash', 'SWITCH_CHECK');
// => State {
// value: { method: 'check' },
// history: State { ... }
// }
const reviewState = paymentMachine.transition(checkState, 'NEXT');
// => State {
// value: 'review',
// history: State { ... }
// }
const previousState = paymentMachine.transition(reviewState, 'PREVIOUS').value;
// => { method: 'check' }