Type to search Tours, Hints, options, and events.

to navigate · Enter to open · Esc to close

Documentation

Events

React to Tour and Hints lifecycle events, and cancel the ones that matter.

Driver.js exposes its lifecycle through callbacks, which a server-rendered Tour has no place to attach. UX Driver bridges them into DOM events on document instead, so application JavaScript can observe a Tour without ever constructing one.

The complete list — nine Tour events, six Hints events, every detail payload — is in the Stimulus reference. This page is about what to do with them.

Blocking a navigation

Five Tour events are cancelable: ux-driver:next, ux-driver:previous, ux-driver:close, ux-driver:done, and ux-driver:destroy-started. Calling preventDefault() on one stops the default behavior the controller would otherwise run — moveNext(), movePrevious(), destroy(), or completion.

That makes a Step into a gate:

document.addEventListener('ux-driver:next', (event) => {
  const { tourId, index } = event.detail

  if (tourId === 'checkout-tour' && index === 1 && !form.reportValidity()) {
    event.preventDefault()
  }
})

The Tour stays where it is, the form shows its own validation, and no Driver.js API was touched.

Reacting to progress

The non-cancelable events are for observation — analytics, lazy loading, syncing another panel:

document.addEventListener('ux-driver:highlighted', (event) => {
  const { tourId, index, element } = event.detail

  analytics.track('tour_step_viewed', { tour: tourId, step: index })
  element.scrollIntoView({ block: 'nearest' })
})

Every Tour event after connect carries the same shape: {tourId, index, step, element, driver}. The driver handle is the live Driver.js instance, which is the escape hatch for anything the bundle does not serialize.

Hints

Hints events are never cancelable — they report what happened, they do not gate it:

document.addEventListener('ux-driver:hint-dismiss', (event) => {
  const { groupId, hintId } = event.detail

  fetch('/preferences/hints', {
    method: 'POST',
    body: JSON.stringify({ group: groupId, hint: hintId }),
  })
})

That listener is also how a dismissal is made to last: the bundle does not persist dismissed Hints, so recording it server-side and re-rendering the group without that Hint is the durable version.

Timing

ux-driver:pre-connect fires before the Driver.js instance exists and carries the configuration about to be used — mutate it there if a Tour needs a runtime value. ux-driver:connect fires once the instance is built. ux-driver:empty fires when a controller connects with nothing to show, which usually means a selector matched no element.