Tour
Product tour: the page dims around a lit outline that glides from target to target, with a focus-trapped step card.
New in 1.7.Installs @floating-ui/react-dom and motion.
- Requests
- 1.24M
- last 24 h
- p95
- 182 ms
- −11 ms
- Errors
- 0.04%
- 38 total
Recent deployments
- Cache product images at the edgeada, 12 minLive
- Fix cart total rounding for JPYlin, 3 h
- Move checkout to the new APIada, yesterday
Arrow keys step through it, Escape skips.
Installation
npx shadcn@latest add https://neonkit.heyaadi.com/r/tour.jsonUsage
import { Tour } from "@/components/ui/tour"
const [open, setOpen] = React.useState(false)
<Tour
open={open}
onOpenChange={setOpen}
steps={[
{ target: "#deploy", title: "Ship from main", body: "Builds the latest commit in about two minutes." },
{ target: logsRef, title: "Logs stay for 14 days", side: "right" },
]}
/>Examples
Controlled, whole page
step and open are both controlled, so the links above jump to any step. The first step has no target, so its card sits in the middle of the screen.
One-step coachmark
A single step drops the count and Skip, and labels renames Done.
API reference
Tour
Renders nothing until open. Accepts div attributes for the card; the ref points at it.
| Prop | Type | Default |
|---|---|---|
steps | TourStep[]The stops, in order. | None |
open | booleanControlled open state. | None |
defaultOpen | booleanStarting open state when uncontrolled. | false |
onOpenChange | (open: boolean) => voidCalled on Skip, Escape and Done. | None |
step | numberControlled step, zero-based. | None |
defaultStep | numberWhere an uncontrolled tour starts. It starts there again each time it opens. | 0 |
onStepChange | (step: number) => voidCalled on Next, Back and the arrow keys. | None |
onComplete | () => voidNext on the last step. The tour then closes. | None |
onSkip | (step: number) => voidSkip or Escape, with the step it was on. | None |
container | RefObject<HTMLElement>Dims only this element instead of the page. Give it position: relative. The card still renders in the body, so it's never clipped. | None |
labels | { next?, back?, skip?, done? }Button text. | Next, Back, Skip tour, Done |
color | NeonColorOutline and button color. Falls back to the color where the tour was opened, then cyan. | inherited |
TourStep
| Prop | Type | Default |
|---|---|---|
target | stringA selector, ref, element or function. Leave it out, or point at a hidden element, for a centered card with no cutout. | None |
title | ReactNodeLabels the dialog. | None |
body | ReactNodeDescribes the dialog. | None |
side | "top"Where the card sits. It flips, then falls back to the other axis, when there isn't room. | "bottom" |
align | "start"Alignment along that side. | "center" |
padding | numberSpace between the target and the outline, in px. | 6 |
radius | numberCorner radius of the outline. | target's radius + padding |
Accessibility
- The card is a modal dialog labelled by the step's title and described by its body. A polite live region says "Step 2 of 5" as you move.
- Focus moves to Next when the tour opens, stays inside the card (Tab wraps), and returns to where it was when the tour closes.
- Escape skips. Left and Right go back and forward, unless focus is in a text field inside the card.
- Targets scroll into view when they're near an edge, and the outline follows them through scrolling and resizing. A target that mounts a frame late is waited for.
- The outline and the dimmed page are one SVG whose geometry is written straight to the DOM each frame of a glide, so stepping never re-renders the page or animates layout. The card is placed with floating-ui on the same frame.
- The outline strikes once when the tour opens. With reduced motion it appears without the flicker and jumps between steps.