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.

<page-tour id="demo-tour" data-title="Getting Started" data-trigger="manual" data-mode="passive" data-persist="none" > <details class="page-tour-guide" open> <summary class="page-tour-summary"> <span>Getting Started Tour</span> <span class="page-tour-count">3 steps</span> </summary> <ol class="page-tour-list"> <li> <tour-step data-target="#site-header"> <h3>Header</h3> <p>The main navigation lives here.</p> </tour-step> </li> <li> <tour-step data-target="#search-input" data-placement="bottom"> <h3>Search</h3> <p>Search across all content.</p> </tour-step> </li> <li> <tour-step data-target="#theme-picker" data-placement="bottom"> <h3>Theme Picker</h3> <p>Change the visual style.</p> </tour-step> </li> </ol> <button class="page-tour-start-btn" type="button">Start Tour</button> </details> </page-tour>

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.

<page-tour data-title="Settings Tour" data-trigger="auto" data-mode="active" data-persist="session" > <tour-step data-target="#profile-name" data-action="input" data-action-hint="Type your name to continue" > <h3>Display Name</h3> <p>Update your name here.</p> </tour-step> <tour-step data-target="#save-btn" data-action="click" data-action-hint="Click Save to continue" > <h3>Save Settings</h3> <p>Click to apply your changes.</p> </tour-step> </page-tour>

Forced Tour (Compliance)

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

<page-tour data-title="Required Setup" data-trigger="auto" data-mode="forced" data-persist="local" > <tour-step data-target="#terms"> <h3>Terms of Service</h3> <p>Review the updated terms.</p> </tour-step> <tour-step data-target="#accept-checkbox" data-action="click" data-action-hint="Check the box to accept" > <h3>Accept Terms</h3> <p>You must accept to continue.</p> </tour-step> </page-tour>

Button-Triggered Tour

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

<!-- External trigger button --> <button data-tour="editor-tour" type="button"> Take a Tour </button> <!-- The tour --> <page-tour id="editor-tour" data-title="Editor Tour" data-trigger="button" data-mode="passive" > <tour-step data-target="#editor"> <h3>Editor</h3> <p>Write your content here.</p> </tour-step> <tour-step data-target="#sidebar"> <h3>Sidebar</h3> <p>Document properties and settings.</p> </tour-step> </page-tour>

Attributes

AttributeValuesDefaultDescription
data-title string Tour Tour name for aria-label and heading
data-trigger "auto", "manual", "button" manual How the tour is initiated
data-mode "passive", "active", "forced" passive Skip and action-gate behaviour
data-persist "none", "session", "local" session Where to store progress
data-persist-key string Storage key override (defaults to page path)
data-spotlight-padding number 8 Pixel padding around the spotlight rect
data-step number 0 Current step index (0-based), reflects state
data-active boolean Present while tour is running
data-complete boolean Present after tour finishes or is skipped

Required Structure

ElementRequiredDescription
<tour-step> yes Individual tour step — contains heading and description
<details class="page-tour-guide"> no Layer 3 collapsible wrapper (optional)
<button class="page-tour-start-btn"> no Start 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
const tour = document.querySelector('page-tour'); const toasts = document.querySelector('toast-msg'); tour.addEventListener('tour:start', (e) => { console.log('Tour started at step', e.detail.step); }); tour.addEventListener('tour:step', (e) => { console.log('Step', e.detail.step, e.detail.direction); }); tour.addEventListener('tour:action', (e) => { console.log('Action completed:', e.detail.action); }); tour.addEventListener('tour:complete', () => { toasts.show({ message: 'Tour complete!', variant: 'success' }); }); tour.addEventListener('tour:skip', (e) => { toasts.show({ message: 'Tour skipped.', variant: 'info' }); });

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
const tour = document.querySelector('page-tour'); // Start tour at step 0 tour.start(); // Navigate tour.next(); tour.prev(); tour.goto(2); // End tour tour.skip(); // fires tour:skip tour.stop(); // silent // Reset persistence tour.reset(); // For custom action gating tour.dispatchEvent( new CustomEvent('tour:action', { bubbles: false }) );

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