Skip to main content
WireKit
Copy for LLM

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.

Tour Setup
+ Create
Dashboard
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 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:

{{-- 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.

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
<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():

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

Was this page helpful?

Thanks — that helps.

Voting requires cookies or local storage. What we store