Tour
Highlights page elements and displays step instructions.
Component behavior
Tour highlights one target per step over a backdrop. Targets are resolved again for several frames after onEnter. Missing targets produce a centered step. The highlight tracks scrolling, resizing, and rerenders.
- First-use guidance for unfamiliar controls.
- Guidance for a moved or changed feature.
- Tasks explained through steps anchored to controls.
Stepper: the steps are the task’s main content.
Callout: one note belongs beside a specific control.
HoverCard / Tooltip: the explanation should appear on demand.
EmptyState: the view has no data and needs an initial action.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
steps | TourStep[] | — | Steps in display order. |
open | boolean | — | Controlled visibility. Pair with onOpenChange. |
defaultOpen | boolean | false | Starts open when uncontrolled. |
onOpenChange | (open: boolean) => void | — | Fires on every open and close. |
step | number | — | Controlled current step index. Use with onStepChange. |
defaultStep | number | 0 | Starting step, and where a re-opened tour restarts. |
onStepChange | (index: number, step: TourStep) => void | — | Fires on every move between steps. |
onFinish | () => void | — | The reader reached the end and pressed the last button. |
onSkip | (index: number, step: TourStep) => void | — | Called on Skip, Escape, or close-button dismissal. |
onEnd | (reason: TourEndReason, index: number) => void | — | Fires on either ending, after onFinish / onSkip. |
placement | TourPlacement | "bottom" | Default card placement against the target. Per-step overrides win. |
offset | number | 12 | Gap between the target and the card, in px. |
mask | boolean | true | The dim over the rest of the page. |
maskOpacity | number | 0.55 | 0–1 dim strength. |
maskColor | string | var(--m-color-bg-deep) | Any CSS color. |
spotlight | boolean | true | Highlights the target by excluding it from the backdrop. False dims the full page. |
spotlightPadding | number | 6 | Breathing room around the target inside the cut-out, in px. |
spotlightRadius | number | 6 | Corner radius of the cut-out, in px. |
spotlightRing | boolean | true | Hairline ring around the cut-out. |
interactive | boolean | false | Lets the reader use the highlighted element. Off, the cut-out swallows the click. |
closeOnEscape | boolean | true | Escape ends the tour. |
closeOnMaskClick | boolean | false | A click on the dim ends the tour. |
closeButton | boolean | true | Close button in the card header. |
skipButton | boolean | true | Skip button in the footer, on every step but the last. |
scrollIntoView | boolean | ScrollLogicalPosition | "center" | Scrolls each target into view, and where it lands. |
keyboard | boolean | true | ← / → step through the tour while the card has focus. |
progress | "dots" | "count" | "bar" | "none" | "dots" | How the reader is told where they are. |
progressClickable | boolean | true | Dots jump to their step. |
width | number | 320 | Card width in px. |
targetTimeout | number | 1200 | How long to keep looking for a step's target, in ms. |
labels | Partial<TourLabels> | — | Overrides the button and counter copy. |
renderStep | (api: TourStepApi) => ReactNode | — | Replaces the card's contents entirely. |
renderFooter | (api: TourStepApi) => ReactNode | — | Replaces the footer row only. |
maskClassName | string | — | Class on the dim layer. |
Steps
| Prop | Type | Default | Description |
|---|---|---|---|
id | string | — | Stable key. Defaults to the step's index. |
target | string | HTMLElement | RefObject<HTMLElement> | (() => HTMLElement | null) | — | What the step is about. Omit for a centered step. |
title | ReactNode | — | Card heading. |
content | ReactNode | — | Step content. |
placement | TourPlacement | — | Overrides the tour's placement for this step. |
offset | number | — | Overrides the tour's offset for this step. |
spotlightPadding | number | — | Overrides the tour's spotlightPadding. |
spotlightRadius | number | — | Overrides the tour's spotlightRadius. |
interactive | boolean | — | Lets the reader use this step's target. |
mask | boolean | — | Set false to drop the dim for this step. |
width | number | — | Overrides the card width for this step. |
nextLabel | ReactNode | — | Replaces 'Next' on this step. |
backLabel | ReactNode | — | Replaces 'Back' on this step. |
canAdvance | boolean | — | False disables Next until the reader has done what the step asked. |
onEnter | () => void | — | Called when the step is entered. Use to reveal a target inside a closed container. |
onLeave | () => void | — | Runs as the step is left, in either direction. |
actions | ReactNode | — | Replaces this step's footer row. |
Labels
| Prop | Type | Default | Description |
|---|---|---|---|
next | ReactNode | "Next" | Advance button. |
back | ReactNode | "Back" | Previous button. |
finish | ReactNode | "Done" | Advance button on the last step. |
skip | ReactNode | "Skip tour" | Leave-early button. |
close | string | "End tour" | Accessible name of the header close button. |
step | (index: number, total: number) => ReactNode | "3 of 7" | The counter line, when progress is 'count'. |
Render props
renderStep and renderFooter receive the same step state object.
| Prop | Type | Default | Description |
|---|---|---|---|
step | TourStep | — | The step being shown. |
index | number | — | Its position, zero-based. |
total | number | — | Number of steps. |
isFirst | boolean | — | True on the first step. |
isLast | boolean | — | True on the last step. |
target | HTMLElement | null | — | The resolved element, or null for a centered step. |
next | () => void | — | Advance, or finish on the last step. |
back | () => void | — | Step back. |
goTo | (index: number) => void | — | Jump to a step. |
skip | () => void | — | Calls onSkip and closes the tour. |
finish | () => void | — | Calls onFinish and closes the tour. |
Targets
target accepts a CSS selector, element, ref, or getter. Omitting it centers the step like a dialog. Use refs for application-owned elements or selectors for elements identified by stable attributes.
const steps: TourStep[] = [
{ target: "#deploy-button", title: "Ship it", content: "…" },
{ target: deployRef, title: "Ship it", content: "…" },
{ target: () => document.querySelector("[data-active-row]"), title: "…" },
{ title: "That is the tour", content: "…" }, // centered, no target
];Use onEnter when a step must reveal its target, such as opening a menu. Target resolution retries for targetTimeout milliseconds, defaulting to 1200.
Examples
A four-step tour
The panel below is ordinary markup with ids on three of its controls. The last step has no target, so it lands centered.
One pointed-at thing
A tour can contain one step. The example hides progress with progress="none", supplies labels, and enables interaction with the highlighted control.
Drawing the card yourself
renderStep replaces card content. renderFooter replaces only the action row. Both receive TourStepApi for navigation and completion.
<Tour
steps={steps}
progress="bar"
renderFooter={({ index, total, next, skip, isLast }) => (
<Inline gap="2" alignItems="center" style={{ padding: "var(--m-space-2)" }}>
<Text size="xs" color="fg-faint">
{index + 1} / {total}
</Text>
<Spacer />
<Button size="sm" variant="ghost" onClick={skip}>
Not now
</Button>
<Button size="sm" variant="primary" onClick={next}>
{isLast ? "Finish setup" : "Continue"}
</Button>
</Inline>
)}
/>Keyboard & accessibility
- The card is a
dialog, focused when the tour opens and after every move. ←and→step through it;Escapeends it (unlesscloseOnEscapeis off).- Focus is trapped in the card, except on an
interactivestep, where focus must reach the highlighted control. - The cut-out swallows pointer events unless the step is
interactive.
Import
import { Tour } from "@solarius/onset";
import type { TourStep } from "@solarius/onset";
const [open, setOpen] = useState(() => !localStorage.getItem("tour:seen"));
<Tour
steps={steps}
open={open}
onOpenChange={setOpen}
onEnd={() => localStorage.setItem("tour:seen", "1")}
/>;