Skip to Content
🎉 Now published as @nfsfu234/tour-guide project-wide config support — see what's new →
Project-Wide Config

Project-Wide Config Example

A realistic setup for an app running more than one tour — onboarding, a settings walkthrough, and a feature announcement — all sharing the same visual identity via defineConfig() and <TourProvider>.

New in v1.1.0. See Configuration for the full reference, and What’s New if you’re upgrading from an earlier version.


The Problem This Solves

Without shared config, every <Tour> repeats the same theme props:

// ❌ Repetitive — theme/accentColor duplicated on every tour <Tour steps={onboardingSteps} theme="dark" accentColor="#10b981" showBranding={false} /> <Tour steps={settingsSteps} theme="dark" accentColor="#10b981" showBranding={false} /> <Tour steps={featureSteps} theme="dark" accentColor="#10b981" showBranding={false} />

If you rebrand later, that’s three (or ten) places to update, and it’s easy for one to drift out of sync.


Step 1: Define Shared Config

tour.config.ts
// tour.config.ts import { defineConfig } from '@nfsfu234/tour-guide'; export default defineConfig({ theme: 'dark', accentColor: '#10b981', showBranding: false, buttonLabels: { next: 'Next →', previous: '← Back', skip: 'Skip', finish: 'Done! 🎉', }, });

Step 2: Wrap the App

app/layout.tsx
// app/layout.tsx import { TourProvider } from '@nfsfu234/tour-guide'; import tourConfig from '../tour.config'; export default function RootLayout({ children }) { return ( <TourProvider config={tourConfig}> {children} </TourProvider> ); }

Step 3: Define Each Tour’s Steps

Steps are not shared via config — each tour keeps its own content. See Sharing Steps Across Tours if some steps genuinely repeat between tours.

components/OnboardingTour.tsx
// components/OnboardingTour.tsx 'use client'; import { useState, useEffect } from 'react'; import { Tour } from '@nfsfu234/tour-guide'; export default function OnboardingTour() { const [isActive, setIsActive] = useState(false); useEffect(() => { const seen = localStorage.getItem('onboarding_completed') === 'true'; if (!seen) { const timer = setTimeout(() => setIsActive(true), 100); return () => clearTimeout(timer); } }, []); const handleDone = () => { localStorage.setItem('onboarding_completed', 'true'); setIsActive(false); }; const steps = [ { target: '#dashboard', content: 'This is your dashboard.' }, { target: '#create-project', content: 'Create your first project here.' }, ]; // No theme, accentColor, or buttonLabels here — inherited from TourProvider return ( <Tour tourId="onboarding" isActive={isActive} steps={steps} onComplete={handleDone} onSkip={handleDone} /> ); }
components/SettingsTour.tsx
// components/SettingsTour.tsx 'use client'; import { useState } from 'react'; import { Tour } from '@nfsfu234/tour-guide'; export default function SettingsTour() { const [isActive, setIsActive] = useState(false); const steps = [ { target: '#profile-settings', content: 'Update your profile here.' }, { target: '#notification-settings', content: 'Control what you get notified about.' }, ]; return ( <> <button onClick={() => setIsActive(true)}>Show me around settings</button> <Tour tourId="settings-tour" isActive={isActive} steps={steps} onComplete={() => setIsActive(false)} onSkip={() => setIsActive(false)} /> </> ); }
components/FeatureAnnouncementTour.tsx
// components/FeatureAnnouncementTour.tsx 'use client'; import { useState, useEffect } from 'react'; import { Tour } from '@nfsfu234/tour-guide'; const CURRENT_VERSION = '2.0.0'; export default function FeatureAnnouncementTour() { const [isActive, setIsActive] = useState(false); useEffect(() => { const lastSeen = localStorage.getItem('last_seen_version'); if (lastSeen !== CURRENT_VERSION) { setIsActive(true); } }, []); const steps = [ { target: '#ai-assistant', content: '🤖 New: try our AI assistant.' }, ]; // Overrides accentColor for this one tour, everything else still inherited return ( <Tour tourId="whats-new-2-0" isActive={isActive} steps={steps} accentColor="#a855f7" welcomeScreen={{ enabled: false }} onComplete={() => { localStorage.setItem('last_seen_version', CURRENT_VERSION); setIsActive(false); }} onSkip={() => { localStorage.setItem('last_seen_version', CURRENT_VERSION); setIsActive(false); }} /> ); }

Step 4: Mount Them

app/dashboard/page.tsx
// app/dashboard/page.tsx import OnboardingTour from '@/components/OnboardingTour'; import SettingsTour from '@/components/SettingsTour'; import FeatureAnnouncementTour from '@/components/FeatureAnnouncementTour'; export default function DashboardPage() { return ( <> <OnboardingTour /> <FeatureAnnouncementTour /> <div id="dashboard">{/* ... */}</div> <button id="create-project">Create Project</button> <div id="profile-settings">{/* ... */}</div> <div id="notification-settings">{/* ... */}</div> <SettingsTour /> <div id="ai-assistant">{/* ... */}</div> </> ); }

What Each Tour Inherits vs Overrides

TourthemeaccentColorshowBrandingbuttonLabels
Onboardingdark (inherited)#10b981 (inherited)false (inherited)inherited
Settingsdark (inherited)#10b981 (inherited)false (inherited)inherited
Feature Announcementdark (inherited)#a855f7 (overridden)false (inherited)inherited

Per-instance props always win — see Override Precedence.


Next Steps

Last updated on