Vanilla Breeze

page-tour

Progressive-enhancement guided tour with spotlight overlay, action gating, and keyboard navigation.

Overview

A guided page tour that highlights elements sequentially with a spotlight overlay and popover card. Follows the four-layer progressive enhancement stack — content is readable without CSS or JS.

Usage

Wrap <tour-step> elements inside <page-tour>. Each step targets a page element via data-target (a CSS selector).

Basic Tour with Layer 3 Fallback

When JS is unavailable, the <details> wrapper provides a collapsible guide with anchor links. The "Start Tour" button is auto-wired when the component upgrades.

Active Tour (Action-Gated)

Set data-mode="active" and data-action on steps to require user interaction before advancing. The Next button stays disabled until the action completes.

Forced Tour (Compliance)

Set data-mode="forced" to prevent skipping. No Skip button, Escape is disabled, and backdrop clicks are ignored.

Button-Triggered Tour

Use data-trigger="button" and add data-tour="tour-id" to any element to start the tour declaratively.

Attributes

AttributeValuesDefaultDescription
data-titlestringTourTour name for aria-label and heading
data-trigger"auto", "manual", "button"manualHow the tour is initiated
data-mode"passive", "active", "forced"passiveSkip and action-gate behaviour
data-persist"none", "session", "local"sessionWhere to store progress
data-persist-keystringStorage key override (defaults to page path)
data-spotlight-paddingnumber8Pixel padding around the spotlight rect
data-stepnumber0Current step index (0-based), reflects state
data-activebooleanPresent while tour is running
data-completebooleanPresent after tour finishes or is skipped

Required Structure

ElementRequiredDescription
<tour-step>yesIndividual tour step — contains heading and description
<details class="page-tour-guide">noLayer 3 collapsible wrapper (optional)
<button class="page-tour-start-btn">noStart button inside details (optional, auto-wired)

Events

EventDetailDescription
tour:start{ step }Fired once when tour begins
tour:step{ step, target, direction }Fired on each step change
tour:action{ step, action }Fired when a required action completes
tour:complete{ steps }Fired when last step is finished
tour:skip{ step }Fired when user skips the tour

JavaScript API

MethodDescription
tour.start(step?)Start tour, optionally at a given step index
tour.stop()Stop tour silently (no event)
tour.next()Advance to next step
tour.prev()Go to previous step
tour.goto(index)Jump to specific step (0-based)
tour.skip()Skip tour (fires tour:skip)
tour.reset()Clear persistence and reset to step 0

Tour Modes

ModeSkip AllowedAction GateUse Case
passiveYesOptionalInformational tours, docs sites
activeYesRequiredOnboarding that validates user can perform tasks
forcedNoRequiredCompliance flows, mandatory training

Keyboard Navigation

KeyAction
EscapeSkip tour (passive/active modes only)
TabCycle focus within the card (focus trap)
ArrowRight / ArrowDownNext step
ArrowLeft / ArrowUpPrevious step
HomeFirst step
EndLast step

Progressive Enhancement

The component follows a four-layer degradation strategy:

LayerEnvironmentExperience
1No CSS, no JSOrdered step list with headings and anchor links
2CSS onlyStyled guide card with step numbering
3CSS + <details>Collapsible guide with start button
4CSS + JSInteractive spotlight overlay with popover

Accessibility

  • Card uses role="dialog" with aria-modal="true"
  • An aria-live="polite" region announces step transitions
  • Focus is trapped within the card (relaxed during action-gated steps)
  • Focus returns to the original element when the tour ends
  • prefers-reduced-motion disables all spotlight/card transitions

CSS Custom Properties

PropertyDefaultPurpose
--tour-backdrop-coloroklch(0% 0 0 / 0.5)Overlay background
--tour-spotlight-ringvar(--color-primary)Spotlight outline color
--tour-spotlight-padding8pxSpace around highlighted element
--tour-spotlight-radiusvar(--radius-m)Spotlight corner radius
--tour-card-max-width22remMaximum card width
--tour-card-min-width16remMinimum card width
--tour-card-offsetvar(--size-s)Gap between spotlight and card
--tour-transition-durationvar(--duration-normal)Spotlight/card animation