Tour
Accessible, unstyled onboarding tour with positioned step content and a target spotlight.
| Package | @vizejs/ui/tour |
| Maturity | stable |
| Own the source | vize lib pull tour |
| Requires | context, controllable-state, dismissable-layer, focus-scope, id, portal, positioner, presence |
| Aliases | tour, product tour, onboarding, walkthrough, coachmark, guided tour |
| Covers | WAI-ARIA Dialog (non-modal), Ark UI Tour, Shepherd.js, Driver.js, Reactour |
Usage
import { Tour, TourRoot, TourArrow, TourClose, TourContent, TourDescription, TourNext, TourPrev, TourProgress, TourSpotlight, TourStep, TourTitle } from "@vizejs/ui/tour";
Or copy the source into your project with vize lib pull tour (see Source Distribution).
API
Tour<TStep extends TourStepDefinition>
Source: src/families/overlays/tour/tour-root.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
steps (required) |
readonly TStep[] |
required |
Ordered step definitions. The step type, including consumer-owned fields, is inferred and returned through slot state and v-model:step. |
id |
string | null |
undefined |
Consumer-owned Tour base id. null and undefined select a deterministic fallback. |
open |
boolean |
undefined |
Controlled open state. undefined selects uncontrolled behavior. |
defaultOpen |
boolean |
false |
Initial open state for uncontrolled use. |
step |
StepValue | null |
undefined |
Controlled current step value. undefined selects uncontrolled behavior. |
defaultStep |
StepValue |
undefined |
Initial step for uncontrolled use. undefined selects the first enabled step. |
placement |
Placement |
"bottom" |
Placement used by steps that do not declare their own. |
missingTarget |
TourMissingTargetBehavior |
"center" |
Behavior when a declared step target is not found on the client. skip continues in the navigation direction; center keeps the step and centers the content. |
scrollIntoView |
boolean |
true |
Scroll each resolved target into view. Motion is instant under prefers-reduced-motion. |
markTarget |
boolean |
true |
Mark the current target with data-vize-tour-target="active" for consumer highlighting. |
keyboardNavigation |
boolean |
true |
Let ArrowLeft and ArrowRight inside TourContent move between steps. |
dir |
TourDirection |
"ltr" |
Reading direction used to map horizontal arrow keys. |
beforeEnter |
TourBeforeEnter<TStep> |
undefined |
Hook run before any step becomes current, before the step's own beforeEnter. Receives the inferred step type. Resolving false cancels; rejecting emits navigation-error. While it is pending the root publishes data-pending and TourPrev/TourNext are disabled; a newer request aborts the pending one through signal. |
afterLeave |
TourAfterLeave<TStep> |
undefined |
Hook run after a step stops being current, including when the tour closes. |
messages |
Partial<TourMessages> |
undefined |
Overrides for default strings rendered by Tour parts. |
Events
| Event | Payload | Description |
|---|---|---|
update:open |
[value: boolean] |
Fired when the tour requests a controlled open value. |
open-change |
[value: boolean, previous: boolean, nativeEvent: Event | null] |
Fired after any distinct open-state request. |
update:step |
[value: StepValue] |
Fired when the tour requests a controlled step value. |
step-change |
[value: StepValue, previous: StepValue | null, nativeEvent: Event | null] |
Fired after any distinct step request. |
complete |
[nativeEvent: Event | null] |
Fired when TourNext is activated on the last step or complete() is called. |
dismiss |
[reason: TourDismissReason, nativeEvent: Event | null] |
Fired when an open tour closes without completing. |
navigation-cancel |
[value: StepValue, from: StepValue | null] |
Fired when a beforeEnter hook resolves false; the current step is kept. |
navigation-error |
[error: unknown, value: StepValue, from: StepValue | null] |
Fired when a beforeEnter hook throws or rejects; the current step is kept. |
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
TourSlotState<TStep> |
Compound Tour children. Receives the inferred current step and progress. |
Exposed
| Member | Type | Description |
|---|---|---|
complete |
— | |
dismiss |
— | |
first |
— | |
goTo |
— | |
id |
— | |
index |
— | |
last |
— | |
next |
— | |
open |
— | |
pending |
— | |
previous |
— | |
refresh |
— | |
setOpen |
— | |
start |
— | |
state |
— | |
step |
— | |
target |
— | |
targetState |
— | |
total |
— | |
value |
— |
TourRoot<TStep extends TourStepDefinition>
Source: src/families/overlays/tour/tour-root.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
steps (required) |
readonly TStep[] |
required |
Ordered step definitions. The step type, including consumer-owned fields, is inferred and returned through slot state and v-model:step. |
id |
string | null |
undefined |
Consumer-owned Tour base id. null and undefined select a deterministic fallback. |
open |
boolean |
undefined |
Controlled open state. undefined selects uncontrolled behavior. |
defaultOpen |
boolean |
false |
Initial open state for uncontrolled use. |
step |
StepValue | null |
undefined |
Controlled current step value. undefined selects uncontrolled behavior. |
defaultStep |
StepValue |
undefined |
Initial step for uncontrolled use. undefined selects the first enabled step. |
placement |
Placement |
"bottom" |
Placement used by steps that do not declare their own. |
missingTarget |
TourMissingTargetBehavior |
"center" |
Behavior when a declared step target is not found on the client. skip continues in the navigation direction; center keeps the step and centers the content. |
scrollIntoView |
boolean |
true |
Scroll each resolved target into view. Motion is instant under prefers-reduced-motion. |
markTarget |
boolean |
true |
Mark the current target with data-vize-tour-target="active" for consumer highlighting. |
keyboardNavigation |
boolean |
true |
Let ArrowLeft and ArrowRight inside TourContent move between steps. |
dir |
TourDirection |
"ltr" |
Reading direction used to map horizontal arrow keys. |
beforeEnter |
TourBeforeEnter<TStep> |
undefined |
Hook run before any step becomes current, before the step's own beforeEnter. Receives the inferred step type. Resolving false cancels; rejecting emits navigation-error. While it is pending the root publishes data-pending and TourPrev/TourNext are disabled; a newer request aborts the pending one through signal. |
afterLeave |
TourAfterLeave<TStep> |
undefined |
Hook run after a step stops being current, including when the tour closes. |
messages |
Partial<TourMessages> |
undefined |
Overrides for default strings rendered by Tour parts. |
Events
| Event | Payload | Description |
|---|---|---|
update:open |
[value: boolean] |
Fired when the tour requests a controlled open value. |
open-change |
[value: boolean, previous: boolean, nativeEvent: Event | null] |
Fired after any distinct open-state request. |
update:step |
[value: StepValue] |
Fired when the tour requests a controlled step value. |
step-change |
[value: StepValue, previous: StepValue | null, nativeEvent: Event | null] |
Fired after any distinct step request. |
complete |
[nativeEvent: Event | null] |
Fired when TourNext is activated on the last step or complete() is called. |
dismiss |
[reason: TourDismissReason, nativeEvent: Event | null] |
Fired when an open tour closes without completing. |
navigation-cancel |
[value: StepValue, from: StepValue | null] |
Fired when a beforeEnter hook resolves false; the current step is kept. |
navigation-error |
[error: unknown, value: StepValue, from: StepValue | null] |
Fired when a beforeEnter hook throws or rejects; the current step is kept. |
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
TourSlotState<TStep> |
Compound Tour children. Receives the inferred current step and progress. |
Exposed
| Member | Type | Description |
|---|---|---|
open |
boolean |
Whether the tour is open. |
state |
TourState |
Stable open state token. |
step |
Step | null |
Current step definition, or null when no enabled step exists. |
value |
TourStepValue<Step> | null |
Current step value, or null when no enabled step exists. |
index |
number |
Zero-based position of the current step among enabled steps, or -1. |
total |
number |
Number of enabled steps. |
first |
boolean |
Whether the current step is the first enabled step. |
last |
boolean |
Whether the current step is the last enabled step. |
targetState |
TourTargetState |
Resolution status of the current step target. |
pending |
boolean |
Whether a beforeEnter hook is pending. |
id |
string |
Root-owned base id for the Tour family. |
target |
Element | null |
Currently resolved target element, if any. |
start |
(event?: Event | null) => boolean |
Open the tour at the first enabled step. Reports whether a step could be entered. |
setOpen |
(value: boolean, event?: Event | null) => boolean |
Request an open state. Opening resumes at the current step. |
next |
(event?: Event | null) => boolean |
Move to the next enterable step, or complete the tour on the last step. |
previous |
(event?: Event | null) => boolean |
Move to the previous enterable step. |
goTo |
(value: TourStepValue<Step>, event?: Event | null) => boolean |
Move to an enabled step by value, regardless of target availability. |
dismiss |
(reason?: TourDismissReason, event?: Event | null) => boolean |
Close the tour with a dismissal reason. |
complete |
(event?: Event | null) => boolean |
Close the tour as completed. |
refresh |
() => void |
Re-resolve the current target, for targets rendered after the step became active. |
TourArrow
Source: src/families/overlays/tour/tour-arrow.vue
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
TourArrowSlotState |
Decorative arrow contents. Receives coordinates for optional custom drawing. |
Exposed
| Member | Type | Description |
|---|---|---|
element |
Readonly<ShallowRef<HTMLElement | null>> |
Template ref to the rendered root element. |
x |
— | |
y |
— |
TourClose
Source: src/families/overlays/tour/tour-close.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
disabled |
boolean |
false |
Disable this control. |
ariaLabel |
string |
undefined |
Accessible name when the visible label is not enough. |
reason |
TourCloseReason |
"close" |
Dismissal reason reported by TourRoot. Use skip for "Skip tour" controls. |
Events
| Event | Payload | Description |
|---|---|---|
click |
[nativeEvent: MouseEvent] |
Fired before dismissing the tour. Call preventDefault() to keep the tour unchanged. |
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
TourControlSlotState |
Control label. Receives position and availability state; defaults to the TourRoot messages label. |
TourContent
Source: src/families/overlays/tour/tour-content.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
forceMount |
boolean |
false |
Keep the content mounted while the tour is closed. |
to |
string | HTMLElement |
"body" |
CSS selector or element the tour layer is moved into. |
portalDisabled |
boolean |
false |
Render in place instead of teleporting. |
defer |
boolean |
true |
Keep content in place until the portal target exists, avoiding SSR mismatch. |
strategy |
PositionerStrategy |
"fixed" |
CSS positioning mode published on the floating host. |
offset |
number |
12 |
Gap on the main axis between the target and the content. |
collisionPadding |
number |
8 |
Viewport padding the content should not cross. |
arrowPadding |
number |
0 |
Inset kept between the arrow and content edges. |
flip |
boolean |
true |
Flip to the opposite side when the preferred side overflows more. |
shift |
boolean |
true |
Shift the content back into the viewport after flip. |
size |
boolean |
false |
Constrain the host and publish positioner available-size CSS variables. |
safeArea |
boolean |
false |
Keep content clear of safe-area insets. |
hide |
boolean |
false |
Mark the host hidden when the target leaves the viewport. |
updateOnScroll |
boolean |
true |
Recalculate while ancestors scroll. |
updateOnResize |
boolean |
true |
Recalculate when the document or visual viewport resizes. |
viewport |
Rect |
undefined |
Viewport used for flip, shift, and hide. |
modal |
boolean |
false |
Publish aria-modal="true" and contain focus inside the content. |
autoFocus |
boolean |
true |
Move focus to the content when the tour opens. |
restoreFocus |
boolean |
true |
Restore focus to the previously focused element when the tour closes. |
focusOnStepChange |
boolean |
true |
Move focus to the content whenever the current step changes. |
closeOnEscape |
boolean |
true |
Let Escape dismiss the tour with reason escape-key. |
closeOnPointerDownOutside |
boolean |
false |
Let pointer-down outside the content and target dismiss the tour. |
closeOnFocusOutside |
boolean |
false |
Let focus moving outside the content and target dismiss the tour. |
ariaLabel |
string |
undefined |
Accessible name when no TourTitle labels the content. |
ariaLabelledby |
string | null |
undefined |
Ids that label the content. null omits the default TourTitle id. |
ariaDescribedby |
string | null |
undefined |
Ids that describe the content. null omits the default TourDescription id. |
Events
| Event | Payload | Description |
|---|---|---|
open-auto-focus |
[event: FocusScopeAutoFocusEvent] |
Fired before automatic entry focus is applied. |
close-auto-focus |
[event: FocusScopeAutoFocusEvent] |
Fired before automatic focus restoration is applied. |
escape-key-down |
[event: DismissableLayerEscapeKeyDownEvent] |
Fired before Escape requests dismissal. |
pointer-down-outside |
[event: DismissableLayerPointerDownOutsideEvent] |
Fired before an outside pointer-down requests dismissal. |
focus-outside |
[event: DismissableLayerFocusOutsideEvent] |
Fired before outside focus movement requests dismissal. |
interact-outside |
[event: DismissableLayerInteractOutsideEvent] |
Fired before outside pointer or focus interaction requests dismissal. |
dismiss |
[event: DismissableLayerDismissEvent] |
Fired after an unprevented dismissal request, before the tour closes. |
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
TourContentSlotState |
Step content. Receives the current step, progress, and resolved placement. |
Exposed
| Member | Type | Description |
|---|---|---|
element |
HTMLDivElement | null |
Rendered dialog element while mounted. |
state |
TourState |
Stable open state token. |
placement |
TourContentPlacement |
Resolved placement, or center. |
focusContent |
(options?: FocusOptions) => void |
Move focus to the dialog element. |
update |
() => void |
Recompute the floating position against the current target. |
TourDescription
Source: src/families/overlays/tour/tour-description.vue
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
TourSlotState |
Description text. Receives the current step and progress. |
TourNext
Source: src/families/overlays/tour/tour-next.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
disabled |
boolean |
false |
Disable this control. |
ariaLabel |
string |
undefined |
Accessible name when the visible label is not enough. |
Events
| Event | Payload | Description |
|---|---|---|
click |
[nativeEvent: MouseEvent] |
Fired before moving to the next step or completing on the last step. Call preventDefault() to keep the tour unchanged. |
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
TourControlSlotState |
Control label. Receives position and availability state; defaults to the TourRoot messages label. |
TourPrev
Source: src/families/overlays/tour/tour-prev.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
disabled |
boolean |
false |
Disable this control. |
ariaLabel |
string |
undefined |
Accessible name when the visible label is not enough. |
Events
| Event | Payload | Description |
|---|---|---|
click |
[nativeEvent: MouseEvent] |
Fired before moving to the previous step. Call preventDefault() to keep the tour unchanged. |
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
TourControlSlotState |
Control label. Receives position and availability state; defaults to the TourRoot messages label. |
TourProgress
Source: src/families/overlays/tour/tour-progress.vue
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
TourProgressSlotState |
Progress text. Defaults to TourRoot messages.progress(current, total) (current / total). |
TourSpotlight
Source: src/families/overlays/tour/tour-spotlight.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
padding |
number |
4 |
Pixels added around the target box on every side. |
radius |
number |
0 |
Corner radius published as --vize-ui-tour-spotlight-radius, in pixels. |
interactive |
boolean |
false |
Whether the highlighted target stays interactive. Published as data-interactive so consumer CSS can cut a pointer hole; the spotlight never blocks input on its own. |
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
TourSpotlightSlotState |
Optional overlay contents, such as an SVG mask. Receives the padded target box. |
Exposed
| Member | Type | Description |
|---|---|---|
element |
HTMLDivElement | null |
Rendered overlay element while open. |
rect |
TourSpotlightRect | null |
Padded target box, or null. |
update |
() => void |
Re-measure the target box. |
TourStep
Source: src/families/overlays/tour/tour-step.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
value (required) |
string |
required |
Step value whose content this part renders. |
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
TourStepSlotState |
Content rendered only while this step is current; the hidden wrapper stays mounted. |
TourTitle
Source: src/families/overlays/tour/tour-title.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
as |
"h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "p" |
"h2" |
Heading element rendered for the step title. |
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
TourSlotState |
Title text. Receives the current step and progress. |
Behavior
Normative state x input -> outcome table for tour-root.vue, tour-content.vue,
tour-title.vue, tour-description.vue, tour-step.vue, tour-progress.vue,
tour-prev.vue, tour-next.vue, tour-close.vue, tour-arrow.vue, and
tour-spotlight.vue (@vizejs/ui/tour). Every row is proven by the named test.
| ID | State | Input | Outcome | Evidence |
|---|---|---|---|---|
| U1 | closed | render, then start() |
nothing but the root and hidden hosts render; starting opens a non-modal role="dialog" labelled and described by TourTitle/TourDescription ids |
stays closed until started, then wires an accessible non-modal dialog |
| U2 | open, targetless step | render | content is centered (data-placement="center", empty positioner style), the arrow is hidden, progress reads 1 / 3, and TourPrev is disabled |
stays closed until started, then wires an accessible non-modal dialog |
| U3 | open | TourNext / TourPrev | steps advance and retreat; resolved targets anchor the content, receive data-vize-tour-target="active", and scroll into view |
next and previous walk steps, anchor resolved targets, and complete on the last step |
| U4 | open, last step | TourNext | TourNext publishes data-last; activation closes the tour, emits complete, and unmarks the target |
next and previous walk steps, anchor resolved targets, and complete on the last step |
| U5 | open | step change | focus moves to the dialog so the new step is announced | next and previous walk steps, anchor resolved targets, and complete on the last step |
| U6 | opened from a launcher | close | focus returns to the element focused before the tour opened | restores focus to the launcher when the tour closes |
| U7 | controlled open / step |
navigation or close | requests are emitted, rendered state stays controlled until the parent accepts | controlled open and step emit requests without changing until the parent accepts |
| U8 | declared target not found | enter step, missingTarget="center" |
the step stays current with data-target="missing" and centered content; invalid selectors resolve as missing |
missing targets center by default and skip in the navigation direction when configured |
| U9 | declared target not found | enter step, missingTarget="skip" |
navigation continues in its direction; per-step missingTarget overrides the root and applies to the initial step after mount |
missing targets center by default and skip in the navigation direction when configured |
| U10 | disabled step | navigation / goTo |
disabled steps leave the sequence, progress totals, and goTo rejects them |
disabled steps leave the sequence and progress counts |
| U11 | open | ArrowRight / ArrowLeft / Escape | arrows navigate (mirrored for dir="rtl") except inside editable controls; Escape dismisses with escape-key unless closeOnEscape is false |
Escape dismisses with a reason and arrow keys navigate outside editable controls |
| U12 | open | TourClose click | preventDefault() in the click handler keeps the tour open; otherwise it dismisses with the control's reason (default close) |
close controls report their reason and preventDefault keeps the tour open |
| U13 | open | TourClose reason="skip" |
dismisses with skip and never emits complete |
skip controls dismiss with the skip reason |
| U14 | open, resolved target | render, resize, targetless step | TourSpotlight publishes padded --vize-ui-tour-target-{x,y,width,height} and radius, re-measures on resize, and clears them without a target |
spotlight publishes padded target geometry as custom properties |
| U15 | exposed instance | setOpen, goTo, refresh, complete, dismiss |
typed imperative controls work; refresh() resolves targets rendered after the step became current |
exposes typed imperative controls and re-resolves late targets on refresh |
| U16 | reduced motion / opt-outs | enter step | scrolling is instant under prefers-reduced-motion; scrollIntoView=false and markTarget=false leave the target untouched |
respects reduced motion when scrolling targets into view |
| U17 | missing provider | setup | compound parts fail closed with the shared context diagnostic | compound parts require a matching root provider |
| U18 | SSR | two isolated renders | markup is byte-identical, targets report pending, content is centered, and no geometry is emitted |
renders byte-identical open tour markup across isolated SSR requests |
| U19 | SSR, closed | render | the content host is hidden and no dialog is emitted | renders a closed tour without dialog content |
| U20 | hydration | mount over server HTML | server nodes are kept without diagnostics; targets resolve after mount | hydrates the open tour without replacement or diagnostics, then resolves targets |
| U21 | pure helpers | selectors, refs, getters, rects | target resolution never throws, step walks never wrap, and spotlight boxes clamp at zero | resolves selector, element, ref, and getter targets without throwing, walks enabled steps in one direction without wrapping, pads spotlight boxes and clamps negative sizes, maps placements, including the centered fallback, to side and alignment tokens, detects editable keyboard targets |
| U22 | open, async beforeEnter |
TourNext | the request stays pending (data-pending, pending, TourPrev/TourNext disabled); on true the step commits and its target is re-resolved |
async beforeEnter keeps navigation pending, disables controls, and re-resolves the target |
| U23 | open, beforeEnter |
hook returns/resolves false, throws, or rejects |
the step is kept; navigation-cancel(value, from) or navigation-error(error, value, from) is emitted and no step request is made |
beforeEnter false cancels and failures report navigation-error while keeping the step |
| U24 | pending beforeEnter |
newer navigation / dismissal | the stale hook's signal aborts and its settlement is ignored; the latest request wins |
a newer navigation aborts the pending hook and the latest request wins |
| U25 | open | step change / close | afterLeave({ step, to }) runs after the step stops being current, with to: null on close |
afterLeave runs after steps change and when the tour closes |
| U26 | messages |
render | TourProgress uses messages.progress(current, total); empty TourPrev/TourNext/TourClose slots fall back to the configured labels |
messages override progress and fallback control labels |
Notes
beforeEnterhooks (TourRoot prop first, then the step's own) run for navigation requests (start,setOpen(true), next/previous,goTo, missing-target skips), never while rendering; the initialdefaultOpen/controlled step does not run them. Synchronous hooks settle in the same call. Steps with hooks are always enterable so a hook may render askiptarget first.Steps are data (
stepsprop).TourRootis a generic SFC: the step type, including consumer-owned fields such astitle, is inferred fromstepsand returned through the root slot,v-model:step, and the exposed instance.TourSteprenders per-step template content only while itsvalueis current.Target resolution, marking, scrolling, and measurement run only on the client after mount; SSR and the hydration pass report
data-target="pending"and a centered placement.TourContent reuses the Positioner controller (collision-aware placement against the target), the dismissable layer (Escape and optional outside dismissal; the target is a branch), and the focus scope (entry focus on the dialog, optional containment when
modal, focus restoration). Centered steps clear the positioner host style; consumer CSS centers[data-vize-ui="tour-positioner"][data-placement="center"].TourSpotlight is decorative (
aria-hidden="true") and never blocks input on its own; use the published custom properties (for example withclip-pathor an SVG mask) to draw the highlight anddata-interactiveto decide pointer behavior.