Tour

Guided product walkthroughs — a sequence of cards that point at real elements, spotlight them, and let people move on, go back, or leave.
1<TourDemo />

Anatomy

Describe a tour as an array of steps and drop in the root. With no children it renders the overlay plus the standard card.

1import { Tour } from "@raystack/apsara";
2
3const steps = [
4 { target: '[data-tour="search"]', title: "Search", content: "Find anything." },
5 { target: filtersRef, title: "Filter", content: "Narrow results.", side: "right" },
6 { title: "You're all set!", content: "Explore at your own pace." }, // no target → centered
7];
8
9<Tour steps={steps} defaultOpen />;

To compose the card yourself, pass the parts as children:

1<Tour steps={steps} defaultOpen>
2 <Tour.Overlay />
3 <Tour.Content>
4 <Tour.Title />
5 <Tour.Description />
6 <Tour.Progress />
7 <Tour.Prev />
8 <Tour.Next />
9 </Tour.Content>
10</Tour>

API Reference

Tour

The root. Holds the steps and the open/index state, resolves targets, emits events, and owns the actions. Renders a context provider — its children default to <Tour.Overlay /> and <Tour.Content />.

Prop

Type

Step

Each entry in the steps array.

Prop

Type

Tour.Content

The card. Accepts positioning defaults and either static children, a render function, or the default layout.

Prop

Type

Tour.Overlay

The dimmed backdrop with a spotlight cutout over the target.

Prop

Type

Tour.Progress

Renders the step counter, e.g. "2 of 5".

Prop

Type

Tour.Title / Tour.Description

Give the card its accessible name and description, falling back to the step's title and content.

Tour.Next / Tour.Prev / Tour.Skip / Tour.Close

Navigation buttons. Each runs your onClick first, then its action (next, prev, skip, stop). Tour.Next finishes the tour past the last step.

Actions

start, next, prev, go, skip, and stop. Reach them through actionsRef, or anywhere inside <Tour> with the useTour() hook. start is the only action that runs while the tour is closed.

Prop

Type

Events

Every lifecycle moment is reported to onEvent.

Prop

Type

Examples

Controlled with imperative controls

Control open and stepIndex, pass an actionsRef, and drive the tour from your own UI.

1const actionsRef = useRef<TourActions>(null);
2const [open, setOpen] = useState(false);
3const [index, setIndex] = useState(0);
4
5<Button onClick={() => actionsRef.current?.start()}>Take a tour</Button>
6
7<Tour
8 steps={steps}
9 open={open}
10 stepIndex={index}
11 actionsRef={actionsRef}
12 onOpenChange={(open, { status }) => {
13 setOpen(open);
14 if (!open) analytics.track("tour_end", { status });
15 }}
16 onStepChange={setIndex}
17/>;

Custom card with a render function

Leave out Tour.Next and advance from app code when the user does something — useful for action-gated steps (draw a shape, open a panel).

1<Tour steps={steps} open={open} stepIndex={index} actionsRef={actionsRef} disableOverlay>
2 <Tour.Content>
3 {({ step, index, totalSteps, isLastStep, actions }) => (
4 <Flex direction="column" gap={3}>
5 <Text weight="medium">{step.title}</Text>
6 <Text variant="secondary">{step.content}</Text>
7 <Flex justify="between" align="center">
8 <Text size="mini">{index + 1} of {totalSteps}</Text>
9 <Button size="small" onClick={isLastStep ? actions.stop : actions.next}>
10 {isLastStep ? "Done" : "Next"}
11 </Button>
12 </Flex>
13 </Flex>
14 )}
15 </Tour.Content>
16</Tour>;
17
18// advance the draw step once the app reports a shape on the map
19useEffect(() => {
20 if (open && index === 2 && store.hasShape) actionsRef.current?.go(3);
21}, [open, index, store.hasShape]);

Targets and spotlight

A target can be a CSS selector, an element, a React ref, or a function. Targets that mount after the tour starts are awaited automatically; if one never appears within targetTimeout the tour skips it (or stops, per targetNotFound). If a target unmounts mid-step — its dialog closes, its route changes — the tour recovers instead of leaving a broken overlay.

1const steps = [
2 { target: "#search", title: "Selector target" },
3 { target: searchRef, title: "Ref target", spotlightClicks: true },
4 { target: () => editor.getNode("aoi"), title: "Function target" },
5 { target: "#panel", spotlightTarget: "#panel-header", title: "Spotlight a child" },
6];

Motion between steps

The dimmed backdrop stays put for the whole tour — it never flashes away between steps. Only the spotlight cutout animates: it fades shut (dimming the old target), repositions while covered, and fades back open once the next target has scrolled into view and settled, so the hole never slides across the screen or opens at a position it is about to leave.

The transition prop controls only the popover card. By default (transition="fade") the card cross-fades with the spotlight. Set transition="move" to glide the card smoothly from one target to the next, which suits walkthroughs whose consecutive targets sit close together.

1<Tour steps={steps} defaultOpen transition="move" />

Both collapse to instant swaps under prefers-reduced-motion: reduce.

Accessibility

  • The card is built on a Base UI Popover, so Tour.Title and Tour.Description provide its accessible name and description and it is exposed as a dialog.
  • Focus moves into the card when a step opens and when the step changes, so keyboard users can reach the navigation buttons. Steps with spotlightClicks keep focus on the page so the highlighted element stays operable.
  • Escape ends the tour. Outside clicks and focus moves do not, so a step can keep focus on the element it highlights.
  • The tour is non-modal (modal={false}); it does not trap focus or lock scroll.
  • Motion — the spotlight cutout's fade, the card fade or glide, and scroll-into-view — is enabled only when prefers-reduced-motion: no-preference; otherwise steps swap instantly.