Tour
A guided walkthrough that spotlights elements on the page, step by step.
Usage
import {
Tour,
TourActions,
TourContent,
TourDescription,
TourHeader,
TourProgressText,
TourTitle,
TourTrigger,
type TourStepType,
} from "@kanzo-tech/ui";const steps: TourStepType[] = [
{
id: "intro",
type: "dialog",
title: "Welcome to the board",
description: "A quick tour.",
actions: [{ label: "Start", action: "next" }],
},
{
id: "search",
type: "tooltip",
target: () => document.getElementById("tour-search"),
title: "Find the contract",
description: "Titles, regions and beasts all match here.",
actions: [
{ label: "Back", action: "prev" },
{ label: "Next", action: "next" },
],
},
];The content is written once and driven by the step data — TourTitle, TourDescription and
TourProgressText read from the active step themselves, so there is nothing to map over.
target is a function rather than a ref because it is called when the step is shown, not when
the tour is defined: the element may not have mounted yet, or may be inside a panel the
previous step just opened.
Anatomy
Four parts sit under TourContent and are exported for rearranging it. TourPositioner places the
step against its target; TourOverlay is the scrim, and is DialogOverlay under the tour's own
data-slot; TourSpotlight is the hole punched through that scrim around the target;
TourActionTrigger is the raw button behind TourNextStep, TourPreviousStep and TourClose,
for a step whose footer needs an action those three do not name.
Tour (owns the tour machine)
├── TourTrigger (starts it)
└── TourContent (portal + overlay + positioner + spotlight)
├── TourHeader
│ ├── TourTitle
│ └── TourDescription
├── DialogBody (a step body is Dialog's, not a tour part)
├── TourProgressText
├── TourActions (renders the step's `actions` array)
│ ├── TourPreviousStep
│ └── TourNextStep
└── TourCloseTourActions renders whatever the current step declares, mapping prev/dismiss to outline
buttons and next to a filled one. Use TourPreviousStep and TourNextStep directly, inside
your own DialogFooter, when a step needs its navigation laid out among other footer content —
TourActions already carries Ark's Tour.Control, which is a plain div of anatomy attributes,
so there is nothing a second tour-shaped footer part would wire up.
Step types
type | Behaviour |
|---|---|
dialog | Centred modal card with a backdrop. No target. |
tooltip | Anchored to target, which the spotlight cuts out of the backdrop. |
floating | Positioned on the page, unanchored. |
wait | Advances on an effect rather than a click. |
API Reference
Tour
| Prop | Type | Default |
|---|---|---|
steps | TourStepType[] | [] |
keyboardNavigation | boolean | — |
onStepChange | (details: { stepId: string | null }) => void | — |
onStatusChange | (details: { status: string }) => void | — |
lazyMount | boolean | true |
unmountOnExit | boolean | true |
Tour creates the tour machine internally, so unlike Ark's root it takes steps instead of a
tour instance. useTourContext() exposes that instance and handleStart to descendants.
useTourContext()
const { tour, handleStart } = useTourContext();Returns UseTourContextReturn — tour, Ark's machine api, and handleStart. Both are exported,
so a helper that takes what the hook returns can name it.
@ark-ui/react exports a useTourContext of its own, and it is a different hook. Ours is the
one bound to Tour, and it returns { tour, handleStart } rather than Ark's tour context. The name
matches the reference exactly, which is why it stays — but with both packages in scope the import
is worth reading twice: there is no error to tell them apart.
TourStepType
| Field | Type | Notes |
|---|---|---|
id | string | Required, unique. |
type | "dialog" | "tooltip" | "floating" | "wait" | See above. |
target | () => HTMLElement | null | Resolved when the step is shown. |
title / description | ReactNode | Rendered by TourTitle / TourDescription. |
actions | { label: string; action?: "next" | "prev" | "dismiss" | "skip" }[] | Footer buttons. |
placement | Placement | "center" | Relative to target. |
backdrop | boolean | Dim the page behind this step. |
TourContent
| Prop | Type | Default |
|---|---|---|
showCloseButton | boolean | true |
Like Dialog, TourTitle reserves inline-end padding whenever the close trigger is present, so
a long step title wraps instead of running under the X.