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

to navigate · Enter to open · Esc to close

Documentation

Dynamic targets

Point Steps at elements that appear late, or never appear at all.

A selector-based Step assumes its target is in the DOM when the Tour reaches it. Turbo Frames, modals, and lazily rendered panels break that assumption. Driver.js 1.8 gives two Step options for it, and UX Driver passes both through.

Wait for an element

waitForElement takes a timeout in milliseconds. The Tour pauses on that Step until the selector matches or the timeout expires:

Twig builder

{% set tour = create_tour('invoice-tour')
    .addStep('#open-invoice', 'Invoices', 'Open one to see the preview')
    .addStep('#invoice-preview', 'Preview', 'This renders after the modal opens', options: {
        waitForElement: 2000,
    }) %}

<button type="button" {{ ux_tour(tour) }} {{ ux_tour_action('start') }}>
    Start tour
</button>

PHP builder

$tour = $this->tourBuilder->create('invoice-tour')
    ->addStep('#open-invoice', 'Invoices', 'Open one to see the preview')
    ->addStep('#invoice-preview', 'Preview', 'This renders after the modal opens', options: [
        'waitForElement' => 2000,
    ]);

Skip a missing target

skipMissingElement lets the Tour move on rather than stall when a selector never matches — the shape for Steps that only exist for some roles:

Twig builder

{% set tour = create_tour('admin-tour')
    .addStep('.orders', 'Orders', 'Every account sees this')
    .addStep('.export', 'Export', 'Only admins see this control', options: {
        skipMissingElement: true,
    }) %}

<button type="button" {{ ux_tour(tour) }} {{ ux_tour_action('start') }}>
    Start tour
</button>

PHP builder

$tour = $this->tourBuilder->create('admin-tour')
    ->addStep('.orders', 'Orders', 'Every account sees this')
    ->addStep('.export', 'Export', 'Only admins see this control', options: [
        'skipMissingElement' => true,
    ]);

In PHP the alternative is to not add the Step at all — if ($this->isGranted('ROLE_ADMIN')) around the addStep() call is often clearer than shipping a Step the Tour then has to skip.

Without it, a missing target leaves the Tour waiting on that Step with nothing to highlight.

Refresh after a layout shift

Neither option helps once a Tour is already running and the page moves under it — a Turbo render, a modal animation, a lazily loaded image. refresh recomputes the overlay and popover position against the current DOM:

<button {{ ux_tour_action('refresh') }}>Refresh tour</button>
<button {{ ux_hints_action('refresh') }}>Refresh hints</button>

Hints

Hints expose neither waitForElement nor skipMissingElement through this bundle. Either render Hint targets before the Hints controller connects, or start the group with :autostart="false" and fire show once the targets exist. Control and persistence has both actions.