Control and persistence
Start, drive, and remember Tours and Hints from Stimulus actions.
Tours and Hints each have their own Stimulus controller. Templates print the trigger with
ux_tour_action() and ux_hints_action(); the same attributes can be written by hand. Nothing here
requires application JavaScript.
Autostart
Tours default to autostart=false; Hints default to autostart=true. The asymmetry is deliberate: a Tour
takes the screen away from someone, a beacon does not.
Component
<twig:Driver:Tour id="welcome" :autostart="true">
...
</twig:Driver:Tour>
<twig:Driver:Hints id="help" :autostart="false">
...
</twig:Driver:Hints>Twig builder
{% set tour = create_tour('welcome') %}
{% set hints = create_hints('help') %}
{# A builder Tour always waits: give it a trigger. #}
<button {{ ux_tour(tour) }} {{ ux_tour_action('start') }}>Start tour</button>
{# Hints autostart unless the second argument says otherwise. #}
<div {{ ux_hints(hints, false) }}></div>PHP builder
$tour = $this->tourBuilder->create('welcome');
$hints = $this->hintsBuilder->create('help');autostart is the one setting with no builder method — it is decided when the template prints the
attributes, with ux_tour_action('start') for a Tour and ux_hints(hints, false) for a Hints group.
Builders keep the same defaults: ux_tour(tour) renders a Tour that waits, and ux_hints(hints)
autostarts unless you pass false as the second argument.
once
once is a UX Driver option, not a Driver.js one. With :once="true" on the component, .once() on the
Twig builder, or ->once() on the PHP builder, the controller writes ux-driver:seen:<id> to
window.localStorage and refuses to start that Tour again — autostart and manual triggers both return
early.
Component
<twig:Driver:Tour id="welcome" :once="true">
...
</twig:Driver:Tour>Twig builder
{% set tour = create_tour('welcome').once() %}PHP builder
$tour = $this->tourBuilder->create('welcome')->once();The flag is written at exactly one moment: when someone clicks the done button on the last Step. That narrow rule has consequences worth knowing:
- Closing the Tour, pressing Escape, or navigating away leaves it unseen, so it comes back next time.
- Calling
preventDefault()on the cancelableux-driver:doneevent also prevents the write. - If
localStorageis unavailable — private browsing, blocked cookies, a full quota — the controller catches the failure, treats the Tour as unseen, and shows it again. The page keeps working; the Tour simply stops being once-only.
Hints have no equivalent. Driver.js tracks dismissed beacons on the live instance only, and a reload brings them all back.
Restarting
start and highlight both destroy the current Driver.js instance before creating the next one, so a
second trigger always yields a clean Tour from Step one — unless moveTo sends it elsewhere.
Tour actions
<button {{ ux_tour_action('start') }}>Start tour</button>
<button {{ ux_tour_action('next') }}>Next</button>
<button {{ ux_tour_action('previous') }}>Previous</button>
<button {{ ux_tour_action('refresh') }}>Refresh position</button>
<button {{ ux_tour_action('destroy') }}>Close tour</button>
<button {{ ux_tour_action('moveTo', {index: 2}) }}>Go to step 3</button>
moveTo requires the index param and counts from zero. Omitting it does nothing.
Hints actions
<button {{ ux_hints_action('show') }}>Show hints</button>
<button {{ ux_hints_action('hide') }}>Hide hints</button>
<button {{ ux_hints_action('close') }}>Close the open popover</button>
<button {{ ux_hints_action('restoreAll') }}>Restore dismissed hints</button>
Four more actions take a hintId param. All eight, with their parameters, are in the
Hints reference.
Next
- Events — cancel a navigation, or react to one.
- Stimulus reference — every value, action, and event, in one table.