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

to navigate · Enter to open · Esc to close

Documentation

First Tour

Build a product tour with Twig Components or the PHP builder, and start it from a button.

A Tour is an ordered list of Steps. Each Step points at one element and carries the popover text shown when the Tour reaches it. Here is the same three-Step Tour over an orders screen, written in each of the three authoring forms — a Twig Component tree, the Twig builder, and the PHP builder in a controller.

Component

<twig:Driver:Tour id="orders-tour" :once="true">
    <button type="button" {{ ux_tour_action('start') }}>
        Start tour
    </button>

    <twig:Driver:Step :order="1" title="Orders" description="Review current customer orders">
        <h1>Orders</h1>
    </twig:Driver:Step>

    <twig:Driver:Step :order="2" title="Filters" description="Narrow the list before export" side="bottom">
        <form class="filters">...</form>
    </twig:Driver:Step>

    <twig:Driver:Step :order="3" title="Done" description="Export the filtered result" side="left">
        <button type="button">Export</button>
    </twig:Driver:Step>
</twig:Driver:Tour>

Each <twig:Driver:Step> wraps the element it describes, so the Tour reads top to bottom like the page does.

Twig builder

{% set tour = create_tour('orders-tour')
    .addStep('.orders-title', 'Orders', 'Review current customer orders')
    .addStep('.filters', 'Filters', 'Narrow the list before export')
    .addStep('.export', 'Done', 'Export the filtered result', 'left')
    .once() %}

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

The builder addresses elements by selector, so the Steps do not have to sit inside the markup they explain.

PHP builder

namespace App\Controller;

use Pentiminax\UX\Driver\Builder\TourBuilder;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class OrderController extends AbstractController
{
    public function __construct(private readonly TourBuilder $tourBuilder)
    {
    }

    #[Route('/orders', name: 'orders')]
    public function index(): Response
    {
        $tour = $this->tourBuilder->create('orders-tour')
            ->addStep('.orders-title', 'Orders', 'Review current customer orders')
            ->addStep('.filters', 'Filters', 'Narrow the list before export')
            ->addStep('.export', 'Done', 'Export the filtered result', side: 'left')
            ->once();

        return $this->render('order/index.html.twig', ['tour' => $tour]);
    }
}

TourBuilder is an autowired service, so the constructor above is all the wiring there is. The template prints the same two attributes as the Twig builder:

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

Build the Tour in PHP when the Steps depend on the user, the data, or a feature flag — the whole Tour object is ordinary PHP, so it can be assembled in a service and unit-tested.

Both modes serialize the same Driver.js configuration for the same Stimulus controller. Authoring modes covers how to choose.

Starting the Tour

Tours wait for an explicit trigger. Either put ux_tour_action('start') on a control inside the Tour, as above:

<button {{ ux_tour_action('start') }}>Start tour</button>

…or set :autostart="true" and let the Tour run when the controller connects.

Tuning the popovers

showProgress is already on for Tours declared through UX Driver, so 1 of 3 appears without asking. Everything else — button labels, overlay color, stage padding, placement — is a Tour or Step option: