Skip to main content

How to migrate from React Joyride to Tour Kit (step-by-step)

Replace React Joyride with Tour Kit in under 2 hours. Step-by-step migration with API mapping, working code, and side-by-side testing.

DomiDex
DomiDexCreator of Tour Kit
April 7, 202610 min read
Share
How to migrate from React Joyride to Tour Kit (step-by-step)

How to migrate from React Joyride to Tour Kit (step-by-step)

Your React Joyride integration works. Tours fire, users click through. But the cracks are showing.

Maybe your design system overhaul means fighting Joyride's inline styles on every tooltip. Maybe your React 19 upgrade stalled for months because of GitHub issue #1122. Or you need analytics and checklists that Joyride simply doesn't offer.

This guide walks you through replacing React Joyride with Tour Kit incrementally, running both libraries side-by-side so nothing breaks in production. Budget 1-2 hours for a typical 5-10 step tour.

Bias disclosure: We built Tour Kit. Every claim is verifiable against npm, GitHub, and the source code.

npm install @tourkit/core @tourkit/react

Why migrate from React Joyride?

React Joyride has 667K weekly npm downloads and 7,690 GitHub stars as of April 2026, making it the most popular open-source React tour library by a wide margin. But popularity doesn't mean it fits every project. Teams migrate away from React Joyride when they hit design system conflicts, delayed framework support, or architectural limits that surface as apps grow.

When we integrated React Joyride into a shadcn/ui project, matching our design tokens required a custom tooltipComponent, floaterProps overrides, and roughly 80 lines of CSS workarounds. Tour Kit rendered through our existing components with zero overrides.

Here are the specific pain points that push teams toward a migration:

  • Inline style injection conflicts with Tailwind and design tokens, requiring CSS overrides for every tooltip (OnboardJS evaluation)
  • React 19 support took six months in v2 while teams waited on issue #1122. One developer wrote: "We are still waiting for this update as it prevents us from upgrading to react 19"
  • Typed callbacks use any on step data, losing compile-time safety on custom payloads
  • Multi-page tours need setTimeout() workarounds since Joyride has no router integration
  • No built-in analytics, checklists, announcements, surveys, or adoption tracking

To be fair: React Joyride v3 fixed React 19 support and switched to Floating UI with built-in TypeScript. If those fixes address your pain points, staying on Joyride is a valid choice. Migrate when the architectural differences matter to your project.

API mapping: React Joyride to Tour Kit

Tour Kit uses a different mental model than React Joyride. Joyride is configuration-driven with a single <Joyride> component. Tour Kit is composition-driven with providers, hooks, and renderless components. This table maps the concepts you already know to their Tour Kit equivalents.

React JoyrideTour KitNotes
<Joyride steps={[...]} /><TourProvider> + <Tour>Provider wraps your app; Tour renders per-tour
steps array propcreateTour({ steps: [...] })Type-safe tour factory with createStep()
target: '.my-element'target: '.my-element'Same CSS selector pattern
content: <div>...</div><TourStep> childrenYou control the entire tooltip JSX
callback propuseTour() hook + event callbacksTyped TourCallbackContext instead of any
getHelpers() (v2)useTour() hookReturns next(), prev(), stop(), goTo()
useJoyride() (v3)useTour()Similar hook API, different return shape
tooltipComponentNot neededYou write the tooltip directly with TourCard
floaterPropsPlacement prop on <Tour>Uses @floating-ui/react under the hood
disableScrollingscroll config in TourKitProviderGranular scroll behavior control
spotlightClicks<TourOverlay clickThrough />Explicit component prop
run propuseTour().start() / .stop()Imperative control via hook
continuousDefault behaviorTour Kit is continuous by default
No router supportuseNextAppRouter() / useReactRouter()Built-in adapters for multi-page tours
No analytics@tour-kit/analyticsPlugin-based: PostHog, Mixpanel, GA4, custom
No checklists@tour-kit/checklistsTask dependencies, progress tracking

Before you start

Before writing any migration code, verify your current React Joyride version and confirm your project meets Tour Kit's requirements: React 18.2 or later (React 19 works natively) and TypeScript 5+. The examples below use npm, but pnpm and yarn work identically. Most teams finish the migration in a single sprint.

