Skip to main content

A tour guides users through product features with step-by-step overlays.

Loading...

Features

  • Supports different step types such as "dialog", "floating", "tooltip" or "wait".
  • Supports customizable content per step.
  • Wait steps for waiting for a specific selector to appear on the page before showing the next step.
  • Flexible positioning of the tour dialog per step.
  • Progress tracking shows users their progress through the tour.

Installation

Install the tour package:

npm install @zag-js/tour @zag-js/vue # or yarn add @zag-js/tour @zag-js/vue

Anatomy

Check the tour anatomy and part names.

Each part includes a data-part attribute to help identify them in the DOM.

Usage

Import the tour package:

import * as tour from "@zag-js/tour"

These are the key exports:

  • machine - State machine logic.
  • connect - Maps machine state to JSX props and event handlers.

Then use the framework integration helpers:

<script setup lang="ts"> import * as tour from "@zag-js/tour" import { useMachine, normalizeProps } from "@zag-js/vue" import { useId, computed, Teleport } from "vue" const steps: tour.StepDetails[] = [ { type: "dialog", id: "start", title: "Ready to go for a ride", description: "Let's take the tour component for a ride and have some fun!", actions: [{ label: "Let's go!", action: "next" }], }, { type: "dialog", id: "logic", title: "Statechart", description: `As an engineer, you'll learn about the internal statechart that powers the tour.`, actions: [ { label: "Prev", action: "prev" }, { label: "Next", action: "next" }, ], }, { type: "dialog", id: "end", title: "Amazing! You got to the end", description: "Like what you see? Now go ahead and use it in your project.", actions: [{ label: "Finish", action: "dismiss" }], }, ] const service = useMachine(tour.machine, { id: useId(), steps }) const api = computed(() => tour.connect(service, normalizeProps)) const open = computed(() => api.value.open && api.value.step) </script> <template> <div> <button @click="api.start()">Start Tour</button> <div id="step-1">Step 1</div> </div> <Teleport to="body" v-if="open"> <div v-if="api.step?.backdrop" v-bind="api.getBackdropProps()" /> <div v-bind="api.getSpotlightProps()" /> <div v-bind="api.getPositionerProps()"> <div v-bind="api.getContentProps()"> <div v-if="api.step?.arrow" v-bind="api.getArrowProps()"> <div v-bind="api.getArrowTipProps()" /> </div> <p v-bind="api.getTitleProps()">{{ api.step?.title }}</p> <div v-bind="api.getDescriptionProps()"> {{ api.step?.description }} </div> <div v-bind="api.getProgressTextProps()"> {{ api.getProgressText() }} </div> <div v-if="api.step?.actions" class="tour button__group"> <button v-for="action in api.step?.actions" :key="action.label" v-bind="api.getActionTriggerProps({ action })" > {{ action.label }} </button> </div> <button v-bind="api.getCloseTriggerProps()">X</button> </div> </div> </Teleport> </template>

Using step types

The tour machine supports different types of steps, allowing you to create a diverse and interactive tour experience. The available step types are defined in the StepType type:

  • "tooltip": Displays the step content as a tooltip, typically positioned near the target element.

  • "dialog": Shows the step content in a modal dialog centered on screen, useful for starting or ending the tour. This usually doesn't have a target defined.

  • "floating": Presents the step content as a floating element, which can be positioned flexibly on the screen. This usually doesn't have a target defined.

  • "wait": A special type that waits for a specific condition before proceeding to the next step.

