shadcn.io is not affiliated with official shadcn/ui
React Aria does not include this primitive, so this page uses the Ark UI component. The visuals match the Ark UI version.
Steps that point at one control at a time. A step that never points at a control is just a dialog.
Use it when a first visit should show the next control. For one hint with no sequence, use a Tooltip. In React, React Aria follows the steps you pass.
Installation
Anatomy

TourContent
TourActionTrigger
TourClose
TourProgressText
TourTitle
TourDescription
TourPositioner
TourArrow
└── TourArrowTip
TourOverlay
TourSpotlightUsage
import React from "react";
import { Button } from "@/components/ui/button";
import {
Tour, TourContent, TourDescription, TourFooter, TourHeader,
TourNextStep, TourPreviousStep, TourProgressText, type TourStepType,
TourTitle, TourTrigger,
} from "@/components/ui/tour";const TourDemo = () => {
const installRef = React.useRef<HTMLButtonElement>(null);
const usageRef = React.useRef<HTMLDivElement>(null);
const steps = React.useMemo<TourStepType[]>(
() => [
{
actions: [{ action: "next", label: "Start tour" }],
description:
"This walkthrough stays inside the preview. Next steps highlight pieces of this mini app.",
id: "welcome",
title: "Welcome",
type: "dialog",
},
{
actions: [
{ action: "prev", label: "Back" },
{ action: "next", label: "Next" },
],
description: "Use the CLI or a manual install from this control.",
id: "install",
placement: "bottom",
target: () => installRef.current,
title: "Install",
type: "tooltip",
},
{
actions: [
{ action: "prev", label: "Back" },
{ action: "next", label: "Next" },
],
description:
"Usage notes live next to install so you can copy a snippet.",
id: "usage",
placement: "top",
target: () => usageRef.current,
title: "Usage",
type: "tooltip",
},
{
actions: [{ action: "dismiss", label: "Done" }],
description:
"The overlay, spotlight, and popover are clipped to this frame.",
id: "done",
title: "That’s it",
type: "dialog",
},
],
[]
);
return (
<Tour steps={steps}>
<div className="flex w-full max-w-lg flex-col overflow-hidden rounded-xl border bg-background shadow-sm">
<header className="flex items-center justify-between gap-3 border-b px-3 py-2.5">
<span className="font-semibold text-sm">Acme UI</span>
<div className="flex items-center gap-1">
<Button ref={installRef} size="sm" type="button" variant="ghost">
Install
</Button>
<TourTrigger asChild>
<Button size="sm">Start tour</Button>
</TourTrigger>
</div>
</header>
<div className="grid gap-3 p-4 sm:grid-cols-2">
<div className="rounded-lg border bg-muted/40 p-3">
<p className="font-medium text-sm">Install</p>
<p className="mt-1 text-muted-foreground text-xs">
Add the component with the CLI, then import it.
</p>
</div>
<div className="rounded-lg border bg-muted/40 p-3" ref={usageRef}>
<p className="font-medium text-sm">Usage</p>
<p className="mt-1 text-muted-foreground text-xs">
Compose steps, then call start() from a trigger.
</p>
</div>
</div>
</div>
<TourContent>
<TourHeader>
<TourProgressText />
<TourTitle />
<TourDescription />
</TourHeader>
<TourFooter>
<TourPreviousStep />
<TourNextStep />
</TourFooter>
</TourContent>
</Tour>
);
};
Accessibility
- Arrow Right
- Moves to the next step in the tour.
- Arrow Left
- Moves to the previous step in the tour.
- EscEscape
- Closes the tour.
- Enter
- Activate the focused control.
- Space
- Activate the focused control.
Demos
Async
The second step fetches a GitHub profile before it will appear. Until the response arrives the step stays hidden, still titled as loading. effect then updates the title and description with the name, the public repo count, and the follower count, and calls show. A failed request fills the step with an error instead of leaving it blank, and leaving the step aborts the fetch.
Custom Spacing
The step panel uses a tighter --space than the default, set on TourContent through the spacing scale. Header and footer padding both follow that variable, so one token restyles the panel. Use a smaller value when the tour is cramped inside a preview. The same variable can grow at a wider breakpoint without new styles on each part.
Events
A log under the targets records onStepChange with the step id and onStatusChange with the tour status, appending a line as you move forward, back, and finish. Use the callbacks when you need to remember that someone finished the walkthrough, or to keep another part of the page in sync with the current step. The log is your markup. The tour does not render one.
Keyboard Navigation
keyboardNavigation is turned on, and the line under the button says how: the right arrow advances, the left arrow goes back, and Escape closes. The steps are tooltips on three targets. Turn this on when people should move through the tour without aiming at Next. Leave it off when those arrow keys already do something else on the page.
Progress
Four tooltip steps, plus a bar along the bottom of the panel whose width is the tour progress percent. TourProgressText still prints the count above the title. Read the percent from the tour context when you want a graphic rather than only that count. The bar is your own element, pinned to the bottom of the content, and it does not ship with the component.
Skip
The early steps offer Skip beside Next. Skip is an action whose action is dismiss, so it closes the tour immediately. The last step says Finish and dismisses the same way. Add a skip when the walkthrough is optional, and keep Back on the later steps so closing is not the only way out.
Step Types
The opening and closing steps use type dialog: centered, with no target. Between them, a tooltip step anchors to the target element, and a floating step sits at placement bottom-end with no element at all. Use dialog for a welcome or a goodbye, tooltip when you are pointing at a control, and floating when the note should hold a corner while the page stays visible.
Wait For Click
Add, then Edit, then Delete, and none of those steps has a Next button. effect shows the step, waits for a click on that target, and only then moves on. Use this when the lesson is the click itself, not a paragraph about the click. The closing dialog is the first step that offers Finish, after all three buttons have been used.
Wait For Element
Clicking Add Item appends a row, and the next step stays hidden until that row exists. The wait looks for an element marked data-item new, and it gives up after five seconds. Use this when the next target is created by the previous action, such as a row or a panel that was not in the page yet. Pointing earlier would highlight nothing.
Wait For Input
The name step moves on only after the field has at least two characters. The email step waits for an address shape, and the terms step waits until the checkbox is checked. Each one listens for input or change and tests the element before advancing. Use this when continuing on an empty or invalid value would skip the lesson.
FAQ
How do I build a product tour or onboarding walkthrough in React?
Describe the tour as an array of steps, each with an id, a title, a description, a type and its actions, pass it to Tour, and start it with a TourTrigger, as the default demo does. Steps of type tooltip point at an element with target.
How do I highlight a specific element in a Tour step?
Give the step a target, a function that returns the element, and a placement such as bottom. The Tour scrolls it into view, spotlights it with an overlay and places the step next to it.
What step types does the Tour support?
Three, shown in the Step Types demo: dialog for a centered step with no target, like a welcome screen; tooltip for a step attached to an element; and floating for a step pinned to a corner of the screen.
How do I wait for the user to click a button before the next Tour step?
Use the step's effect: it receives next, target and show, so it can wait for an event and then call next. Ark's waitForEvent helper does the waiting, as the Wait For Click and Wait For Input demos do, and waitForElement waits for an element to appear, as the Wait For Element demo does.
How do I load data before showing a Tour step?
Do the work in the step's effect and call show once it is done, or update to change the step's content, as the Async demo does. The step stays hidden while it loads.
How do I let users skip or close the Tour?
Add an action with action set to dismiss, labeled Skip or Done, as the Skip demo does. Escape and a click outside also close it, unless you turn off closeOnEscape or closeOnInteractOutside.
API Reference
Tour
<div> data-slot="tour"
stepsTourStepDetails[]
The steps to display in the tour.
- Default
[]
dir?"ltr" | "rtl"
The document's text/writing direction.
- Type
"ltr" | "rtl"- Default
"ltr"
id?string
The unique identifier of the machine.
ids?object
The ids of the scroll area elements.
- Type
{ root?: string; viewport?: string; content?: string; scrollbar?: string; thumb?: string }
keyboardNavigation?boolean
Enable arrow key navigation between steps.
onStatusChange?(details: { status: string }) → void
Called when the tour status changes.
- Type
(details: { status: string }) → void- Parameters
details– { status: string }- Returns
void
onStepChange?(details: { stepId: string | null }) → void
Called when the current step changes.
- Type
(details: { stepId: string | null }) → void- Parameters
details– { stepId: string | null }- Returns
void
asChild?boolean
Use the provided child element as the default rendered element, combining their props and behavior.
data-overflow-x?string
Present when the content overflows the x-axis.
data-overflow-y?string
Present when the content overflows the y-axis.
--corner-heightstring
The height of the Root.
--corner-widthstring
The width of the Root.
--thumb-heightstring
The height of the slider thumb.
--thumb-widthstring
The width of the slider thumb.
TourContent
<div> data-slot="tour-content"
showCloseButton?boolean
Show close button at the top right corner.
- Default
true
asChild?boolean
Use the provided child element as the default rendered element, combining their props and behavior.
className?string
Merged with the part's classes.
data-overflow-x?string
Present when the content overflows the x-axis.
data-overflow-y?string
Present when the content overflows the y-axis.
TourTrigger
<button> data-slot="tour-trigger"
asChild?boolean
Use the provided child element as the default rendered element, combining their props and behavior.
data-overflow-x?string
Present when scrollbarXHidden is false.
data-overflow-y?string
Present when scrollbarYHidden is false.
TourActionTrigger
data-slot="tour-action-trigger"
asChild?boolean
Use the provided child element as the default rendered element, combining their props and behavior.
TourOverlay
data-slot="tour-overlay"
asChild?boolean
Use the provided child element as the default rendered element, combining their props and behavior.
className?string
Merged with the part's classes.
TourPositioner
data-slot="tour-positioner"
asChild?boolean
Use the provided child element as the default rendered element, combining their props and behavior.
className?string
Merged with the part's classes.
TourBody
data-slot="tour-body"
data-overflow-x?string
Present when scrollbarXHidden is false.
data-overflow-y?string
Present when scrollbarYHidden is false.
TourSpotlight
data-slot="tour-spotlight"
asChild?boolean
Use the provided child element as the default rendered element, combining their props and behavior.
className?string
Merged with the part's classes.
TourHeader
data-slot="tour-header"
data-overflow-x?string
Present when scrollbarXHidden is false.
data-overflow-y?string
Present when scrollbarYHidden is false.
TourTitle
data-slot="tour-title"
asChild?boolean
Use the provided child element as the default rendered element, combining their props and behavior.
className?string
Merged with the part's classes.
TourDescription
data-slot="tour-description"
asChild?boolean
Use the provided child element as the default rendered element, combining their props and behavior.
className?string
Merged with the part's classes.
TourProgressText
data-slot="tour-progress-text"
asChild?boolean
Use the provided child element as the default rendered element, combining their props and behavior.
className?string
Merged with the part's classes.
TourClose
data-slot="tour-close-trigger"
asChild?boolean
Use the provided child element as the default rendered element, combining their props and behavior.
TourFooter
asChild?boolean
Use the provided child element as the default rendered element, combining their props and behavior.
TourActions
asChild?boolean
Use the provided child element as the default rendered element, combining their props and behavior.
className?string
Merged with the part's classes.
TourPreviousStep
asChild?boolean
Use the provided child element as the default rendered element, combining their props and behavior.
data-overflow-x?string
Present when scrollbarXHidden is false.
data-overflow-y?string
Present when scrollbarYHidden is false.
TourNextStep
asChild?boolean
Use the provided child element as the default rendered element, combining their props and behavior.
data-overflow-x?string
Present when scrollbarXHidden is false.
data-overflow-y?string
Present when scrollbarYHidden is false.