Keep Joyride installed during migration. You'll run both libraries simultaneously, migrating one tour at a time, and remove Joyride only after every tour has been ported and tested.

Verify your current Joyride version:

npm ls react-joyride

If you're on v2, note that some prop names changed in v3 (callback became onEvent, getHelpers became useJoyride()). This guide maps from both versions.

Step 1: install Tour Kit alongside React Joyride

Install Tour Kit's two core packages without removing React Joyride. Both libraries use independent React contexts, so they coexist in the same bundle without conflicts. This side-by-side approach lets you migrate one tour at a time while keeping existing tours running.

npm install @tourkit/core @tourkit/react

Then wrap your app with the Tour Kit provider. This goes alongside your existing code and doesn't affect Joyride's behavior.

// src/app/layout.tsx (or your root layout)
import { TourKitProvider } from '@tourkit/react'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <TourKitProvider>
      {children}
    </TourKitProvider>
  )
}

Tour Kit's provider adds no visible DOM. Your existing Joyride tours continue working exactly as before.

Step 2: map your Joyride step configuration

The biggest conceptual shift in this migration is how steps are defined. React Joyride uses a flat array of objects passed to a single component. Tour Kit uses createTour() and createStep() factory functions that return typed configurations, paired with composable JSX components for rendering. Here's a before-and-after for a typical 3-step tour.

React Joyride (before):

// src/components/OnboardingTour.tsx
import Joyride from 'react-joyride'

const steps = [
  {
    target: '.dashboard-header',
    content: 'Welcome to your dashboard. This is where you manage everything.',
    disableBeacon: true,
  },
  {
    target: '.create-project-btn',
    content: 'Click here to create your first project.',
    placement: 'bottom' as const,
  },
  {
    target: '.settings-link',
    content: 'Configure your workspace settings here.',
    placement: 'right' as const,
  },
]

export function OnboardingTour() {
  return <Joyride steps={steps} continuous showProgress showSkipButton />
}

Tour Kit (after):

// src/components/OnboardingTour.tsx
import {
  Tour, TourStep, TourCard, TourCardHeader, TourCardContent,
  TourCardFooter, TourNavigation, TourProgress, TourOverlay,
  TourClose, useTour, createTour, createStep,
} from '@tourkit/react'

const onboardingTour = createTour({
  tourId: 'onboarding',
  steps: [
    createStep({
      target: '.dashboard-header',
      placement: 'bottom',
    }),
    createStep({
      target: '.create-project-btn',
      placement: 'bottom',
    }),
    createStep({
      target: '.settings-link',
      placement: 'right',
    }),
  ],
})

export function OnboardingTour() {
  return (
    <Tour {...onboardingTour}>
      <TourOverlay />
      <TourStep>
        <TourCard>
          <TourClose />
          <TourCardHeader />
          <TourCardContent />
          <TourCardFooter>
            <TourNavigation />
            <TourProgress />
          </TourCardFooter>
        </TourCard>
      </TourStep>
    </Tour>
  )
}

The Tour Kit version is more lines of JSX but every piece is a component you control. Want to swap TourCard for your own design system card? Just replace the component. No tooltipComponent override, no floaterProps hacking.

Step 3: migrate tour controls and callbacks

React Joyride manages tour state through a callback prop (v2) or onEvent (v3) where the event data is typed as any. Tour Kit replaces this pattern entirely with the useTour() hook, which returns typed methods like start(), stop(), next(), and prev() alongside typed state like currentStepIndex and isActive. The hook approach means your IDE autocompletes everything.

React Joyride v2 callback:

// Joyride v2 — callback data typed as `any`
<Joyride
  callback={(data) => {
    if (data.action === 'close' || data.status === 'finished') {
      setRunTour(false)
    }
  }}
/>

Tour Kit equivalent:

// src/components/TourTrigger.tsx
import { useTour } from '@tourkit/react'

export function TourTrigger() {
  const { start, stop, isActive, currentStepIndex } = useTour()

  return (
    <button onClick={() => isActive ? stop() : start()}>
      {isActive ? `Step ${currentStepIndex + 1}` : 'Start tour'}
    </button>
  )
}

Every return value from useTour() is fully typed. No guessing whether data.action is a string or enum. TypeScript catches mistakes at compile time instead of production.

