How It Works

Joyride runs as a state machine with two dimensions: tour status and step lifecycle.

Tour Status

The tour can also be paused or skipped from running — see transitions below.

While the tour is running, each step progresses through the lifecycle below.

Step Lifecycle

Both beacon and tooltip phases have a preceding _before phase — see full lifecycle below.

Tour Status

The tour moves through these statuses:

StatusMeaning
idleInitial state. No tour running.
waitingTour started but no steps are loaded yet. Transitions to running once steps are available.
readySteps are loaded, tour is ready to start.
runningTour is active and displaying steps.
pausedTour was stopped mid-way via controls.stop(). Can be resumed.
finishedAll steps completed.
skippedUser clicked Skip to exit early.

Transitions

  • run={true} or controls.start()running
  • controls.stop()paused
  • controls.start() from paused → running (resumes)
  • Last step completed → finished
  • Skip button → skipped
  • controls.reset() → back to ready
"run" vs "controls.start()"

The run prop is the declarative way to start and stop the tour.

Setting run={true} calls controls.start() internally, and run={false} calls controls.stop().

Both work with the component and the hook.

See STATUS for the constant values.

Step Lifecycle

Each step goes through these phases:

PhaseWhat happensWhen
initTarget lookup. before hook runs if defined.Always
readyTarget found and visible.Always
beacon_beforeScroll to target. Positioning calculated.skipBeacon is false
beaconBeacon displayed. Waiting for user click.Beacon visible
tooltip_beforeScroll to target. Positioning calculated.Always
tooltipTooltip displayed and interactive.Always
completeafter hook fires, tour advances.Always
Beacon phases

The beacon_before and beacon phases are skipped when skipBeacon is set, placement is center, or in continuous tours navigating with Next/Prev.

See LIFECYCLE for the constant values.

Event Sequence

As a step progresses through its lifecycle, Joyride fires events via the onEvent callback. Here's the typical sequence:

tour:start
step:before_hook    (only if step has a `before` hook)
step:before
scroll:start        (only if scrolling is needed)
scroll:end
beacon              (skipped in continuous mode with Next/Prev)
tooltip
step:after
step:after_hook     (only if step has an `after` hook)
... next step ...
tour:end

Error events (error:target_not_found, error) can fire at any point if the target is missing or a hook fails.

See the Events page for how to handle these in your code.

Controlled vs Uncontrolled

Uncontrolled (default)

Joyride manages the step index internally. The built-in buttons (Next, Back, Skip, Close) handle navigation. You can also use controls for programmatic navigation.

<Joyride
  continuous
  run={true}
  steps={steps}
  onEvent={(data, controls) => {
    // React to events, but Joyride handles navigation
  }}
/>

Controlled

Set stepIndex to take full control. You manage the index yourself via onEvent:

const [stepIndex, setStepIndex] = useState(0);
const [run, setRun] = useState(false);
 
<Joyride
  continuous
  run={run}
  stepIndex={stepIndex}
  steps={steps}
  onEvent={(data, controls) => {
    const { action, index, status, type } = data;
 
    if ([EVENTS.STEP_AFTER, EVENTS.TARGET_NOT_FOUND].includes(type)) {
      setStepIndex(index + (action === ACTIONS.PREV ? -1 : 1));
    } else if ([STATUS.FINISHED, STATUS.SKIPPED].includes(status)) {
      setRun(false);
    }
  }}
/>

The useJoyride hook provides a controls object for programmatic navigation in uncontrolled mode.

See it in action

The Controlled Tour demo shows a full implementation with multi-step state management.