# Tour

> A React product tour that dims the page around the real UI, explains each step in a card, docks at the bottom on phones and adds hotspots.

- Type: React component (Overlays)
- Access: Pro (the source needs a Wingo UI Pro plan, see https://wingo-ui.com/pricing)
- Version: 1.0.0
- Install: `npx wingo-ui@latest add tour`
- Docs: https://wingo-ui.com/components/tour

## Interaction

Start the tour with its button or the shortcut and the page dims around the first target: a rounded spotlight hugs the element and a card beside it explains the step. Next, Back and Skip sit in the card; the progress dots can be clicked to jump. Each step scrolls its target into view first, then the spotlight glides there on a spring and the card fades in beside it; steps without a target show a centered card. Keys: Right arrow or Enter goes next, Left goes back, Escape skips (announced), Tab stays inside the card, and focus returns to where it was when the tour ends. Interactive steps let clicks and typing reach the highlighted element, and advanceOn click moves on when you press the real button. On a phone the card docks at the bottom above the safe area and the keyboard, its target is scrolled into the top half, and a swipe left or right pages the steps. With persistKey a finished or skipped tour is remembered and autoStart leaves it alone. TourHotspot is a pulsing dot on a new feature: tap or click it for a hint (a bottom card on phones); once seen it stays away. Reduced motion makes the spotlight jump and the cards fade.

## Installation

```bash
npx wingo-ui@latest add tour
```

Pro item: run `npx wingo-ui@latest login` once with a Pro account. The CLI adds the files, their dependencies and a version record for updates.

With shadcn: `npx shadcn@latest add @wingo-ui/tour` (needs the @wingo-ui registry with your API key in components.json)

Files:

- `components/ui/tour.tsx`

npm dependencies: `npm i @radix-ui/react-popover motion lucide-react`

Wingo UI dependencies (installed automatically):

- [Button](https://wingo-ui.com/components/button) ([Markdown](https://wingo-ui.com/components/button.md))
- [Kbd](https://wingo-ui.com/components/kbd) ([Markdown](https://wingo-ui.com/components/kbd.md))
- [Progress](https://wingo-ui.com/components/progress) ([Markdown](https://wingo-ui.com/components/progress.md))
- [useControllableState](https://wingo-ui.com/components/use-controllable-state) ([Markdown](https://wingo-ui.com/components/use-controllable-state.md))
- [useKeyboardInset](https://wingo-ui.com/components/use-keyboard-inset) ([Markdown](https://wingo-ui.com/components/use-keyboard-inset.md))
- [useMediaQuery](https://wingo-ui.com/components/use-media-query) ([Markdown](https://wingo-ui.com/components/use-media-query.md))
- [usePersistedState](https://wingo-ui.com/components/use-persisted-state) ([Markdown](https://wingo-ui.com/components/use-persisted-state.md))
- [useShortcut](https://wingo-ui.com/components/use-shortcut) ([Markdown](https://wingo-ui.com/components/use-shortcut.md))
- [Haptics](https://wingo-ui.com/components/haptics) ([Markdown](https://wingo-ui.com/components/haptics.md))
- [Motion presets](https://wingo-ui.com/components/motion) ([Markdown](https://wingo-ui.com/components/motion.md))
- [Tones](https://wingo-ui.com/components/tones) ([Markdown](https://wingo-ui.com/components/tones.md))
- [UIProvider](https://wingo-ui.com/components/ui-config) ([Markdown](https://wingo-ui.com/components/ui-config.md))

## Usage

```tsx
import { useRef } from "react"
import { Button } from "@/components/ui/button"
import { Tour, type TourStep } from "@/components/ui/tour"

export function ClientsTour() {
  const search = useRef<HTMLInputElement>(null)
  const invoice = useRef<HTMLButtonElement>(null)
  const steps: TourStep[] = [
    { id: "welcome", title: "Welcome to Northwind CRM", content: "We show you where everything is in 30 seconds." },
    { id: "search", target: search, title: "Find any client", content: "A name, an EIN or a city." },
    { id: "invoice", target: invoice, title: "Issue an invoice", content: "Press New invoice.", advanceOn: "click" },
  ]
  return (
    <Tour
      steps={steps}
      persistKey="northwind-crm:tour"
      autoStart
      shortcut="shift+h"
      labels={{ next: "Continue" }}
    >
      <input ref={search} placeholder="Search clients" />
      <Button ref={invoice}>New invoice</Button>
    </Tour>
  )
}
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `steps` | `TourStep[]` |  | The steps: { id, target? (selector, function or ref), title, content?, media?, side? (top / right / bottom / left / auto), align?, spotlightPadding?, spotlightRadius?, interactive?, advanceOn?: "click", beforeEnter?, nextLabel? }. A step without a target, or whose target is not found within 1s, shows a centered card. |
| `defaultOpen?` | `boolean` | `false` | Starts open. Use open / onOpenChange to control it, or start() from useTour / setApi. |
| `open?` | `boolean` |  | Controlled open state. Only one tour shows at a time; a second one waits for the first to close. |
| `onOpenChange?` | `(open: boolean) => void` |  | Called when the tour opens or closes. |
| `defaultStep?` | `number` | `0` | The step index it starts at. |
| `step?` | `number` |  | Controlled step index. |
| `onStepChange?` | `(step: number) => void` |  | Called with the new index on Next, Back, a dot, a swipe or goTo. |
| `onComplete?` | `() => void` |  | Called when the last step's Done is pressed (or finish()). |
| `onSkip?` | `() => void` |  | Called on Skip, the close button, Escape, an overlay click with closeOnOverlayClick, or skip(). |
| `persistKey?` | `string` | `""` | Local storage key that remembers the tour was completed or skipped. autoStart does not start a finished tour; start() still does, and reset() forgets it. |
| `autoStart?` | `boolean` | `false` | Opens once after hydration and a 600ms settle, unless persistKey says it was already finished. |
| `shortcut?` | `string` | `""` | A global key that starts the tour, in mod+k notation. Empty binds nothing; the demo passes "shift+h" (the showcase owns ?). |
| `spotlight?` | `boolean` | `true` | Dims the page with a rounded cutout around the target and blocks clicks elsewhere. false shows only the cards, with an arrow (a ring alone marks the target when the card is docked), and leaves the page usable (coachmarks). |
| `overlayOpacity?` | `number` | `0.55` | Strength of the dim. Dark mode adds 0.1 so the page still recedes behind the card. |
| `spotlightPadding?` | `number` | `8` | Room in px between the target and the edge of the cutout. Steps can override it. |
| `spotlightRadius?` | `number` | `14` | Corner radius of the cutout in px, capped at half the smaller side. Steps can override it. |
| `showProgress?` | `"dots" \| "fraction" \| "bar" \| "none"` | `"dots"` | How the card shows where you are: dots (clickable), 2 / 5, a thin bar, or nothing (screen readers still hear it). |
| `size?` | `"sm" \| "md"` | `"md"` | Card width 288 / 340px (never wider than the screen minus 24px), with matching padding and type. |
| `radius?` | `"none" \| "sm" \| "md" \| "lg" \| "full"` | `"lg"` | Card corners: 0, 12, 16 or 24px; full falls back to lg. The docked phone card keeps its 28px sheet top. |
| `tone?` | `"neutral" \| "brand" \| "success" \| "warning" \| "danger" \| "info"` | `"neutral"` | Color of the Next button and the progress. |
| `color?` | `string` | `""` | Any CSS color for the Next button and the progress; replaces the tone. |
| `showSkip?` | `boolean` | `true` | A Skip button in the card on every step but the last. |
| `showClose?` | `boolean` | `true` | The × in the card's corner (on phones it shows only when Skip is hidden). It skips the tour. |
| `keyHints?` | `boolean` | `true` | Shows the → key on the Next button on desktops. Hidden on touch screens. |
| `closeOnOverlayClick?` | `boolean` | `false` | A click on the dimmed area skips the tour. Off by default so a stray tap does not end it. |
| `scrollBehavior?` | `"smooth" \| "instant"` | `"smooth"` | How targets scroll into view (instant under reduced motion). Nested scrollers scroll too. |
| `mobileLayout?` | `"docked" \| "popover"` | `"docked"` | Below 768px the card docks at the bottom where the thumb is, with a 44px Next and swipe paging, and targets scroll into the top half. popover keeps the desktop card. |
| `docked?` | `boolean` | `false` | Docks the card at every width, e.g. inside a phone mockup. |
| `container?` | `Element \| null` |  | Where the overlay renders (default document.body). A transformed element becomes the frame: the dim, the spotlight and the docked card stay inside it. |
| `setApi?` | `(api: TourApi) => void` |  | Receives { start(stepId?), next(), previous(), goTo(index \| id), skip(), finish(), reset() } once mounted. |
| `labels?` | `Partial<TourLabels>` |  | Every string: next "Next", back "Back", skip "Skip tour", done "Done", close "Close tour", progress(i, n) "Step i of n", stepAnnounce(i, n, title), skipped "Tour closed", completed "Tour completed". DEFAULT_TOUR_LABELS has the English set. |
| `classNames?` | `Partial<Record<"overlay" \| "spotlight" \| "card" \| "media" \| "title" \| "content" \| "progress" \| "actions" \| "close" \| "handle", string>>` |  | Extra classes for the inner parts. |
| `children?` | `ReactNode` |  | Rendered in place; anything inside can read the tour with useTour(). |

## Parts

### TourHotspot (component)

A pulsing dot on a corner of its children (or of target) that opens a hint: title, content, placement ("top-end" | "top-start" | "bottom-end" | "bottom-start"), offset, side, align, persistKey (gone once seen), tone, color, pulse, open / defaultOpen / onOpenChange, onDismiss, responsive (a bottom card below 768px), labels { open, dismiss "Got it" }, classNames. Props: TourHotspotProps.

### useTour (hook)

The nearest tour's API plus { open, step, count, status }; null outside a Tour. Signature: () => TourState | null.

### DEFAULT_TOUR_LABELS (constant)

The English card strings: next, back, skip "Skip tour", done, close, progress and the screen reader announcements. Override keys through labels.

### DEFAULT_TOUR_HOTSPOT_LABELS (constant)

The hotspot strings: open "Show tip" (the dot's accessible name) and dismiss "Got it". Override them through TourHotspot labels.

### TourStep (type)

One step: id, target (selector, function or ref), title, content, media, side, align, interactive, advanceOn, beforeEnter and nextLabel.

### TourApi (type)

The controls setApi hands you: start(stepId?), next, previous, goTo(index or id), skip, finish and reset.

### TourHotspotProps (type)

The props of TourHotspot, for wrappers that render hints from your own data.

## Examples

- **Progress bar, small card**: showProgress="bar" with size="sm": 288px cards for short, three-step tours.
- **Coachmarks without dimming**: spotlight={false}: no overlay, the page stays usable and each card points at its target with an arrow.
- **Hotspot**: TourHotspot pulses on a new feature; opened once, persistKey keeps it away. Tap it on a phone for a bottom card.
- **Interactive step**: interactive: the search takes the caret and typing reaches the page while the tour waits.
- **Phone frame, docked and swipeable**: docked inside a phone mockup (container): the card sits at the bottom, swipe it left or right to page.
- **beforeEnter opens a menu**: The second step opens the Actions menu before it highlights Export to Excel; the third closes it again.

## See also

- [Popover](https://wingo-ui.com/components/popover) ([Markdown](https://wingo-ui.com/components/popover.md))
- [Onboarding Carousel](https://wingo-ui.com/components/onboarding-carousel) ([Markdown](https://wingo-ui.com/components/onboarding-carousel.md))
- [Shortcuts Dialog](https://wingo-ui.com/components/shortcuts-dialog) ([Markdown](https://wingo-ui.com/components/shortcuts-dialog.md))

---

Canonical: https://wingo-ui.com/components/tour
More components: https://wingo-ui.com/components · Index for agents: https://wingo-ui.com/llms.txt