For analytics tracking that used to live in the Joyride callback, Tour Kit has a dedicated analytics package:

npm install @tour-kit/analytics

Step 4: test both tours side-by-side

With Tour Kit rendering your migrated tours and Joyride still handling the rest, you now need to verify that the new implementation matches the old behavior on every page. The goal is confidence that no user-facing tour regresses before you cut over completely. Use a feature flag to toggle between implementations during QA.

A practical approach: add a feature flag or environment variable.

// src/components/OnboardingTour.tsx
const USE_TOUR_KIT = process.env.NEXT_PUBLIC_USE_TOUR_KIT === 'true'

export function OnboardingTour() {
  if (USE_TOUR_KIT) {
    return <TourKitOnboarding />
  }
  return <JoyrideOnboarding />
}

Test these specific behaviors during the side-by-side phase:

  1. Tour starts correctly on page load or button click
  2. Steps highlight the right elements (check CSS selectors)
  3. Navigation works: next, previous, skip, close
  4. Tour state persists across page refreshes (if you had this with Joyride)
  5. Keyboard navigation: Tab, Escape, Enter
  6. Screen reader announces step changes

If your Joyride tour relied on disableScrollParentFix or scrollOffset, test scroll behavior carefully. Tour Kit uses scrollIntoView() with configurable options through the scroll config in TourKitProvider.

Step 5: remove React Joyride

After every tour passes testing with Tour Kit, remove React Joyride from your dependency tree. This step reclaims roughly 30KB of bundle size and eliminates the duplicate positioning library. Uninstall cleanly:

npm uninstall react-joyride

Delete old tour components and any tooltipComponent overrides. Clean up CSS workarounds you no longer need. Search your codebase for leftover imports:

grep -r "react-joyride" src/

If that returns nothing, you're clean.

What you'll gain (and what you'll lose)

Every migration involves tradeoffs, and honesty about those tradeoffs matters more than a sales pitch. We measured the concrete differences after migrating a 10-step onboarding tour from React Joyride v3 to Tour Kit in a production shadcn/ui project. Here's what changed.

You gain:

  • Full control over tooltip rendering with your design system components
  • Tour Kit core ships at under 8KB gzipped versus React Joyride's approximately 30KB
  • Typed callbacks and hook returns instead of any parameters
  • Built-in router adapters for Next.js App Router, Pages Router, React Router

Plus optional packages for analytics, checklists, announcements, surveys, adoption tracking, and scheduling. WCAG 2.1 AA accessibility with focus trapping is built in. And React 19 support from day one, not six months after release.

You lose:

  • Drop-in simplicity. Tour Kit requires writing tooltip JSX instead of passing a steps array
  • Community size. React Joyride has 7,690 stars and a decade of Stack Overflow answers

Tour Kit is newer with a smaller community. It doesn't ship pre-built themes either. Joyride gives you default tooltip styles; Tour Kit is headless, so you style everything yourself (though it pairs well with shadcn/ui).

Tour Kit limitation to know: Tour Kit requires React 18.2+ and doesn't support React Native or non-React frameworks. If your app isn't React, Joyride (or Shepherd.js) may be a better fit.

Common issues and troubleshooting

We hit these problems during our own migrations and collected them from early adopters. Each has a specific fix.

"Tour tooltip doesn't appear after migration"

The target element probably hasn't rendered when the tour initializes. React Joyride polls for elements internally. Tour Kit uses waitForElement() which respects React's rendering lifecycle instead. If your target loads lazily, start the tour after the component mounts:

import { useTour, waitForElement } from '@tourkit/react'

function DashboardPage() {
  const { start } = useTour()

  useEffect(() => {
    waitForElement('.dashboard-header').then(() => start())
  }, [start])

  return <Dashboard />
}

"Overlay covers the target element"

Check that your target elements don't have position: fixed or position: sticky inside a scrollable container. Tour Kit's overlay uses a portal and the spotlight calculates positions from the document root. If your layout uses a fixed sidebar, pass the scroll parent explicitly through the scroll configuration.

"Step placement is wrong"

Tour Kit uses @floating-ui/react for positioning, same as Joyride v3. But Joyride's placement values like 'bottom-start' map directly. If you used Joyride's floaterProps.offset, set the offset in Tour Kit's step configuration. The default offset is 8px.

