Skip to main content
xriptxr

Tour

Info:Overlay
18

A guided sequence of Spotlights: point at one thing, say something, and move to the next with Back / Next / Skip and a progress readout.

walkthrough onboarding coachmark guide steps product tour spotlight next back

Live demo

live · @xtyle/astro

Tour

Point at things, in order

One spotlight, re-aimed step by step, with Back / Next / Skip and a progress readout. Each target stays live underneath.

GH

Grace's workspace

A step can point at something that is not on screen yet. beforeStep runs before the target is resolved, so the step makes what it needs first; the callout opens straight away either way and attaches the target the moment it appears.

prepared 0 times

The spotlight knobs are the tour's to set once for every step: dim how dark the veil goes, blur how far the page behind it softens, padding how much room the hole leaves around a target, and arrow whether the pointer bounces, holds still, or is dropped. scrollIntoView brings a target that is off screen to the middle before its callout opens. Any step can still override its own.

Everything begins here. The button still works while the tour points at it. Search across every thread, contact, and file. Your account and status live behind your avatar. Theme, shortcuts, and accounts. That's the tour. The drawer below is still closed. This button did not exist a moment ago; the step opened the drawer that holds it. dim at 0.9 and blur at 4px, so the page behind reads as out of focus rather than merely darkened. padding at 16px opens the hole well clear of the field, and arrow="static" points without animating. The tour sets the defaults; this step still takes its own pulse. Where a new message begins. Find anything, fast. Theme, shortcuts, accounts.

Tour is a Spotlight with more than one step. It owns the sequence — which step is showing, the Back / Next / Skip / Done buttons, and the progress readout — and drives a single composed <xtyle-spotlight> through it, so every step gets the same honest isolation: the page dims, a hole is cut over the target, and a callout points at it, with the target left live underneath.

Each step is an <xtyle-tour-step> carrying a target and, as its content, whatever the callout should say; any spotlight knob (heading, placement, shape, pulse, arrow, dim, blur, no-dismiss) set on a step overrides the Tour's default for that step alone. The Tour resolves each step's target against the page and hands the element to the spotlight directly, so a selector still finds a node the tour's own shadow root can't see, and the step-to-step focus handling — the part that goes wrong when a sequence is hand-rolled — is the spotlight's, already proven. The only chrome the Tour invents is the nav row; it renders through component.tour, so a mod can reshape Back / Next / Skip and the progress dots without touching the sequencing.

When to use

How this component composes with the rest of the set.

Give every step a target that resolves. A tour points at things; a step with nothing to point at has no callout to open, because the callout anchors to the target.
Set the common look on the Tour and only the exceptions on a step. placement, shape, and pulse cascade down; a step overrides just what it needs.
Reach for dots progress on a short tour and count on a long one — five dots read at a glance, fifteen don't.
Emit complete when the last step's Done is pressed and skip when the user bails; both are followed by close. Which of the two a returning user's flag keys on is the difference between a tour they finished and one they escaped, so persist against those rather than against close, and hand the answer back as taken. Where that flag lives is your app's call — xtyle reaches outside the page for nothing.
Pair noSkip with noDismiss for a tour that must be finished — but leave that for the rare step that truly can't be skipped, because a walkthrough with no exit is a trap.

Props

21 props, straight from the manifest.