const steps: tour.StepDetails[] = [ // Tooltip step { id: "step-1", type: "tooltip", placement: "top-start", target: () => document.querySelector("#target-1"), title: "Tooltip Step", description: "This is a tooltip step", }, // Dialog step { id: "step-2", type: "dialog", title: "Dialog Step", description: "This is a dialog step", }, // Floating step { id: "step-3", type: "floating", placement: "top-start", title: "Floating Step", description: "This is a floating step", }, // Wait step { id: "step-4", type: "wait", title: "Wait Step", description: "This is a wait step", effect({ next }) { // do something and go next // you can also return a cleanup }, }, ]

Configuring actions

Every step supports a list of actions that are rendered in the step footer. Use the actions property to define each action.

const steps: tour.StepDetails[] = [ { id: "step-1", type: "dialog", title: "Dialog Step", description: "This is a dialog step", actions: [{ label: "Show me a tour!", action: "next" }], }, ]

Changing tooltip placement

Use the placement property to define the placement of the tooltip.

const steps: tour.StepDetails[] = [ { id: "step-1", type: "tooltip", placement: "top-start", // ... }, ]

Hiding the arrow

Set arrow: false in the step property to hide the tooltip arrow. This is only useful for tooltip steps.

const steps: tour.StepDetails[] = [ { id: "step-1", type: "tooltip", arrow: false, }, ]

Hiding the backdrop

Set backdrop: false in the step property to hide the backdrop. This applies to all step types except the wait step.

const steps: tour.StepDetails[] = [ { id: "step-1", type: "dialog", backdrop: false, }, ]

Step Effects

Step effects are functions that are called before a step is opened. They are useful for adding custom logic to a step.

This function provides the following methods:

  • next(): Call this method to move to the next step.
  • goto(id): Jump to a specific step by id.
  • dismiss(): Dismiss the tour immediately.
  • show(): Call this method to show the current step.
  • update(details: Partial<StepBaseDetails>): Call this method to update the current step details (for example, after data is fetched).
const steps: tour.StepDetails[] = [ { id: "step-1", type: "tooltip", effect({ next, show, update }) { fetchData().then((res) => { // update the step details update({ title: res.title }) // then show show the step show() }) return () => { // cleanup fetch data } }, }, ]

Wait Steps

Wait steps are useful when you need to wait for a specific condition before proceeding to the next step.

Use the step effect function to perform an action and then call next() to move to the next step.

Note: You cannot call show() in a wait step.

const steps: tour.StepDetails[] = [ { id: "step-1", type: "wait", effect({ next }) { const button = document.querySelector("#button") const listener = () => next() button.addEventListener("click", listener) return () => button.removeEventListener("click", listener) }, }, ]

Showing progress dots

Use the api.getProgressPercent() to show the progress dots.

const ProgressBar = () => { const service = useMachine(tour.machine, { steps: [] }) const api = tour.connect(service, normalizeProps) return <div>{api.getProgressPercent()}</div> }

Tracking the lifecycle

As the tour is progressed, events are fired and you can track the lifecycle of the tour. Here's are the events you can listen to:

  • onStepChange: Fires when the current step changes.
  • onStepsChange: Fires when the steps array is updated.
  • onStatusChange: Fires when the status of the tour changes.
const Lifecycle = () => { const service = useMachine(tour.machine, { steps: [], onStepChange(details) { // => { stepId: "step-1", stepIndex: 0, totalSteps: 3, complete: false, progress: 0 } console.log(details) }, onStepsChange(details) { // => { steps: StepDetails[] } console.log(details.steps) }, onStatusChange(details) { // => { status: "started" | "skipped" | "completed" | "dismissed" | "not-found" } console.log(details.status) }, }) const api = tour.connect(service, normalizeProps) // ... }

Controlled current step

Use stepId and onStepChange for controlled navigation.

const service = useMachine(tour.machine, { steps, stepId, onStepChange(details) { setStepId(details.stepId) }, })

Programmatic tour control

Use the connected API to drive tour flow from code.

api.start() api.setStep("step-2") api.next() api.prev() api.updateStep("step-2", { title: "Updated title" })

Dismiss and interaction behavior

Configure dismissal and page interaction behavior with machine props.

const service = useMachine(tour.machine, { closeOnEscape: false, closeOnInteractOutside: false, preventInteraction: true, })

Customizing progress text

Use translations.progressText to customize the progress message.

const service = useMachine(tour.machine, { translations: { progressText: ({ current, total }) => `Step ${current} of ${total}`, }, })

Styling guide

Prerequisites

Ensure the box-sizing is set to border-box for the means of measuring the tour target.

* { box-sizing: border-box; }

Ensure the body has a position of relative.

body { position: relative; }

Overview

Each tour part has a data-part attribute that can be used to style them in the DOM.

[data-scope="tour"][data-part="content"] { /* styles for the content part */ } [data-scope="tour"][data-part="positioner"] { /* styles for the positioner part */ } [data-scope="tour"][data-part="arrow"] { /* styles for the arrow part */ } [data-scope="tour"][data-part="title"] { /* styles for the title part */ } [data-scope="tour"][data-part="description"] { /* styles for the description part */ } [data-scope="tour"][data-part="progress-text"] { /* styles for the progress text part */ } [data-scope="tour"][data-part="action-trigger"] { /* styles for the action trigger part */ } [data-scope="tour"][data-part="backdrop"] { /* styles for the backdrop part */ }

Step types

The tour component can render tooltip, dialog, or floating content. You can apply specific styles based on the rendered type:

[data-scope="tour"][data-part="content"][data-type="dialog"] { /* styles for content when step is dialog type */ } [data-scope="tour"][data-part="content"][data-type="floating"] { /* styles for content when step is floating type */ } [data-scope="tour"][data-part="content"][data-type="tooltip"] { /* styles for content when step is tooltip type */ } [data-scope="tour"][data-part="positioner"][data-type="dialog"] { /* styles for positioner when step is dialog type */ } [data-scope="tour"][data-part="positioner"][data-type="floating"] { /* styles for positioner when step is floating type */ }

Placement Styles

For floating type tours, you can style based on the placement using the data-placement attribute:

[data-scope="tour"][data-part="positioner"][data-type="floating"][data-placement*="bottom"] { /* styles for bottom placement */ } [data-scope="tour"][data-part="positioner"][data-type="floating"][data-placement*="top"] { /* styles for top placement */ } [data-scope="tour"][data-part="positioner"][data-type="floating"][data-placement*="start"] { /* styles for start placement */ } [data-scope="tour"][data-part="positioner"][data-type="floating"][data-placement*="end"] { /* styles for end placement */ }

Methods and Properties

Machine Context

The tour machine exposes the following context properties:

  • idsPartial<{ content: string; title: string; description: string; positioner: string; backdrop: string; arrow: string; }>The ids of the elements in the tour. Useful for composition.
  • stepsStepDetails[]The steps of the tour
  • stepIdstringThe id of the currently highlighted step
  • onStepChange(details: StepChangeDetails) => voidCallback when the highlighted step changes
  • onStepsChange(details: StepsChangeDetails) => voidCallback when the steps change
  • onStatusChange(details: StatusChangeDetails) => voidCallback when the tour is opened or closed
  • closeOnInteractOutsidebooleanWhether to close the tour when the user clicks outside the tour
  • closeOnEscapebooleanWhether to close the tour when the user presses the escape key
  • keyboardNavigationbooleanWhether to allow keyboard navigation (right/left arrow keys to navigate between steps)
  • preventInteractionbooleanPrevents interaction with the rest of the page while the tour is open
  • spotlightOffsetPointThe offsets to apply to the spotlight
  • spotlightRadiusnumberThe radius of the spotlight clip path
  • translationsIntlTranslationsThe translations for the tour
  • dir"ltr" | "rtl"The document's text/writing direction.
  • idstringThe unique identifier of the machine.
  • getRootNode() => ShadowRoot | Node | DocumentA root node to correctly resolve document in custom environments. E.x.: Iframes, Electron.
  • onPointerDownOutside(event: PointerDownOutsideEvent) => voidFunction called when the pointer is pressed down outside the component
  • onFocusOutside(event: FocusOutsideEvent) => voidFunction called when the focus is moved outside the component
  • onInteractOutside(event: InteractOutsideEvent) => voidFunction called when an interaction happens outside the component

Machine API

The tour api exposes the following methods:

  • openbooleanWhether the tour is open
  • totalStepsnumberThe total number of steps
  • stepIndexnumberThe index of the current step
  • stepStepDetailsThe current step details
  • hasNextStepbooleanWhether there is a next step
  • hasPrevStepbooleanWhether there is a previous step
  • firstStepbooleanWhether the current step is the first step
  • lastStepbooleanWhether the current step is the last step
  • addStep(step: StepDetails) => voidAdd a new step to the tour
  • removeStep(id: string) => voidRemove a step from the tour
  • updateStep(id: string, stepOverrides: Partial<StepDetails>) => voidUpdate a step in the tour with partial details
  • setSteps(steps: StepDetails[]) => voidSet the steps of the tour
  • setStep(id: string) => voidSet the current step of the tour
  • start(id?: string) => voidStart the tour at a specific step (or the first step if not provided)
  • isValidStep(id: string) => booleanCheck if a step is valid
  • isCurrentStep(id: string) => booleanCheck if a step is visible
  • nextVoidFunctionMove to the next step
  • prevVoidFunctionMove to the previous step
  • getProgressText() => stringReturns the progress text
  • getProgressPercent() => numberReturns the progress percent
Edit this page on GitHub