Tour
Overlays

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.

Use cases
  • First-use guidance for unfamiliar controls.
  • Guidance for a moved or changed feature.
  • Tasks explained through steps anchored to controls.
Alternatives

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

TourProps props
PropTypeDefaultDescription
stepsTourStep[]—Steps in display order.
openboolean—Controlled visibility. Pair with onOpenChange.
defaultOpenbooleanfalseStarts open when uncontrolled.
onOpenChange(open: boolean) => void—Fires on every open and close.
stepnumber—Controlled current step index. Use with onStepChange.
defaultStepnumber0Starting 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.
placementTourPlacement"bottom"Default card placement against the target. Per-step overrides win.
offsetnumber12Gap between the target and the card, in px.
maskbooleantrueThe dim over the rest of the page.
maskOpacitynumber0.550–1 dim strength.
maskColorstringvar(--m-color-bg-deep)Any CSS color.
spotlightbooleantrueHighlights the target by excluding it from the backdrop. False dims the full page.
spotlightPaddingnumber6Breathing room around the target inside the cut-out, in px.
spotlightRadiusnumber6Corner radius of the cut-out, in px.
spotlightRingbooleantrueHairline ring around the cut-out.
interactivebooleanfalseLets the reader use the highlighted element. Off, the cut-out swallows the click.
closeOnEscapebooleantrueEscape ends the tour.
closeOnMaskClickbooleanfalseA click on the dim ends the tour.
closeButtonbooleantrueClose button in the card header.
skipButtonbooleantrueSkip button in the footer, on every step but the last.
scrollIntoViewboolean | ScrollLogicalPosition"center"Scrolls each target into view, and where it lands.
keyboardbooleantrue← / → step through the tour while the card has focus.
progress"dots" | "count" | "bar" | "none""dots"How the reader is told where they are.
progressClickablebooleantrueDots jump to their step.
widthnumber320Card width in px.
targetTimeoutnumber1200How long to keep looking for a step's target, in ms.
labelsPartial<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.
maskClassNamestring—Class on the dim layer.

Steps

TourStep props
PropTypeDefaultDescription
idstring—Stable key. Defaults to the step's index.
targetstring | HTMLElement | RefObject<HTMLElement> | (() => HTMLElement | null)—What the step is about. Omit for a centered step.
titleReactNode—Card heading.
contentReactNode—Step content.
placementTourPlacement—Overrides the tour's placement for this step.
offsetnumber—Overrides the tour's offset for this step.
spotlightPaddingnumber—Overrides the tour's spotlightPadding.
spotlightRadiusnumber—Overrides the tour's spotlightRadius.
interactiveboolean—Lets the reader use this step's target.
maskboolean—Set false to drop the dim for this step.
widthnumber—Overrides the card width for this step.
nextLabelReactNode—Replaces 'Next' on this step.
backLabelReactNode—Replaces 'Back' on this step.
canAdvanceboolean—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.
actionsReactNode—Replaces this step's footer row.

Labels

TourLabels props
PropTypeDefaultDescription
nextReactNode"Next"Advance button.
backReactNode"Back"Previous button.
finishReactNode"Done"Advance button on the last step.
skipReactNode"Skip tour"Leave-early button.
closestring"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.

TourStepApi props
PropTypeDefaultDescription
stepTourStep—The step being shown.
indexnumber—Its position, zero-based.
totalnumber—Number of steps.
isFirstboolean—True on the first step.
isLastboolean—True on the last step.
targetHTMLElement | 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.

Four ways to point at somethingTSX
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.

Tour

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.

Tour (single step)

Drawing the card yourself

renderStep replaces card content. renderFooter replaces only the action row. Both receive TourStepApi for navigation and completion.

renderFooterTSX
<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; Escape ends it (unless closeOnEscape is off).
  • Focus is trapped in the card, except on an interactive step, where focus must reach the highlighted control.
  • The cut-out swallows pointer events unless the step is interactive.

Import

ImportTSX
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")}
/>;