PropTypeDefaultBindingsDescription
open boolean false
html svelte astro
Whether the tour is running. start(), next(), back(), go(), finish(), skip(), and close() are the imperative doors.
index number 0
html svelte astro
The step to open on, and the live position while running.
progress TourProgress
count dots none
count
html svelte astro
The progress readout. count prints "2 of 5", dots draws one dot per step, none shows nothing.
backLabel html: back-label string Back
html svelte astro
The back button's label.
nextLabel html: next-label string Next
html svelte astro
The advance button's label on every step but the last.
doneLabel html: done-label string Done
html svelte astro
The advance button's label on the last step.
skipLabel html: skip-label string Skip
html svelte astro
The skip button's label.
spec TourSpec
html svelte astro
A whole tour as data - { id, title, summary, steps } - in place of authoring TourStep children by hand. It materializes those children rather than rendering a second way, so a spec-driven tour behaves identically to a slotted one; the same items-mode/slotted-mode split tabs and accordion carry. Declaring a tour as data is what lets it be held in a variable, addressed by id from a help menu or a command palette, listed in a picker, or contributed by a mod - none of which markup living inside one screen can be. Set it as a property from JS, or as a JSON string in markup, the way Progress takes a ramp stop list.
beforeStep
property
(index: number) => void | Promise<void>
html svelte
Run before a step's target is resolved, and awaited when it returns a promise. This is the seam for a step that has to make what it points at rather than merely find it - open a collapsed panel, select a layer, switch a tool - because a target that does not exist yet cannot be measured, and step fires on arrival, which is already too late. Property only; it is a function.
targetTimeout html: target-timeout number 2000
html svelte astro
How long to keep watching for a step's target after the callout is already up, in milliseconds. The step never waits on this - it opens immediately against whatever resolves at that moment and attaches the target the frame it appears - so this only bounds how long a late target has to show up before target-missing says it never did.
taken boolean false
html svelte astro
Whether this tour has already been taken. The component reports complete and skip separately and keeps no memory of either: which storage holds that, under what key, whether it is per-user or per-device, and whether it resets when the tour's content changes are all the app's decisions. So this is the app telling the component, not the component guessing.
noSkip html: no-skip boolean false
html svelte astro
Drops the skip button, for a tour the app wants finished. Pair with noDismiss so the veil and Escape can't leave it either.
placement PopoverPlacement bottom
html svelte astro
The default callout side for every step, overridable per step.
shape SpotlightShape
auto rect circle
auto
html svelte astro
The default hole shape for every step, overridable per step.
pulse SpotlightPulse
none slow fast
html svelte astro
The default ring pulse for every step, overridable per step.
padding number 8
html svelte astro
The default breathing room between a target and the edge of the hole, in px. Overridable per step.
dim number 0.72
html svelte astro
How dark every step's veil goes, 0–1. Overridable per step.
blur number 0
html svelte astro
How far the page behind every step's veil blurs, in px. Nothing blurs at 0, which is the default: blur costs a compositor layer over the whole viewport. Overridable per step.
arrow SpotlightArrow
none static bounce
bounce
html svelte astro
The default pointer at each target: bounce animates toward it, static holds still, none draws nothing. Overridable per step.
scrollIntoView html: scroll-into-view boolean false
html svelte astro
Scrolls each step's target to the middle of the viewport before its callout opens. A step pointing at something off-screen is a solid scrim. Overridable per step.
noDismiss html: no-dismiss boolean false
html svelte astro
Makes every step's veil and Escape inert, so the tour advances only through its nav. Overridable per step.

Events

What the component emits, and what rides along on event.detail. The name in the first column is the one addEventListener takes.

EventDetailBindingsDescription
start { total }
html svelte astro
The tour opened on its first step.
step { index, total }
html svelte astro
The tour advanced to a step.
complete
html svelte astro
The last step's Done was pressed.
skip { index, total }
html svelte astro
The user left the tour early.
close
html svelte astro
The tour closed, for any reason. Fires after complete or skip.
target-missing svelte: ontargetmissing { index, target }
html svelte astro
A step's target selector never resolved, even after targetTimeout. The callout still opened, anchored to nothing - without this the step degrades in silence and reads exactly like a mistyped selector.

Methods

What you can call on the element itself — the half of the surface a prop or an event cannot express. A control driven by a gesture, or one whose state the platform owns, opens here. Astro renders on the server and hands back no instance, so no method is reachable from it.

MethodReturnsBindingsDescription
start(index?: number) void
html svelte
Open the tour on a step, defaulting to the first. Fires start, and runs beforeStep before the step's target is measured.
next() void
html svelte
Advance one step, finishing the tour if there is nowhere further to go.
back() void
html svelte
Step back one, stopping at the first rather than wrapping.
go(index: number) void
html svelte
Jump straight to a step by index — what a picker or a resumed walkthrough needs. Out-of-range is ignored rather than clamped, so a stale index cannot silently show the wrong step.
finish() void
html svelte
End the tour as completed. Fires complete, which is the half of the take-versus-replay distinction a taken store watches.
skip() void
html svelte
End the tour as abandoned. Fires skip, so a store can tell the user who bailed from the user who finished.
close() void
html svelte
Close the callout without judging it either way — neither completed nor skipped.

Appearance

States

running

.xtyle-tour:not([hidden])

A step is showing: the spotlight is open on the step's target and the nav is live.

Anatomy