Next steps

Once the migration is complete, Tour Kit's extended package ecosystem opens up capabilities that React Joyride simply doesn't offer. These optional packages install independently so you only add what your product actually needs. Start with analytics if you want visibility into tour completion rates.

  • Checklists (@tour-kit/checklists): task-based onboarding with dependencies and progress tracking
  • Analytics (@tour-kit/analytics): plugin-based integration with PostHog, Mixpanel, Amplitude, or custom backends
  • Announcements (@tour-kit/announcements): modal, toast, banner, slideout, and spotlight announcement types
  • Hints (@tour-kit/hints): persistent hotspot beacons for feature discovery
  • Persistence (usePersistence()): save tour progress to localStorage, cookies, or any custom backend

For a full comparison of the two libraries, read our Tour Kit vs React Joyride breakdown.

FAQ

How long does the React Joyride migration take?

Budget 1-2 hours for a standard 5-10 step tour. Most of that time goes to rewriting tooltip JSX since Tour Kit is headless. Teams with an existing component library finish faster because the JSX maps to components they already have. Complex tours with multi-page routing or conditional logic may take an additional hour for router adapter setup.

Can I run React Joyride and Tour Kit at the same time?

Yes. Both libraries use independent React contexts and DOM management. Install Tour Kit alongside Joyride, migrate one tour at a time, then remove Joyride after all tours pass testing. Your bundle temporarily grows by roughly 30KB, which you reclaim after uninstalling.

Does Tour Kit support the same CSS selectors as React Joyride?

Tour Kit uses document.querySelector() under the hood, same as Joyride. Selectors like .my-class, #my-id, and [data-tour="step-1"] transfer directly. No changes needed to your DOM attributes or data attributes.

What about React Joyride's beacon component?

React Joyride renders pulsing beacon dots before showing each step. Tour Kit doesn't bundle a beacon component, but the @tour-kit/hints package provides hotspot beacons with more control over positioning, animation, and dismiss behavior. Install it separately with npm install @tour-kit/hints.

Is Tour Kit free to use?

Tour Kit's core packages (@tourkit/core, @tourkit/react, @tourkit/hints) are MIT licensed and free. Extended packages like analytics, checklists, and announcements require a Pro license at $99 one-time. The free tier covers everything needed for a standard React Joyride migration.


Internal linking suggestions:

Distribution checklist:

  • Cross-post to Dev.to with canonical URL to tourkit.dev
  • Cross-post to Hashnode with canonical URL
  • Share on Reddit r/reactjs as "I wrote a migration guide for teams moving from React Joyride to a headless alternative"
  • Answer Stack Overflow questions tagged [react-joyride] with link to this guide

JSON-LD Schema:

{
  "@context": "https://schema.org",
  "@type": "TechArticle",
  "headline": "How to migrate from React Joyride to Tour Kit (step-by-step)",
  "description": "Replace React Joyride with Tour Kit in under 2 hours. Step-by-step migration with API mapping, working code, and side-by-side testing.",
  "author": {
    "@type": "Person",
    "name": "DomiDex",
    "url": "https://tourkit.dev"
  },
  "publisher": {
    "@type": "Organization",
    "name": "Tour Kit",
    "url": "https://tourkit.dev",
    "logo": {
      "@type": "ImageObject",
      "url": "https://tourkit.dev/logo.png"
    }
  },
  "datePublished": "2026-04-07",
  "dateModified": "2026-04-07",
  "image": "https://tourkit.dev/og-images/migrate-react-joyride-tour-kit.png",
  "url": "https://tourkit.dev/blog/migrate-react-joyride-tour-kit",
  "mainEntityOfPage": {
    "@type": "WebPage",
    "@id": "https://tourkit.dev/blog/migrate-react-joyride-tour-kit"
  },
  "keywords": ["react joyride migration", "replace react joyride", "react joyride alternative", "migrate product tour library"],
  "proficiencyLevel": "Intermediate",
  "dependencies": "React 18.2+, TypeScript 5+",
  "programmingLanguage": {
    "@type": "ComputerLanguage",
    "name": "TypeScript"
  }
}

Ready to try userTourKit?

$ pnpm add @tour-kit/react