---
title: Tour
description: Guided product tour with steps
visibility: guest
draft: false
---

# Tour

The `<x-wirekit::tour>` component creates a step-by-step product tour overlay. Each step is positioned near a target element using [Floating UI](https://floating-ui.com/) and includes navigation controls (Back / Next / Finish). The tour overlays a semi-transparent backdrop and can be dismissed with the Escape key.

## Usage

The demo below shows the tour attached to three target elements in a mock dashboard frame. Click **Start Tour** to run through three steps that appear at different positions. Click **Start Tour** again to restart.

:::preview{title="Tour Setup"}
<div style="position: relative; min-height: 28rem; padding: 1rem; background: var(--color-wk-bg-subtle);">
<x-wirekit::row gap="sm" align="start" justify="between" wrap>
<div data-tour-demo="create" style="background: var(--color-wk-bg-muted); padding: 0.5rem 1.25rem; border-radius: var(--radius-wk-sm); color: var(--color-wk-text-muted); font-size: 0.875rem;">+ Create</div>
<div data-tour-demo="header" style="background: var(--color-wk-bg-muted); padding: 0.5rem 1.25rem; border-radius: var(--radius-wk-sm); color: var(--color-wk-text-muted); font-size: 0.875rem;">Dashboard</div>
<div data-tour-demo="settings" style="background: var(--color-wk-bg-muted); padding: 0.5rem 1.25rem; border-radius: var(--radius-wk-sm); color: var(--color-wk-text-muted); font-size: 0.875rem;">Settings</div>
</x-wirekit::row>
<div style="position: absolute; bottom: 1rem; left: 1rem;">
<x-wirekit::button x-on:click="$dispatch('wirekit-tour-start-onboarding')" intent="neutral" surface="outline" size="sm">Start Tour</x-wirekit::button>
</div>
<x-wirekit::tour name="onboarding">
<x-wirekit::tour.step :index="0" target="[data-tour-demo='header']" placement="bottom">
<x-slot:title>Welcome</x-slot:title>
This is the dashboard. Here you can manage all your projects.
</x-wirekit::tour.step>
<x-wirekit::tour.step :index="1" target="[data-tour-demo='create']" placement="bottom-start">
<x-slot:title>Create a Project</x-slot:title>
Click here to create your first project.
</x-wirekit::tour.step>
<x-wirekit::tour.step :index="2" target="[data-tour-demo='settings']" placement="bottom-end">
<x-slot:title>Settings</x-slot:title>
Customize your workspace in the settings panel.
</x-wirekit::tour.step>
</x-wirekit::tour>
</div>
:::

:::source{language="blade"}
<div data-tour-demo="header" style="background: var(--color-wk-bg-muted); padding: 0.5rem 1.25rem; border-radius: var(--radius-wk-sm); color: var(--color-wk-text-muted); font-size: 0.875rem;">Dashboard</div>
<div data-tour-demo="create" style="background: var(--color-wk-bg-muted); padding: 0.5rem 1.25rem; border-radius: var(--radius-wk-sm); color: var(--color-wk-text-muted); font-size: 0.875rem;">+ Create</div>
<div data-tour-demo="settings" style="background: var(--color-wk-bg-muted); padding: 0.5rem 1.25rem; border-radius: var(--radius-wk-sm); color: var(--color-wk-text-muted); font-size: 0.875rem;">Settings</div>
<x-wirekit::button x-on:click="$dispatch('wirekit-tour-start-onboarding')" intent="neutral" surface="outline" size="sm">Start Tour</x-wirekit::button>
<x-wirekit::tour name="onboarding">
<x-wirekit::tour.step :index="0" target="[data-tour-demo='header']" placement="bottom">
<x-slot:title>Welcome</x-slot:title>
This is the dashboard. Here you can manage all your projects.
</x-wirekit::tour.step>
<x-wirekit::tour.step :index="1" target="[data-tour-demo='create']" placement="bottom-start">
<x-slot:title>Create a Project</x-slot:title>
Click here to create your first project.
</x-wirekit::tour.step>
<x-wirekit::tour.step :index="2" target="[data-tour-demo='settings']" placement="bottom-end">
<x-slot:title>Settings</x-slot:title>
Customize your workspace in the settings panel.
</x-wirekit::tour.step>
</x-wirekit::tour>
:::

### Viewport-Level Rendering

The tour overlay and step panels are rendered directly in `<body>` via Alpine's `x-teleport` — the same pattern used by Modal, Drawer, and other overlay components. This guarantees correct `position: fixed` positioning regardless of ancestor CSS (`transform`, `will-change`, `contain`, etc.) that would otherwise create a containing block and break Floating UI's coordinate math.

### Placement Suffixes

The base placements (`top`, `bottom`, `left`, `right`) center the popover on the target. Add `-start` or `-end` to align the popover's edge with the target's edge:

- `top-start` — popover above the target, **left edges aligned**
- `bottom-end` — popover below the target, **right edges aligned**

This is useful for targets near container edges where centering would push the popover out of bounds.

### Opening Without Flicker

Each `<x-wirekit::tour.step>` ships a CSS rule (`[data-wk-tour-step] { left: -9999px; top: -9999px }`) in `dist/wirekit.css` that parks the panel off-screen until Floating UI computes its real position and writes `left`/`top` as inline styles. Inline styles outrank stylesheet rules, so the computed values immediately override the `-9999px` fallback — the user never sees the panel flash at `(0, 0)`.

### Starting the Tour

The tour starts hidden. Dispatch a custom event to start it:

```blade
{{-- Alpine --}}
<button x-on:click="$dispatch('wirekit-tour-start-onboarding')">
    Start Tour
</button>

{{-- Livewire (server-side) --}}
$this->dispatch('wirekit-tour-start-onboarding');

{{-- Vanilla JS --}}
<script>
    window.dispatchEvent(new CustomEvent('wirekit-tour-start-onboarding'));
</script>
```

The event name follows the pattern `wirekit-tour-start-{name}`, where `{name}` is the `name` prop on the tour component.

### Step Placement

Each step is positioned relative to its `target` element using [Floating UI](https://floating-ui.com/). The `placement` prop controls the preferred position:

```blade
<x-wirekit::tour.step :index="0" target="#my-element" placement="top">
    Step content above the target element.
</x-wirekit::tour.step>

<x-wirekit::tour.step :index="1" target=".sidebar-nav" placement="right">
    Step content to the right of the target.
</x-wirekit::tour.step>
```

Available placements: `top`, `right`, `bottom` (default), `left`, plus their `-start` / `-end` edge-aligned variants (e.g. `top-start`, `bottom-end`). Floating UI will automatically flip to the opposite side if there is not enough space.

### Auto-Scroll

When a step becomes active, the target element is scrolled into view using `scrollIntoView({ behavior: 'smooth', block: 'center' })`. This ensures the user always sees the element being highlighted.

## Navigation

Each step renders built-in navigation controls:

- **Back** button — shown from step 2 onward; calls `prev()` to go back one step
- **Next** button — advances to the next step; label changes to **Finish** on the last step
- **Progress text** — displays "Step X of Y" (also announced to screen readers)

## Keyboard

| Key | Action |
| --- | --- |
| Escape | Dismiss the tour at any point |
| Tab | Move focus between Back, Next, and other focusable elements inside the step |

## Behavior

- **Teleport to body** — overlay, backdrop, and steps are rendered in `<body>` via `x-teleport`, escaping ancestor containing blocks
- **Overlay backdrop** — a fixed, full-screen overlay with `z-index` from `--z-wk-modal` sits behind the steps
- **Floating UI positioning** — each step is positioned 12px away from its target element
- **Step counting** — total steps are counted from `[data-wk-tour-step]` elements in the DOM at init
- **Visibility toggling** — step content uses `x-show="active"` with `x-cloak` so the DOM is present but hidden until the tour starts
- **Dismiss** — calling `dismiss()` or pressing Escape ends the tour immediately, resetting to step 0

## Props

### `<x-wirekit::tour>`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `name` | `string` | `'tour'` | Unique tour identifier — used in the start event name |
| `scope` | `string\|null` | `null` | Scoped personalization key |

### `<x-wirekit::tour.step>`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `target` | `string\|null` | `null` | CSS selector for the element this step points to (e.g. `#my-button`, `.sidebar`) |
| `placement` | `string` | `'bottom'` | Preferred position relative to the target: `top`, `right`, `bottom`, `left` |
| `index` | `int\|null` | `null` | Explicit zero-based step number. Left `null`, steps are auto-assigned in document order — pass an explicit index only to reorder. |
| `scope` | `string\|null` | `null` | Scoped personalization key |

### Named Slots

The `tour.step` component supports:

- **`title`** — optional named slot for the step heading (rendered as `<h3>`)
- **Default slot** — the step body content

```blade
<x-wirekit::tour.step :index="0" target="#btn">
    <x-slot:title>Step Heading</x-slot:title>
    Body text goes here.
</x-wirekit::tour.step>
```

## Sub-Components

| Component | Purpose |
| --- | --- |
| `tour.step` | Individual step popup with title, content, and navigation controls |

## Accessibility

- Steps: `role="dialog"`, `aria-modal="false"` (non-modal — the page is visible beneath the overlay)
- Each step: `aria-label="Tour step N"` provides an accessible name
- Backdrop: `aria-hidden="true"` (decorative overlay)
- Live region: `aria-live="polite"` announces "Step X of Y" on every step change
- Navigation buttons have visible text labels
- Focus ring visible on all interactive elements within steps

## Keyboard Interaction

| Key | Action |
|-----|--------|
| `Tab` | Move focus to the next step's primary action |
| `Enter` / `Space` | Trigger the focused action button (Next / Previous / Done) |
| `Escape` | Dismiss the tour |

## Pitfalls

- **Don't trigger a tour on every page load.** WCAG 2.2.2 (Pause, Stop, Hide) requires a way to dismiss recurring auto-content. Show only on first visit; persist the dismissal in `localStorage`.
- **Don't anchor tour steps to elements that may not exist.** If the target is conditionally rendered, the step renders floating. Use `:steps` callbacks that filter to elements present at runtime.

## Design Tokens

| Token | Used for |
| --- | --- |
| `--font-wk-sans` | Step font family |
| `--font-wk-heading-weight` | Step title weight |
| `--text-wk-sm` / `--text-wk-md` / `--text-wk-lg` | Body / step indicator / title font size |
| `--color-wk-text` | Step body text |
| `--color-wk-text-muted` | Step indicator + secondary text |
| `--color-wk-accent` | Primary action button background |
| `--color-wk-accent-fg` | Primary action button text |
| `--color-wk-accent-hover` | Primary action button hover |
| `--color-wk-bg-elevated` | Step panel background |
| `--color-wk-bg-subtle` | Secondary action button hover |
| `--color-wk-border` | Step panel border |
| `--color-wk-overlay` | Backdrop scrim color |
| `--color-wk-ring` | Button focus ring |
| `--ring-wk-width` | Focus ring width |
| `--border-wk-width` | Border width |
| `--radius-wk-sm` / `--radius-wk-md` / `--radius-wk-lg` | Step + button border radius |
| `--shadow-wk-lg` | Step panel drop shadow |
| `--gap-wk-sm` | Footer-button gap |
| `--padding-wk-x-sm` / `--padding-wk-x-md` / `--padding-wk-y-xs` / `--padding-wk-y-md` | Step + button padding |
| `--z-wk-modal` | Stacking context |

## Personalization

Override classes globally via `WireKit::personalize()`:

```php
use Pushery\WireKit\WireKit;

WireKit::personalize('tour.step', [
    'base' => 'fixed z-50 w-96 bg-white border rounded-xl shadow-2xl p-6',
]);
```

## Further Reading

- [WAI-ARIA Dialog Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/) — dialog semantics used by each tour step
- [Floating UI](https://floating-ui.com/) — positioning library used to anchor steps to target elements
- [MDN: `aria-live`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-live) — live region for step announcements
