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
| Attribute | Values | Default | Description |
|---|---|---|---|
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
| Element | Required | Description |
|---|---|---|
<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
| Event | Detail | Description |
|---|---|---|
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
| Method | Description |
|---|---|
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
| Mode | Skip Allowed | Action Gate | Use Case |
|---|---|---|---|
passive | Yes | Optional | Informational tours, docs sites |
active | Yes | Required | Onboarding that validates user can perform tasks |
forced | No | Required | Compliance flows, mandatory training |
Keyboard Navigation
| Key | Action |
|---|---|
| Escape | Skip tour (passive/active modes only) |
| Tab | Cycle focus within the card (focus trap) |
| ArrowRight / ArrowDown | Next step |
| ArrowLeft / ArrowUp | Previous step |
| Home | First step |
| End | Last step |
Progressive Enhancement
The component follows a four-layer degradation strategy:
| Layer | Environment | Experience |
|---|---|---|
| 1 | No CSS, no JS | Ordered step list with headings and anchor links |
| 2 | CSS only | Styled guide card with step numbering |
| 3 | CSS + <details> | Collapsible guide with start button |
| 4 | CSS + JS | Interactive spotlight overlay with popover |
Accessibility
- Card uses
role="dialog"witharia-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-motiondisables all spotlight/card transitions
CSS Custom Properties
| Property | Default | Purpose |
|---|---|---|
--tour-backdrop-color | oklch(0% 0 0 / 0.5) | Overlay background |
--tour-spotlight-ring | var(--color-primary) | Spotlight outline color |
--tour-spotlight-padding | 8px | Space around highlighted element |
--tour-spotlight-radius | var(--radius-m) | Spotlight corner radius |
--tour-card-max-width | 22rem | Maximum card width |
--tour-card-min-width | 16rem | Minimum card width |
--tour-card-offset | var(--size-s) | Gap between spotlight and card |
--tour-transition-duration | var(--duration-normal) | Spotlight/card animation |