Skip to content
WingoUI
ComponentsTemplatesDocsPricingAI agentsBlogChangelog
  1. Home
  2. Blog
  3. Component guides
  4. React Product Tour: Spotlight, Phone Dock, Hotspots
Auf Deutsch lesen
  1. Blog
  2. Component guides
  3. React Product Tour: Spotlight, Phone Dock, Hotspots
Component guides
Component guides

React Product Tour: Spotlight, Phone Dock, Hotspots

SR
Serban Rusu · Founder of Wingo UI
Oct 19, 2026 · 9 min read

On this page

0%
  1. What should a React product tour handle?
  2. How do the React tour libraries compare?
  3. What props does a React product tour need?
  4. How do steps wait for menus and real clicks?
  5. How should a product tour work on a phone?
  6. How do I remember who finished the tour?
  7. How do I make a product tour accessible?
  8. When is a hotspot better than a tour?
  9. Should I use a tour or an onboarding flow?
  10. What are the common product tour pitfalls?
  11. How do I add it to my project?
  12. FAQ
    1. What is the best React tour library?
    2. How do I show a product tour only once in React?
    3. Should a product tour be skippable?
    4. What is the difference between a product tour and a hotspot?
    5. Is the Wingo UI Tour free?

TL;DR

A good React product tour dims the page around the real element, explains it in a card that keeps focus, and moves on when people press the real button. On a phone the card should dock at the bottom with the target scrolled above it, and the tour should remember who finished or skipped it. For a single new feature, a pulsing hotspot that people open when they want is usually better than a tour.

Published Oct 19, 2026

A React product tour looks simple in a demo: a box around a button, a card with Next, five steps, done. Then it meets a real app. The target is inside a closed menu, the card covers the thing it describes on a 390px screen, Tab wanders off into the page behind the overlay, and everyone who reloads gets the tour again. This guide covers what a production tour needs, how the Wingo UI Tour handles the spotlight, the phone layout and saved progress, and when you should not build a tour at all.

What should a React product tour handle?

Seven things. A React guided tour that only highlights elements on a wide screen covers the first one:

  1. A spotlight on the real UI. Dim the page, cut a rounded hole around the target, and follow it when the page scrolls or resizes.
  2. Targets that are not there yet. A step can point at an item inside a menu or a tab that has to open first.
  3. Interactive steps. "Press New invoice" should move on when people press the real New invoice button.
  4. Keyboard and screen reader support. Focus in the card, arrow keys to page, Escape to leave, focus back where it was.
  5. A phone layout. A card floating next to a 44px button covers half a phone screen.
  6. Memory. Finished or skipped tours do not come back on the next visit.
  7. A way back in. A Help menu item or a shortcut that replays it.

How do the React tour libraries compare?

React Joyride is the established free React tour library, Driver.js is a small library without React bindings, and Shepherd.js is free only for non-commercial work. Those are the names that come up when you look for a React Joyride alternative. Facts come from each project's README or site, as of October 2026.

OptionWhat it isLicenseReactNotes from the docs
React Joyride (opens in a new tab)npm package, <Joyride> component and useJoyride hookMIT16.8 through 19, per the READMELists focus trapping, keyboard navigation, ARIA support and SSR safety
Driver.js (opens in a new tab)Framework-agnostic JavaScript packageMITNo React bindings, you call driver() yourselfZero dependencies and about 5 kB gzipped, per its README; highlights, popovers and tours
Shepherd.js (opens in a new tab)Framework-agnostic packageAGPL-3.0, commercial license sold separatelyWorks with React, per its siteFree for open source, personal and non-commercial projects
Wingo UI TourPro source component, npx wingo-ui@latest add tourWingo UI license19Docked phone card with swipe paging, hotspots, persisted outcome

React Joyride is the safe pick if you want a free, maintained package and plan to handle phones yourself. Driver.js fits a project that is not all React. The rest of this guide covers the Wingo UI Tour, which ships as source in components/ui.

What props does a React product tour need?

One: steps. Each step has an id, a title, optional content and media, and a target, which can be a CSS selector, a function that returns an element, or a ref. A step without a target, or one whose target does not show up within one second, gets a centered card, which is how you write a welcome and a closing step.

