Migrating from React Joyride
Step-by-step guide to migrate from React Joyride to react-tourlight.
If you're coming from React Joyride — especially after hitting React 19 incompatibilities — this guide maps every concept to its react-tourlight equivalent.
Why migrate?
React Joyride uses deprecated React APIs (unmountComponentAtNode, unstable_renderSubtreeIntoContainer) that are removed in React 19. react-tourlight is built from the ground up for modern React with zero deprecated APIs.
Concept mapping
| React Joyride | react-tourlight | Notes |
|---|---|---|
<Joyride steps={...} /> | <SpotlightTour id="..." steps={...} /> | Tours require a unique id |
run prop | useSpotlight().start(id) | Imperative control via hook |
continuous prop | Always continuous | Step-by-step is the default |
callback with STATUS | onComplete / onSkip props | Cleaner event model |
styles prop | theme prop on Provider | Centralized theming |
floaterProps | Floating UI peer dep | Uses @floating-ui/react-dom |
disableOverlay | overlayColor="transparent" | Set overlay to transparent |
spotlightPadding | spotlightPadding on step | Same concept, per-step |
locale | labels prop on Provider | i18n for button text |
tooltipComponent | renderTooltip render prop | Full control via render props |
Step-by-step migration
1. Install react-tourlight
npm uninstall react-joyride
npm install react-tourlight @floating-ui/react-dom2. Replace the provider
Before (Joyride):
import Joyride from 'react-joyride'
function App() {
const [run, setRun] = useState(false)
return (
<>
<Joyride
steps={steps}
run={run}
continuous
callback={(data) => {
if (data.status === 'finished') handleComplete()
}}
/>
<button onClick={() => setRun(true)}>Start Tour</button>
</>
)
}After (react-tourlight):
import { SpotlightProvider, SpotlightTour, useSpotlight } from 'react-tourlight'
import 'react-tourlight/styles.css'
function App() {
return (
<SpotlightProvider>
<SpotlightTour
id="onboarding"
steps={steps}
onComplete={() => handleComplete()}
/>
<StartButton />
</SpotlightProvider>
)
}
function StartButton() {
const { start } = useSpotlight()
return <button onClick={() => start('onboarding')}>Start Tour</button>
}3. Convert step format
Before:
const steps = [
{
target: '.my-element',
content: 'This is the first step',
title: 'Step 1',
placement: 'bottom',
disableBeacon: true,
},
]After:
const steps = [
{
target: '.my-element',
content: 'This is the first step',
title: 'Step 1',
placement: 'bottom',
// No beacon concept — spotlights start directly
},
]Key differences:
- No
disableBeacon— react-tourlight doesn't have beacons contentacceptsReactNode, not just stringsplacementsupports'auto'for smart positioning- Add
spotlightPaddingandspotlightRadiusper step
4. Update event handling
Before:
callback={(data) => {
const { status, type, index } = data
if (status === 'finished') { /* done */ }
if (status === 'skipped') { /* skipped */ }
if (type === 'step:after') { /* step changed */ }
}}After:
<SpotlightTour
id="onboarding"
steps={steps}
onComplete={() => { /* done */ }}
onSkip={(stepIndex) => { /* skipped at step */ }}
/>5. Update custom tooltips
Before:
tooltipComponent={({ step, primaryProps, backProps, skipProps, index, size }) => (
<div>
<h3>{step.title}</h3>
<p>{step.content}</p>
<button {...backProps}>Back</button>
<button {...primaryProps}>Next</button>
</div>
)}After:
renderTooltip={({ step, next, previous, skip, currentIndex, totalSteps }) => (
<div>
<h3>{step.title}</h3>
<p>{step.content}</p>
<button onClick={previous} disabled={currentIndex === 0}>Back</button>
<button onClick={next}>
{currentIndex < totalSteps - 1 ? 'Next' : 'Done'}
</button>
</div>
)}What you gain
After migrating, you get:
- React 19 compatibility — no deprecated APIs
- Better accessibility — focus trap, full keyboard nav, ARIA roles, screen reader support
- Smaller bundle — ~5KB vs ~30KB
- CSS clip-path overlay — works perfectly in dark mode (no
mix-blend-modehacks) - Async element support —
MutationObserver-based waiting for lazy-loaded content