Lightweight, zero-dependency onboarding tour component for React.
Spotlight overlay, smart positioning, keyboard navigation, dark mode support, and fully customizable steps.
Installation • Quick Start • API Reference • Styling • Examples
| guidex-react | intro.js | react-joyride | |
|---|---|---|---|
| Zero dependencies | ~4 KB | ~30 KB (7+ deps) | ~45 KB |
| React-native support | Hooks + Portal | jQuery-style DOM | Wrapper |
| TypeScript | Full | Partial | Full |
| Dark mode | Built-in | Manual CSS | No |
| localStorage | Built-in hook | Manual | Manual |
| License | MIT (free) | AGPL (paid commercial) | MIT |
npm install guidex-reactyarn / pnpm
yarn add guidex-react
# or
pnpm add guidex-reactimport { OnboardingTour, useOnboardingTour } from "guidex-react";
import "guidex-react/styles.css";
const steps = [
{
title: "Welcome!",
content: "Let us show you around.",
},
{
target: "[data-tour='sidebar']",
title: "Navigation",
content: "Use the sidebar to navigate between pages.",
position: "right" as const,
},
{
target: "[data-tour='search']",
title: "Search",
content: "Find anything quickly with the search bar.",
position: "bottom" as const,
},
{
title: "All Done!",
content: "You're ready to go. Enjoy!",
},
];
function App() {
const { active, complete, reset } = useOnboardingTour("my-app-tour");
return (
<div>
<nav data-tour="sidebar">Sidebar</nav>
<input data-tour="search" placeholder="Search..." />
<button onClick={reset}>Replay Tour</button>
<OnboardingTour steps={steps} active={active} onComplete={complete} />
</div>
);
}Add this once at the top level of your app (App.tsx, layout.tsx, or _app.tsx):
import "guidex-react/styles.css";Add data-tour attributes to elements you want to highlight:
<aside data-tour="sidebar">...</aside>
<header data-tour="header">...</header>
<button data-tour="cta-button">Get Started</button>You can also use any valid CSS selector:
#my-id,.my-class,[aria-label='Search']
import type { TourStep } from "guidex-react";
const steps: TourStep[] = [
// No target = centered floating tooltip (great for welcome/finish)
{ title: "Welcome!", content: "We'll walk you through the main features." },
// With target = spotlight on element
{
target: "[data-tour='sidebar']",
title: "Sidebar",
content: "Navigate between pages here.",
position: "right",
},
{ title: "You're All Set!", content: "Start exploring." },
];Position options:
| Position | Tooltip appears... |
|---|---|
"top" |
Above the target |
"bottom" |
Below the target (default) |
"left" |
Left of the target |
"right" |
Right of the target |
| (omitted) | Centered on screen |
function App() {
const { active, complete, reset } = useOnboardingTour("my-app-tour");
return (
<>
<Layout>
<MainContent />
</Layout>
<OnboardingTour
steps={steps}
active={active}
onComplete={complete}
startDelay={1000}
/>
</>
);
}The tour shows automatically on first visit and persists completion in localStorage.
Next.js (App Router)
// app/(dashboard)/layout.tsx
"use client";
import { OnboardingTour, useOnboardingTour } from "guidex-react";
import "guidex-react/styles.css";
const steps = [
{ title: "Welcome!", content: "Let's explore your dashboard." },
{ target: "[data-tour='sidebar']", title: "Sidebar", content: "Navigate here.", position: "right" as const },
{ target: "[data-tour='header']", title: "Header", content: "Your controls.", position: "bottom" as const },
{ title: "All Done!", content: "Enjoy the app!" },
];
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
const { active, complete } = useOnboardingTour("dashboard-tour");
return (
<div className="flex">
<Sidebar />
<div className="flex-1">
<Header />
<main>{children}</main>
</div>
<OnboardingTour steps={steps} active={active} onComplete={complete} startDelay={1500} />
</div>
);
}Next.js (Pages Router)
// pages/_app.tsx
import type { AppProps } from "next/app";
import { OnboardingTour, useOnboardingTour } from "guidex-react";
import "guidex-react/styles.css";
const steps = [/* ... your steps ... */];
export default function App({ Component, pageProps }: AppProps) {
const { active, complete } = useOnboardingTour("app-tour");
return (
<>
<Component {...pageProps} />
<OnboardingTour steps={steps} active={active} onComplete={complete} />
</>
);
}Vite + React
// src/App.tsx
import { OnboardingTour, useOnboardingTour } from "guidex-react";
import "guidex-react/styles.css";
const steps = [/* ... your steps ... */];
function App() {
const { active, complete, reset } = useOnboardingTour("vite-app-tour");
return (
<div>
<button onClick={reset}>Replay Tour</button>
<OnboardingTour steps={steps} active={active} onComplete={complete} />
</div>
);
}| Prop | Type | Default | Description |
|---|---|---|---|
steps |
TourStep[] |
required | Array of tour steps |
active |
boolean |
required | Whether the tour is active |
onComplete |
() => void |
required | Called when tour finishes or is skipped |
doneLabel |
string |
"Get Started" |
Label for the final step button |
nextLabel |
string |
"Next" |
Label for the next button |
backLabel |
string |
"Back" |
Label for the back button |
overlayOpacity |
number |
0.55 |
Overlay background opacity (0-1) |
spotlightPadding |
number |
6 |
Padding around spotlight (px) |
tooltipGap |
number |
14 |
Gap between target and tooltip (px) |
tooltipMaxWidth |
number |
360 |
Max tooltip width (px) |
startDelay |
number |
0 |
Delay before tour starts (ms) |
className |
string |
- | Custom class for tooltip container |
style |
CSSProperties |
- | Inline styles for tooltip |
onStepChange |
(index: number) => void |
- | Callback when step changes |
interface TourStep {
target?: string; // CSS selector
title: string; // Bold heading
content: string; // Description text
position?: "top" | "bottom" | "left" | "right"; // Tooltip placement
}- No
target= centered floating tooltip (ideal for welcome/finish screens) - Target not found in DOM = graceful fallback to centered
const { active, start, complete, reset } = useOnboardingTour("my-unique-key");| Return | Type | Description |
|---|---|---|
active |
boolean |
Whether the tour should show |
start |
() => void |
Manually activate the tour |
complete |
() => void |
Mark complete, persist to localStorage |
reset |
() => void |
Clear storage and restart |
Default key:
"onboarding_tour_complete". Use unique keys when running multiple tours.
Supported out of the box via three methods:
<!-- CSS class (next-themes, manual toggle) -->
<html class="dark">
<!-- data-theme attribute -->
<html data-theme="dark">
<!-- Automatic via prefers-color-scheme (no config needed) -->.rot-btn-next { background: #10b981; }
.rot-btn-next:hover { background: #059669; }
.rot-progress-bar { background: #10b981; }
.rot-spotlight { border-color: rgba(16, 185, 129, 0.5); }/* Glassmorphism */
.rot-tooltip {
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 20px;
}Or via props:
<OnboardingTour
className="my-custom-tooltip"
style={{ borderRadius: 20, fontSize: 16 }}
...
/>CSS Class Reference
| Class | Element |
|---|---|
.rot-spotlight |
Spotlight overlay with cutout |
.rot-tooltip |
Tooltip card container |
.rot-close |
Close (X) button |
.rot-title |
Step title |
.rot-content |
Step description text |
.rot-progress-track |
Progress bar track |
.rot-progress-bar |
Progress bar fill |
.rot-footer |
Footer container |
.rot-step-count |
"1 of 5" counter text |
.rot-buttons |
Button group container |
.rot-btn |
Base button style |
.rot-btn-next |
Next / Done button |
.rot-btn-prev |
Back button |
Conditional Steps (by user role)
const adminSteps: TourStep[] = [
{ target: "[data-tour='admin-panel']", title: "Admin Panel", content: "Manage users here.", position: "right" },
];
const baseSteps: TourStep[] = [
{ title: "Welcome!", content: "Let's get started." },
{ target: "[data-tour='dashboard']", title: "Dashboard", content: "Your overview.", position: "bottom" },
];
const steps = user.role === "admin" ? [...baseSteps, ...adminSteps] : baseSteps;Multi-Page Tours
Use separate storage keys per page:
// Dashboard page
const { active, complete } = useOnboardingTour("dashboard-tour");
// Settings page
const { active, complete } = useOnboardingTour("settings-tour");Trigger Tour from a Button
function HelpButton() {
const { active, start, complete } = useOnboardingTour("help-tour");
return (
<>
<button onClick={start}>Help Tour</button>
<OnboardingTour steps={steps} active={active} onComplete={complete} />
</>
);
}Track Step Analytics
<OnboardingTour
steps={steps}
active={active}
onComplete={() => {
analytics.track("tour_completed");
complete();
}}
onStepChange={(index) => {
analytics.track("tour_step_viewed", { step: index, title: steps[index].title });
}}
/>Custom Start Delay
Wait for data to load before starting:
<OnboardingTour steps={steps} active={active} onComplete={complete} startDelay={2000} />| Key | Action |
|---|---|
→ Right Arrow / Enter |
Next step |
← Left Arrow |
Previous step |
Escape |
Skip / close tour |
| Browser | Version |
|---|---|
| Chrome | 80+ |
| Firefox | 80+ |
| Safari | 14+ |
| Edge | 80+ |
Requires
backdrop-filtersupport for glassmorphism. Falls back gracefully in older browsers.
- Zero external dependencies (React peer dep only)
- Spotlight overlay with smooth transitions
- Smart tooltip positioning (top, bottom, left, right, auto-centered)
- Keyboard navigation (Arrow keys, Enter, Escape)
- Auto-scroll target elements into view
- Animated progress bar with step counter
- Dark mode support (
.dark,[data-theme="dark"],prefers-color-scheme) localStoragepersistence viauseOnboardingTourhook- Fully typed with TypeScript
- ~4 KB minified + gzipped
- Works with Next.js, Vite, CRA, Remix, and any React 17+ project