bash
npx wingo-ui@latest add tour
tsx
"use client";
import { useRef } from "react";
import { Button } from "@/components/ui/button";
import { Tour, type TourStep } from "@/components/ui/tour";
export function ClientsPage() {
const search = useRef<HTMLInputElement>(null);
const invoice = useRef<HTMLButtonElement>(null);
const steps: TourStep[] = [
{ id: "welcome", title: "Welcome to Northwind CRM", content: "Here is where everything lives. It takes 30 seconds." },
{ id: "search", target: search, title: "Find any client", content: "Type a name, an EIN or a city." },
{ id: "filters", target: "[data-tour='filters']", title: "Saved filters", content: "Keep the combinations you use often." },
{ id: "invoice", target: invoice, title: "Issue an invoice", content: "Press New invoice to continue.", advanceOn: "click" },
{ id: "done", title: "You are all set", content: "Replay this tour any time from the Help menu." },
];
return (
<Tour steps={steps} persistKey="northwind-crm:welcome-tour" autoStart shortcut="shift+h">
<input ref={search} placeholder="Search clients" />
<div data-tour="filters">{/* saved filter chips */}</div>
<Button ref={invoice}>New invoice</Button>
</Tour>
);
}

The Tour renders its children in place and puts the overlay in a portal. Each step first scrolls its target into view, then the spotlight glides there on a spring and the card fades in on the first side with room for it (side="auto" tries below, then above, then the wider of left and right). Next you will reach for showProgress (dots, fraction, bar or none), size (sm for 288px cards, md for 340px), tone, spotlightPadding and spotlightRadius (also settable per step), and labels for every string.

Start the tour below and follow it to step four: it waits until you press the real New invoice button. Right arrow and Enter go forward, Left goes back, Escape leaves.

Press Start the tour (or Shift + H), follow the spotlight to step four and press the real New invoice button to move on
$ npx wingo-ui@latest add tour
ProTour docs

How do steps wait for menus and real clicks?

Three step options cover it. interactive: true lets clicks and typing reach the highlighted element, so people can type into the search box while the card explains it. advanceOn: "click" implies interactive and calls next when the target is clicked. beforeEnter runs before the step shows and is awaited for up to three seconds, which is enough to open a menu or switch a tab so the target exists:

tsx
{
id: "export",
target: exportItemRef,
title: "Export to Excel",
content: "The filtered list goes into an .xlsx file.",
side: "right",
beforeEnter: async () => {
setMenuOpen(true);
await new Promise((resolve) => requestAnimationFrame(resolve));
},
},

Close the menu again in the next step's beforeEnter and in onOpenChange when the tour ends, or it stays open after a skip. If a target is removed while its step is showing, the card falls back to the center instead of pointing at nothing.

How should a product tour work on a phone?

Dock the card at the bottom. Below 768px the Tour does that by default (mobileLayout="docked"): the card becomes a sheet above the safe area, its Back and Next buttons are 48px tall, it rides above the on-screen keyboard when one opens, and a target outside the top 55% of the screen scrolls up into it, so the card does not cover it. A swipe left on the card goes to the next step and a swipe right goes back.

The reason is the thumb. A popover anchored to a button at the bottom of a phone screen sits on top of the hand that has to tap Next, and a popover anchored near the top pushes Next out of reach. The same rule drives our React bottom sheet guide. Pass mobileLayout="popover" if you really want the desktop card on phones, or docked to dock it at every width, which is what you want inside a phone mockup together with container.

The same tour with docked on: start it and the card docks at the bottom of your window. Drag or swipe the card sideways to page through the steps
$ npx wingo-ui@latest add tour
ProTour docs

How do I remember who finished the tour?

Give it a persistKey. The Tour then stores completed or skipped in local storage, autoStart (which opens the tour 600ms after hydration) leaves a finished tour alone, start() still replays it, and reset() forgets it. That is enough for one device. For a React onboarding tour shown to signed-in users, save the outcome on the server too, so a new laptop does not replay it:

