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

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. 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. Store a flag on close so a returning user isn't toured twice.
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

12 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 string Back
html svelte astro
The back button's label.
nextLabel string Next
html svelte astro
The advance button's label on every step but the last.
doneLabel string Done
html svelte astro
The advance button's label on the last step.
skipLabel string Skip
html svelte astro
The skip button's label.
noSkip 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.
noDismiss boolean false
html svelte astro
Makes every step's veil and Escape inert, so the tour advances only through its nav. Overridable per step.

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, with their selectors.

tour

.xtyle-tour

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

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

.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

.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

.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

.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

.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: 305 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 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>
<script lang="ts">
	import { Tour, TourStep, Button } from "@xtyle/svelte";

	let touring = $state(false);
</script>

<Button onclick={() => (touring = true)}>Take the tour</Button>

<Tour bind:open={touring} progress="count" oncomplete={() => (touring = false)}>
	<TourStep target="#compose" heading="Start here">Where a new message begins.</TourStep>
	<TourStep target="#inbox" heading="Everything lands here" placement="right">Your inbox.</TourStep>
	<TourStep target="#settings" heading="Make it yours" placement="left">Theme and accounts.</TourStep>
</Tour>
---
import { Tour, TourStep } from "@xtyle/astro";
---

<Tour id="onboard" progress="dots">
	<TourStep target="#compose" heading="Start here">Where a new message begins.</TourStep>
	<TourStep target="#inbox" heading="Everything lands here" placement="right">Your inbox.</TourStep>
	<TourStep target="#settings" heading="Make it yours" placement="left" shape="circle">Settings.</TourStep>
</Tour>

<script>
	const tour = document.getElementById("onboard") as HTMLElement & { start(): void };
	if (!localStorage.getItem("toured")) tour.start();
	tour.addEventListener("close", () => localStorage.setItem("toured", "1"));
</script>