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
Visualize, simulate, inspect, 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)
Open in Stately Viz
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'
Open in Stately Viz
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'
Open in Stately Viz
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'
// }
Open in Stately Viz
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' }