tsx
"use client";
import { Button } from "@/components/ui/button";
import { Tour, useTour, type TourStep } from "@/components/ui/tour";
function saveTour(status: "completed" | "skipped") {
void fetch("/api/me/tours/welcome", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ status }),
});
}
function ReplayTour() {
const tour = useTour();
if (!tour) return null;
return (
<Button variant="ghost" size="sm" onClick={() => tour.start()}>
Replay tour
</Button>
);
}
export function AppShell({ steps, seenTour }: { steps: TourStep[]; seenTour: boolean }) {
return (
<Tour
steps={steps}
autoStart={!seenTour}
onComplete={() => saveTour("completed")}
onSkip={() => saveTour("skipped")}
>
<ReplayTour />
{/* the rest of the app */}
</Tour>
);
}

useTour() gives anything inside the Tour its API plus open, step, count and status, so the replay button can sit in a header or a Help menu. For code outside the Tour's children, the setApi prop hands you the same start, next, previous, goTo, skip, finish and reset. Only one tour shows at a time: a second one that opens waits for the first to close.

How do I make a product tour accessible?

Treat each card as a dialog. The Tour's card has role="dialog", aria-modal, and a label and description tied to the step's title and content. Tab stays inside the card without locking the page, focus returns to where it was when the tour ends, and a polite live region announces "Step 2 of 5: Find any client" on every step and "Tour closed" or "Tour completed" at the end. The highlighted element gets aria-describedby pointing at the step's text for as long as the step shows, so a screen reader reading the real button also hears what the tour says about it.

Clicks on the dimmed area do nothing by default (closeOnOverlayClick={false}), so a stray tap on a phone does not end the tour. By default every card has Skip or a close button, and Escape skips from anywhere. Under reduced motion the spotlight jumps instead of gliding and the cards fade; the prefers-reduced-motion guide explains the hook behind it.

When is a hotspot better than a tour?

When you want to point at one thing. Nielsen Norman Group's Onboarding Tutorials vs. Contextual Help (opens in a new tab) (Page Laubheimer, February 2023) puts it bluntly: "Tutorials interrupt users, don't necessarily improve task performance, and are quickly forgotten." The article recommends help that appears when people need it and is easy to dismiss and find again.

TourHotspot is that pattern. It puts a pulsing dot on a corner of its child (or of any target) and opens a tip when someone taps or clicks it, as a bottom card below 768px. With persistKey the dot disappears for good once the tip has been seen.

tsx
import { Button } from "@/components/ui/button";
import { TourHotspot } from "@/components/ui/tour";
<TourHotspot
title="New: sales by state"
content="Open Reports to see the new chart."
persistKey="northwind-crm:hint-reports"
tone="brand"
>
<Button variant="soft" size="sm">Reports</Button>
</TourHotspot>

A rough split that works for us: a hotspot for one new feature, a coachmark tour (spotlight={false}, cards with an arrow and no dimming, the page stays usable) for two or three related changes in a release, and the full spotlight tour only for first-run orientation in an app with several areas. A single hint on a control that people already understand belongs in a Tooltip or a Popover, both free.

Should I use a tour or an onboarding flow?

They do different jobs. An onboarding flow runs before people reach the app: welcome slides, permission asks, a short profile. The Onboarding Flow block covers that, and the React onboarding flow tutorial walks through it, including why the notification ask needs a card of its own. A product tour runs inside the app, on the real screens, after that. If you have both, keep the tour to the three or four places people use in their first week.

What are the common product tour pitfalls?

  • autoStart without persistKey. The tour opens on every page load. Pair them, or drive autoStart from a server flag.
  • Too many steps. Each extra step is one more chance to press Skip before the part that matters. Split long tours by area and start each one from its page.
  • Steps that describe obvious controls. A search box does not need a step unless it searches something unexpected.
  • A shortcut that collides. ? often opens a shortcuts dialog; the Tour binds nothing until you pass shortcut, and our demo uses shift+h.
  • A selector that matches a hidden copy. A string target goes through document.querySelector, which returns the first match. If that is the desktop toolbar hidden on a phone, the step shows a centered card. Use a ref or a function that returns the visible element, and beforeEnter for anything that takes longer than a second to render.

