react-joyride
Original:🇺🇸 English
Translated
Guide for implementing, configuring, and debugging React Joyride v3 guided tours. Use this skill whenever the user mentions joyride, guided tour, onboarding tour, walkthrough, tooltip tour, step-by-step guide, product tour, or wants to highlight UI elements sequentially. Also use when debugging tour issues like tooltips not appearing, targets not found, or controlled mode problems. This skill covers the useJoyride hook, Joyride component, step configuration, events, controls, custom components, and styling.
3installs
Sourcegilbarbara/react-joyride
Added on
NPX Install
npx skill4agent add gilbarbara/react-joyride react-joyrideTags
Translated version includes tags in frontmatterSKILL.md Content
View Translation Comparison →React Joyride v3
Create guided tours in React apps. Two public APIs: the hook (recommended) and the component.
useJoyride()<Joyride>Online docs: https://v3.react-joyride.com
Quick Start
Using the hook (recommended)
tsx
import { useJoyride, STATUS, Status } from 'react-joyride';
function App() {
const { Tour } = useJoyride({
continuous: true,
run: true,
steps: [
{ target: '.my-element', content: 'This is the first step', title: 'Welcome' },
{ target: '#sidebar', content: 'Navigate here', placement: 'right' },
],
onEvent: (data) => {
if (([STATUS.FINISHED, STATUS.SKIPPED] as Status).includes(data.status)) {
// Tour ended
}
},
});
return <div>{Tour}{/* rest of app */}</div>;
}Using the component
tsx
import { Joyride, STATUS, Status } from 'react-joyride';
function App() {
return (
<Joyride
continuous
run={true}
steps={[
{ target: '.my-element', content: 'First step' },
{ target: '#sidebar', content: 'Second step' },
]}
onEvent={(data) => {
if (([STATUS.FINISHED, STATUS.SKIPPED] as Status).includes(data.status)) {
// Tour ended
}
}}
/>
);
}The hook returns . Render in your JSX.
{ controls, failures, on, state, step, Tour }TourCore Concepts
The tour has two state dimensions:
Tour Status:
idle -> ready -> waiting -> running <-> paused -> finished | skipped- : No steps loaded
idle - : Steps loaded, waiting for
readyrun: true - :
waitingbut steps loading async (transitions to running when steps arrive)run=true - : Tour active
running - : Tour paused (controlled mode at COMPLETE, or
pausedcalled)stop() - /
finished: Tour endedskipped
Step Lifecycle (per step):
init -> ready -> beacon_before -> beacon -> tooltip_before -> tooltip -> complete- phases: scrolling and positioning happen here
*_before - : Pulsing indicator shown (skipped when
beacon+ navigating,continuous, orskipBeacon)placement: 'center' - : The tooltip is visible and interactive
tooltip
Step Configuration
Each step requires and . All other fields are optional.
targetcontenttsx
{
target: '.my-element', // CSS selector, HTMLElement, React ref, or () => HTMLElement
content: 'Step body text', // ReactNode
title: 'Optional title', // ReactNode
placement: 'bottom', // Default. Also: top, left, right, *-start, *-end, auto, center
id: 'unique-id', // Optional identifier
data: { custom: 'data' }, // Attached to event callbacks
}Target types
tsx
// CSS selector
{ target: '.sidebar-nav' }
// HTMLElement
{ target: document.getElementById('my-el') }
// React ref
const ref = useRef(null);
{ target: ref }
// Function (evaluated each lifecycle)
{ target: () => document.querySelector('.dynamic-element') }Common step options (override per-step)
| Option | Default | Description |
|---|---|---|
| | Tooltip position. Use |
| | Skip beacon, show tooltip directly |
| | Buttons in tooltip. Add |
| | Don't show dark overlay |
| | Block clicks on highlighted element |
| - | |
| - | |
| | Don't scroll to target |
| - | Scroll to this element instead of |
| - | Highlight this element instead of |
| | Padding around spotlight. Number or |
| | ms to wait for target to appear. |
| | ms to wait for |
All fields can be set globally via prop or per-step. Per-step values override global.
OptionsoptionsUncontrolled vs Controlled
Uncontrolled (default — strongly preferred)
The tour manages step navigation internally. This is the right choice for most use cases.
The library handles async transitions for you. If a step needs to wait for a UI change (dropdown opening, data loading, animation), use hooks — the tour waits for the promise to resolve before showing the step. If a target element isn't in the DOM yet, (default: 1000ms) handles polling for it. You do NOT need controlled mode for these cases.
beforetargetWaitTimeouttsx
const { Tour } = useJoyride({
continuous: true,
run: isRunning,
steps: [
{ target: '.nav', content: 'Navigation' },
{
target: '.dropdown-item',
content: 'Inside the dropdown',
before: () => {
// Open dropdown and wait for animation — tour waits automatically
openDropdown();
return new Promise(resolve => setTimeout(resolve, 300));
},
after: () => closeDropdown(), // Clean up after step (fire-and-forget)
},
{ target: '.main-content', content: 'Main content' },
],
onEvent: (data) => {
if (([STATUS.FINISHED, STATUS.SKIPPED] as Status).includes(data.status)) {
setIsRunning(false);
}
},
});Controlled (with stepIndex
) — use sparingly
stepIndexOnly use controlled mode when the parent genuinely needs to manage the step index externally (e.g., syncing with URL params, external state machines, or complex multi-component coordination that / hooks can't handle).
beforeaftertsx
const [stepIndex, setStepIndex] = useState(0);
const [run, setRun] = useState(true);
const { Tour } = useJoyride({
continuous: true,
run,
stepIndex, // This makes it controlled
steps,
onEvent: (data) => {
const { action, index, status, type } = data;
if (([STATUS.FINISHED, STATUS.SKIPPED] as Status).includes(status)) {
setRun(false);
return;
}
if (type === 'step:after' || type === 'error:target_not_found') {
setStepIndex(index + (action === 'prev' ? -1 : 1));
}
},
});Controlled mode rules:
- and
go()are disabled (logged warning)reset() - You must update in response to events
stepIndex - The tour pauses at COMPLETE — you must advance it
- Prefer uncontrolled mode with /
beforehooks unless you have a strong reason for external index managementafter
Event System
onEvent
callback
onEventtsx
onEvent: (data: EventData, controls: Controls) => voidThe object contains the full tour state plus event-specific fields. The object lets you programmatically control the tour.
datacontrolsEvent types (in order per step)
| Event | When |
|---|---|
| Tour begins |
| |
| Target found, step about to render |
| Scrolling to target |
| Scroll complete |
| Beacon shown |
| Tooltip shown |
| User navigated (next/prev/close/skip) |
| |
| Tour finished or skipped |
| Status changed (on stop/reset) |
| Target element not found |
| Generic error |
Event subscription with on()
on()tsx
const { on, Tour } = useJoyride({ ... });
useEffect(() => {
const unsubscribe = on('tooltip', (data, controls) => {
analytics.track('tour_step_viewed', { step: data.index });
});
return unsubscribe;
}, [on]);Controls
Available via return value or second argument:
useJoyride()onEvent| Method | Description |
|---|---|
| Advance to next step |
| Go to previous step |
| Close current step, advance |
| Skip the tour entirely |
| Start the tour |
| Stop (pause) the tour |
| Jump to step (uncontrolled only) |
| Reset tour (uncontrolled only) |
| Open tooltip for current step |
| Get current state |
Styling & Theming
Three layers of customization (from simple to full control):
1. Color options (simplest)
tsx
options: {
primaryColor: '#1976d2', // Buttons and beacon
backgroundColor: '#1a1a2e', // Tooltip background
textColor: '#ffffff', // Tooltip text
overlayColor: 'rgba(0,0,0,0.7)', // Overlay backdrop
arrowColor: '#1a1a2e', // Arrow (match background)
}2. Styles override
tsx
styles: {
tooltip: { borderRadius: 12 },
buttonPrimary: { backgroundColor: '#1976d2' },
buttonBack: { color: '#666' },
spotlight: { borderRadius: 8 },
}Style keys: , , , , , , , , , , , , , , , , ,
arrowbeaconbeaconInnerbeaconOuterbeaconWrapperbuttonBackbuttonClosebuttonPrimarybuttonSkipfloaterloaderoverlaytooltiptooltipContainertooltipContenttooltipFootertooltipFooterSpacertooltipTitle3. Custom components (full control)
See next section.
Custom Components
Replace any UI component via props. Each receives render props with step data and button handlers.
Custom Tooltip
tsx
import type { TooltipRenderProps } from 'react-joyride';
function MyTooltip({ backProps, index, primaryProps, size, skipProps, step, tooltipProps }: TooltipRenderProps) {
return (
<div {...tooltipProps} style={{ background: '#fff', padding: 16, borderRadius: 8, width: step.width }}>
{step.title && <h3>{step.title}</h3>}
<div>{step.content}</div>
<div>
{index > 0 && <button {...backProps}>Back</button>}
<button {...primaryProps}>Next</button>
</div>
</div>
);
}
// Usage
<Joyride tooltipComponent={MyTooltip} ... />Important: Spread on the container (sets and ). Spread button props (, , , ) on buttons for correct action handling.
tooltipPropsrole="dialog"aria-modalbackPropsprimaryPropsclosePropsskipPropsCustom Beacon
Must render a (placed inside a wrapper). Receives : .
<span><button>BeaconRenderProps{ continuous, index, isLastStep, size, step }Custom Arrow
Receives : .
ArrowRenderProps{ base, placement, size }Custom Loader
Receives : . Set to to disable the loader.
LoaderRenderProps{ step }nullProblem → Solution Guide
| I need to... | Use |
|---|---|
| Wait for async UI changes between steps (dropdown, animation, data load) | |
| Control step navigation externally (URL sync, external state machine) | Controlled mode with |
| Track which steps failed (target missing, hook error) | |
Listen to specific events without | |
| Show a centered modal-style step | |
Common Gotchas & Debugging
Use debug: true
first
debug: trueThe prop is the most powerful troubleshooting tool. It logs detailed lifecycle transitions, state changes, and event emissions to the console. Always start here when something isn't working.
debugtsx
<Joyride debug={true} ... />
// or
useJoyride({ debug: true, ... })The console output shows exactly which lifecycle phase the tour reaches, what actions are firing, and where it gets stuck.
Tour not starting
- Check is set
run={true} - Verify array is not empty and steps have valid
steps+targetcontent - For SSR: use component (auto-guards DOM access) or check
<Joyride>typeof window !== 'undefined'
Target not found
- Test selector in console:
document.querySelector('.your-selector') - Element must be visible (not ,
display: none, or zero dimensions)visibility: hidden - If element mounts later, increase (default: 1000ms)
targetWaitTimeout - Set to skip waiting entirely
targetWaitTimeout: 0 - In uncontrolled mode, missing targets auto-advance; in controlled mode, handle event
error:target_not_found
Tooltip never appears / overlay flashes
- Add and check the console to see which lifecycle phase is reached
debug: true - Verify the target element is in the viewport or scrollable
- Check for CSS on ancestors clipping the target
overflow: hidden - If using portals or modals, the target may not be accessible
Controlled mode stuck
- First question: do you actually need controlled mode? Most async needs are solved with /
beforehooks in uncontrolled modeafter - If controlled: you MUST update in your
stepIndexhandler whenonEventtype === 'step:after' - Handle both forward () and backward (
action !== 'prev') navigationaction === 'prev' - Also handle to skip broken steps
error:target_not_found - and
go()don't work in controlled modereset()
Before hook timeout
- Default is 5000ms — increase if your async operation takes longer
beforeTimeout - Set for no timeout
beforeTimeout: 0 - The loader appears after (300ms) while waiting
loaderDelay
Scroll issues
- Use to scroll to a different element than the tooltip target
scrollTarget - Adjust (default: 20px) for headers or fixed elements
scrollOffset - Set to disable auto-scrolling for a step
skipScroll: true - by default — set to
scrollToFirstStep: falseif first step is off-screentrue
Center placement
- Use with
placement: 'center'for modal-style centered tooltipstarget: 'body' - Center placement automatically hides the beacon and arrow
Imports
tsx
// Named exports only (no default export in v3)
import { Joyride, useJoyride } from 'react-joyride';
// Constants for type-safe comparisons
import { ACTIONS, EVENTS, LIFECYCLE, ORIGIN, STATUS } from 'react-joyride';
// Types
import type { Step, Props, EventData, Controls, TooltipRenderProps } from 'react-joyride';Reference Files
Read these for complete API details:
- — Full Props, Options (all 30+ fields with defaults), Locale, FloatingOptions, Styles types
references/api-props-options.md - — Step, StepMerged, StepTarget, State, Controls (all 10 methods), UseJoyrideReturn, StepFailure
references/api-step-state-controls.md - — All 13 event types, ACTIONS/LIFECYCLE/STATUS/ORIGIN constants, EventData, custom component render props
references/api-events-components.md - — Complete working examples: controlled mode, before/after hooks, custom tooltip, event subscription, dynamic steps
references/patterns.md