Multi-Page Tours
Build tours that span multiple routes with state persistence and route-aware step configuration.
Some tours need to guide users across multiple pages or routes. react-tourlight supports this through state persistence and route-aware step configuration.
How it works
react-tourlight doesn't manage routing. Instead, it provides persistence hooks that let you save and restore tour state across page navigations. You bring your own router -- react-tourlight works with Next.js, React Router, TanStack Router, or any routing solution.
The pattern is:
- Save tour state on every change via
onStateChange - Restore tour state on mount via
initialState - Configure steps per-route, with
whenpredicates to skip steps not relevant to the current page
Persistence with onStateChange
The onStateChange callback fires whenever a tour's state changes (start, step change, complete, skip). Use it to persist the state to localStorage, a database, or any storage:
function App() {
const [savedState, setSavedState] = useState<Record<string, TourState>>(() => {
if (typeof window === 'undefined') return {}
const stored = localStorage.getItem('spotlight-state')
return stored ? JSON.parse(stored) : {}
})
const handleStateChange = (tourId: string, state: TourState) => {
setSavedState((prev) => {
const next = { ...prev, [tourId]: state }
localStorage.setItem('spotlight-state', JSON.stringify(next))
return next
})
}
return (
<SpotlightProvider
initialState={savedState}
onStateChange={handleStateChange}
>
{/* your app */}
</SpotlightProvider>
)
}Route-aware steps
Define all steps across all routes in a single tour, using when predicates to conditionally show steps based on the current route:
import { usePathname } from 'next/navigation' // or your router
function OnboardingTour() {
const pathname = usePathname()
const steps: SpotlightStep[] = [
// Dashboard page steps
{
target: '#dashboard-header',
title: 'Welcome',
content: 'This is your dashboard.',
when: () => pathname === '/dashboard',
},
{
target: '#metrics-panel',
title: 'Metrics',
content: 'Your key metrics at a glance.',
when: () => pathname === '/dashboard',
},
// Settings page steps
{
target: '#profile-section',
title: 'Profile',
content: 'Update your profile information.',
when: () => pathname === '/settings',
},
{
target: '#notifications-toggle',
title: 'Notifications',
content: 'Configure your notification preferences.',
when: () => pathname === '/settings',
},
]
return (
<SpotlightTour
id="onboarding"
steps={steps}
onComplete={() => console.log('Multi-page tour complete!')}
/>
)
}Navigating between pages
Use onHide on the last step of a page to navigate the user to the next page, then pick up the tour there:
import { useRouter } from 'next/navigation'
function OnboardingTour() {
const router = useRouter()
const pathname = usePathname()
const steps: SpotlightStep[] = [
{
target: '#dashboard-header',
title: 'Welcome',
content: 'Let us show you around.',
when: () => pathname === '/dashboard',
},
{
target: '#quick-actions',
title: 'Quick Actions',
content: 'Next, let us show you the settings.',
when: () => pathname === '/dashboard',
onHide: () => {
// Navigate to the next page when leaving this step
if (pathname === '/dashboard') {
router.push('/settings')
}
},
},
{
target: '#profile-section',
title: 'Profile',
content: 'Update your profile here.',
when: () => pathname === '/settings',
},
]
return <SpotlightTour id="onboarding" steps={steps} />
}Full example with localStorage
Here's a complete multi-page tour setup with persistence:
import { useState } from 'react'
import {
SpotlightProvider,
SpotlightTour,
useSpotlight,
} from 'react-tourlight'
import type { TourState } from 'react-tourlight'
import 'react-tourlight/styles.css'
function loadState(): Record<string, TourState> {
if (typeof window === 'undefined') return {}
try {
const stored = localStorage.getItem('spotlight-tours')
return stored ? JSON.parse(stored) : {}
} catch {
return {}
}
}
function saveState(state: Record<string, TourState>) {
localStorage.setItem('spotlight-tours', JSON.stringify(state))
}
export function AppProviders({ children }: { children: React.ReactNode }) {
const [tourState, setTourState] = useState(loadState)
const handleStateChange = (tourId: string, state: TourState) => {
setTourState((prev) => {
const next = { ...prev, [tourId]: state }
saveState(next)
return next
})
}
return (
<SpotlightProvider
initialState={tourState}
onStateChange={handleStateChange}
>
{children}
</SpotlightProvider>
)
}Works with any router
react-tourlight doesn't import or depend on any router. The when predicates and onHide callbacks are plain functions, so they work with any routing solution:
- Next.js App Router -- use
usePathname()fromnext/navigation - Next.js Pages Router -- use
useRouter()fromnext/router - React Router -- use
useLocation()fromreact-router-dom - TanStack Router -- use
useRouterState()from@tanstack/react-router
The only requirement is that the <SpotlightProvider> wraps your router's layout, so tour state persists across navigations.