Skip to main content

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

Events

Learn about events

Delayed events

Work with time-based transitions

Build docs developers (and LLMs) love