How do I add it to my project?

Run npx wingo-ui@latest add tour. The CLI copies tour.tsx into components/ui, adds the Button, Kbd and Progress components and the hooks and helpers it uses, and installs @radix-ui/react-popover, motion and lucide-react. The Tour and the Onboarding Flow are Pro components (pricing). More single-component deep dives are in the component guides category, and the React form components guide applies the same keyboard, touch and screen reader rules to every form control.

Components in this post

  • 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.

    Pro
  • Onboarding Flow

    A React onboarding flow for a marketplace app: welcome slides, notification and location asks, a short profile, preferences and a finish.

    Pro

FAQ

What is the best React tour library?

React Joyride is the most established free option: MIT licensed, with a Joyride component and a useJoyride hook, and its README lists React 16.8 through 19. Driver.js is a small framework-free MIT library, and Shepherd.js needs a commercial license for commercial use. Pick a source component such as the Wingo UI Tour if you want to edit the code and get a phone layout built in.

How do I show a product tour only once in React?

Store the outcome. With the Wingo UI Tour, persistKey saves completed or skipped in local storage and autoStart leaves a finished tour alone; for signed-in users, also save it on the user record in onComplete and onSkip so a new device does not replay it.

Should a product tour be skippable?

Yes, on every step. Nielsen Norman Group found that tutorials shown up front interrupt people and are quickly forgotten, so the least you can do is make leaving easy and let people replay the tour later from a Help menu.

What is the difference between a product tour and a hotspot?

A tour pushes a sequence of steps at people, usually on first run. A hotspot is a small pulsing dot on one element that opens a tip only when someone taps it, so it fits announcing one new feature without interrupting work.

Is the Wingo UI Tour free?

No. The Tour and the Onboarding Flow block are Pro. The Popover and Tooltip components, which cover single hints without a sequence, are free.

  • Product tour
  • Onboarding
  • Mobile UI
  • Accessibility
  • React

Share

SR

About the author

Serban Rusu

Founder of Wingo UI

Serban Rusu founded Wingo UI. He builds its component library, CLI and MCP server, and writes about React interfaces for phones and AI coding agents.

More from Serban
Keep reading

Related posts

All posts
Build it: screens and apps

React Onboarding Flow: Slides, Permissions, Install

Build a React onboarding flow in Next.js: skippable welcome slides, notification and location asks people can refuse, saved progress, then an install prompt.

SRSerban Rusu·Oct 16, 2026·9 min read
Mobile UI in React

React PWA Install Prompt for Chrome, Edge and iPhone

Build a React PWA install prompt in Next.js: catch beforeinstallprompt in Chrome and Edge, show Add to Home Screen steps on iPhone, and know when not to ask.

SRSerban Rusu·Oct 9, 2026·9 min read
Component guides

Embla Carousel React: Accessible Slides, No Layout Shift

Build an embla carousel react component for photos, listings, testimonials and onboarding, with keyboard controls, pausable autoplay and no layout shift.

SRSerban Rusu·Oct 19, 2026·9 min read
Newsletter

Get new posts by email

New guides, tutorials and comparisons from the Wingo UI blog, sent when they are published.

No spam. Unsubscribe at any time.

WingoUI

Animated, configurable, mobile-first React components and blocks. Copy the source, make it yours, and let your coding agent build with it.

Product

  • Components
  • Templates
  • Pricing
  • AI agents
  • Theme generator

Resources

  • Docs
  • Blog
  • Changelog
  • Changelog RSS
  • llms.txt

Legal

  • Terms
  • Privacy
  • Refunds
  • License
  • Imprint

Component categories

  • Buttons & Actions
  • Inputs
  • Forms
  • Navigation
  • Overlays
  • Feedback
  • Data Display
  • Tables & Lists
  • Charts & Stats
  • Layout
  • Media
  • AI Kit
  • Text & Effects
  • Mobile
  • Commerce
  • Marketing Sections
  • Blocks
  • Hooks & Utilities

© 2026 Wingo UI

Wingo UI