The named parts that make up the component. A ::part() handle is reachable from your own stylesheet; the class beside it is the component's internal selector, which a shadow boundary keeps to itself.

tour

Success:::part(tour) .xtyle-tour

The controller. It paints nothing itself; it drives the composed spotlight and hosts the nav.

spotlight

Success:::part(spotlight) .xtyle-tour__spotlight

The composed <xtyle-spotlight> the tour re-points at each step — the veil, the ring, and the callout are all its.

nav

Success:::part(nav) .xtyle-tour__nav

The step controls in the callout's action row: Back, the progress readout, Skip, and Next / Done.

--space-1 --space-2

back

Success:::part(back) .xtyle-tour__back

Steps back a step. Hidden on the first step.

--fg-2 --state-hover --radius-sm --space-2 --space-3 --weight-semibold --ring

progress

Success:::part(progress) .xtyle-tour__progress

Which step you're on: 2 of 5 under count, a dot per step under dots, nothing under none.

--fg-2 --line-2 --accent --text-xs --radius-full --duration-fast --ease-standard

skip

Success:::part(skip) .xtyle-tour__skip

Ends the tour early. Hidden on the last step, where Done says the same thing.

--fg-2 --state-hover --radius-sm --space-2 --space-3 --weight-semibold --ring

next

Success:::part(next) .xtyle-tour__next

Advances a step, and becomes Done on the last one.

--accent --accent-fg --state-hover --state-press --radius-sm --space-2 --space-3 --weight-semibold --ring

Tokens & coverage

What the component consumes, checked live against what the algorithm produces.

Success:fully covered 18/18 consumed tokens produced default register: 310 tokens

Live coverage check against the xtyle-default register (derive(xtyleDefault, { anchors })coverComponent(manifest, register)). Every token this component consumes must be a key the algorithm produces.

--accent --accent-fg --border-thin --duration-fast --ease-standard --fg-1 --fg-2 --line-2 --radius-full --radius-sm --ring --space-1 --space-2 --space-3 --state-hover --state-press --text-xs --weight-semibold

Slots

default
html svelte astro

The steps, as <xtyle-tour-step> children. Each carries a target and holds the callout content for that step.

Accessibility

Each step is a Spotlight callout in its dialog posture: focus moves into the callout on open, and the step's heading names it.
The nav buttons are real buttons, reachable and operable from the keyboard; Back is removed from the tab order on the first step, and Skip on the last.
A veil click or Escape ends the tour as a skip — unless a step sets noDismiss, in which case only the nav moves it, and Back / Next / Skip must remain the way through.
The progress readout is decorative and aria-hidden; the step's heading and content carry what a screen reader needs.
The ring's pulse and the callout's motion honor prefers-reduced-motion, inherited from the spotlight.

Code

A tour declared as data

spec takes the whole walkthrough as { id, title, summary, steps } instead of TourStep children, and materializes the same steps — so a spec-driven tour behaves identically to a slotted one. That is what lets a tour be held in a variable, addressed by id from a help menu or a command palette, listed in a picker, or contributed by a mod. Keeping it inert data is also why there is no registry here: a table of your tours is a plain object, and it stays yours. taken is the other half — the component reports complete and skip and remembers neither, so where that is stored is your call.

<xtyle-tour id="first-run" spec='{
	"id": "first-run",
	"title": "Getting around",
	"steps": [
		{ "target": "#rail", "heading": "The rail", "body": "Everything hangs off this." },
		{ "target": "#canvas", "heading": "The canvas", "placement": "left" },
		{ "target": "#help", "heading": "Help", "body": "And the tours live here." }
	]
}'></xtyle-tour>

A three-step onboarding tour

Point at the compose button, the inbox, and settings in turn, with a dot per step.

<xtyle-tour id="onboard" progress="dots">
	<xtyle-tour-step target="#compose" heading="Start here" placement="bottom">
		This is where a new message begins.
	</xtyle-tour-step>
	<xtyle-tour-step target="#inbox" heading="Everything lands here" placement="right">
		Your inbox. Unread threads rise to the top.
	</xtyle-tour-step>
	<xtyle-tour-step target="#settings" heading="Make it yours" placement="left" shape="circle">
		Theme, shortcuts, and accounts live in settings.
	</xtyle-tour-step>
</xtyle-tour>

<script>
	document.getElementById("onboard").start();
</script>