Loading...
Loading...
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.
npx skill4agent add gilbarbara/react-joyride react-joyrideuseJoyride()<Joyride>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>;
}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
}
}}
/>
);
}{ controls, failures, on, state, step, Tour }Touridle -> ready -> waiting -> running <-> paused -> finished | skippedidlereadyrun: truewaitingrun=truerunningpausedstop()finishedskippedinit -> ready -> beacon_before -> beacon -> tooltip_before -> tooltip -> complete*_beforebeaconcontinuousskipBeaconplacement: 'center'tooltiptargetcontent{
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
}// 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') }| 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 |
OptionsoptionsbeforetargetWaitTimeoutconst { 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);
}
},
});stepIndexbeforeafterconst [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));
}
},
});go()reset()stepIndexbeforeafteronEventonEvent: (data: EventData, controls: Controls) => voiddatacontrols| 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 |
on()const { on, Tour } = useJoyride({ ... });
useEffect(() => {
const unsubscribe = on('tooltip', (data, controls) => {
analytics.track('tour_step_viewed', { step: data.index });
});
return unsubscribe;
}, [on]);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 |
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)
}styles: {
tooltip: { borderRadius: 12 },
buttonPrimary: { backgroundColor: '#1976d2' },
buttonBack: { color: '#666' },
spotlight: { borderRadius: 8 },
}arrowbeaconbeaconInnerbeaconOuterbeaconWrapperbuttonBackbuttonClosebuttonPrimarybuttonSkipfloaterloaderoverlaytooltiptooltipContainertooltipContenttooltipFootertooltipFooterSpacertooltipTitleimport 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} ... />tooltipPropsrole="dialog"aria-modalbackPropsprimaryPropsclosePropsskipProps<span><button>BeaconRenderProps{ continuous, index, isLastStep, size, step }ArrowRenderProps{ base, placement, size }LoaderRenderProps{ step }null| 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 | |
debug: truedebug<Joyride debug={true} ... />
// or
useJoyride({ debug: true, ... })run={true}stepstargetcontent<Joyride>typeof window !== 'undefined'document.querySelector('.your-selector')display: nonevisibility: hiddentargetWaitTimeouttargetWaitTimeout: 0error:target_not_founddebug: trueoverflow: hiddenbeforeafterstepIndexonEventtype === 'step:after'action !== 'prev'action === 'prev'error:target_not_foundgo()reset()beforeTimeoutbeforeTimeout: 0loaderDelayscrollTargetscrollOffsetskipScroll: truescrollToFirstStep: falsetrueplacement: 'center'target: 'body'// 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';references/api-props-options.mdreferences/api-step-state-controls.mdreferences/api-events-components.mdreferences/patterns.md