Documentation Index Fetch the complete documentation index at: https://mintlify.com/statelyai/xstate/llms.txt
Use this file to discover all available pages before exploring further.
What are transitions?
Transitions define how a state machine moves from one state to another in response to events. They are the edges connecting states in your state machine diagram.
Basic transitions
Define transitions using the on property:
import { createMachine } from 'xstate' ;
const machine = createMachine ({
initial: 'idle' ,
states: {
idle: {
on: {
START: 'running'
}
},
running: {
on: {
STOP: 'idle'
}
}
}
});
When the machine is in the idle state and receives a START event, it transitions to the running state.
Transition targets
Simple target
A string specifying the target state:
states : {
idle : {
on : {
START : 'running' // Target state
}
}
}
Object notation
More control over the transition:
states : {
idle : {
on : {
START : {
target : 'running' ,
actions : () => console . log ( 'Starting!' )
}
}
}
}
Relative targets
Target sibling or child states:
const machine = createMachine ({
initial: 'auth' ,
states: {
auth: {
initial: 'login' ,
states: {
login: {
on: {
SUCCESS: 'dashboard' // Sibling state
}
},
dashboard: {}
}
}
}
});
Absolute targets
Target states using absolute paths with dot notation:
const machine = createMachine ({
id: 'app' ,
initial: 'auth' ,
states: {
auth: {
initial: 'login' ,
states: {
login: {
on: {
LOGOUT: '#app.auth.login' // Absolute path
}
}
}
}
}
});
Self-transitions
Transition to the same state:
states : {
active : {
on : {
REFRESH : 'active' // Self-transition
}
}
}
Self-transitions exit and re-enter the state, executing exit and entry actions.
Internal transitions
Stay in the same state without exiting:
states : {
active : {
on : {
UPDATE : {
actions : 'updateData' ,
// No target = internal transition
}
}
}
}
Guarded transitions
Conditional transitions using guards:
import { setup } from 'xstate' ;
const machine = setup ({
guards: {
isValid : ({ context , event }) => {
return event . value > 0 ;
}
}
}). createMachine ({
initial: 'idle' ,
context: { count: 0 },
states: {
idle: {
on: {
INCREMENT: {
guard: 'isValid' ,
target: 'counting' ,
actions : ({ context , event }) => {
context . count += event . value ;
}
}
}
},
counting: {}
}
});
When multiple transitions share the same event, guards determine which transition to take.
Multiple transitions
Multiple transitions for the same event:
import { setup } from 'xstate' ;
const machine = setup ({
guards: {
isAdult : ({ event }) => event . age >= 18 ,
isChild : ({ event }) => event . age < 18
}
}). createMachine ({
initial: 'checking' ,
states: {
checking: {
on: {
CHECK_AGE: [
{
guard: 'isAdult' ,
target: 'adult'
},
{
guard: 'isChild' ,
target: 'child'
}
]
}
},
adult: {},
child: {}
}
});
Transitions are evaluated in order. The first transition with a passing guard (or no guard) is taken.
Eventless transitions
Automatic transitions that occur immediately:
const machine = createMachine ({
initial: 'start' ,
states: {
start: {
always: 'end' // Immediately transition to 'end'
},
end: {}
}
});
With guards:
import { setup } from 'xstate' ;
const machine = setup ({
guards: {
isComplete : ({ context }) => context . progress >= 100
}
}). createMachine ({
initial: 'loading' ,
context: { progress: 0 },
states: {
loading: {
always: {
guard: 'isComplete' ,
target: 'complete'
}
},
complete: {}
}
});
Delayed transitions
Transitions that occur after a delay:
const machine = createMachine ({
initial: 'idle' ,
states: {
idle: {
on: {
START: 'waiting'
}
},
waiting: {
after: {
5000 : 'timeout' // Transition after 5 seconds
}
},
timeout: {}
}
});
With dynamic delays:
import { setup } from 'xstate' ;
const machine = setup ({
delays: {
customDelay : ({ context }) => context . delayMs
}
}). createMachine ({
initial: 'waiting' ,
context: { delayMs: 3000 },
states: {
waiting: {
after: {
customDelay: 'done'
}
},
done: {}
}
});
Transition actions
Execute side effects during transitions:
import { setup , assign } from 'xstate' ;
const machine = setup ({
actions: {
logTransition : () => console . log ( 'Transitioning' ),
incrementCount: assign ({
count : ({ context }) => context . count + 1
})
}
}). createMachine ({
initial: 'idle' ,
context: { count: 0 },
states: {
idle: {
on: {
START: {
target: 'running' ,
actions: [ 'logTransition' , 'incrementCount' ]
}
}
},
running: {}
}
});
Wildcard transitions
Handle any event:
states : {
active : {
on : {
'*' : {
actions : () => console . log ( 'Unknown event received' )
}
}
}
}
Wildcard transitions match any event not explicitly handled. Use them carefully.
Forbidden transitions
Explicitly forbid certain events:
states : {
final : {
on : {
'*' : undefined // Forbid all events
},
type : 'final'
}
}
Checking possible transitions
Check if an event can be sent:
const snapshot = actor . getSnapshot ();
if ( snapshot . can ({ type: 'START' })) {
console . log ( 'Can start' );
actor . send ({ type: 'START' });
} else {
console . log ( 'Cannot start from current state' );
}
Best practices
Keep transition logic simple. Complex logic should be in actions or guards.
Use descriptive event names that clearly indicate what happened, like USER_LOGGED_IN instead of SUCCESS.
Avoid wildcard transitions unless absolutely necessary. They can make state machines harder to understand.
Next steps
Guards Add conditional logic to transitions
Actions Execute side effects
Delayed events Work with time-based transitions