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 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.
Welcome
Create a Project
Settings
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 alignedbottom-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:
{{-- 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. The placement prop controls the preferred position:
<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>viax-teleport, escaping ancestor containing blocks - Overlay backdrop — a fixed, full-screen overlay with
z-indexfrom--z-wk-modalsits 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"withx-cloakso 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
<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
:stepscallbacks 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():
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 — dialog semantics used by each tour step
- Floating UI — positioning library used to anchor steps to target elements
- MDN:
aria-live— live region for step announcements