Wakeline
← All library comparisons
library comparison

Driver.js vs React Joyride (2026): which to use?

The two most-downloaded tour libraries on npm, both MIT, built on opposite philosophies: one is a tiny DOM utility you call from anywhere, the other is a React component that owns the tour's state. Here is how they differ in practice — with working code for the current versions.

Last checked: October 10, 2026 — versions, licenses and downloads from the npm registry.

Quick verdict

Pick Driver.js if you want the smallest possible footprint, need the same tour code to work outside React, or your tour is mostly highlight-and-explain. Pick React Joyride if your app is React-only and you want the tour to behave like a React component — state, hooks, custom tooltip components and a focus-trapped, accessible dialog out of the box.

  • Both are MIT-licensed and free for commercial use — no license trap on either side.
  • Driver.js 1.9.0 is framework-agnostic, has zero dependencies, and is the lighter of the two.
  • React Joyride 3.2.0 is React-only (16.8–19) and its v3 release changed the API: named import, run defaults to false, callback became onEvent.
  • Neither handles tours that span routes for you; both need you to persist progress and resume.

Driver.js and React Joyride are the two largest tour libraries on npm by a wide margin. In the 90 days to October 8, 2026, driver.js was downloaded about 22.4 million times and react-joyride about 16.5 million — up from roughly 2.1 million and 5.4 million in the same window a year earlier. Nothing else in the category is close.

They are often compared as if they were interchangeable, and they are not. Driver.js is a vanilla TypeScript utility: you hand it a list of selectors and popover text, call drive(), and it draws an overlay and a popover on the live DOM. React Joyride is a React component (and, since v3, a hook): the tour lives in your component tree, renders through a portal, and reports every transition back to you as an event.

If you are on React and searching "react joyride vs driver js", the honest framing is: do you want a tour that is a side effect (Driver.js inside a useEffect) or a piece of UI state (Joyride)? Everything below follows from that choice. For the wider React landscape, see our React tour library roundup.

side by side

The comparison table.

 Driver.jsdriver.jsReact Joyridereact-joyride
LicenseMITMIT
Current version1.9.0 (Oct 3, 2026)3.2.0 (Jul 9, 2026)
Framework supportAny — vanilla TypeScript, no framework bindings neededReact 16.8–19 only
Size & approach~5 kB gzip per its README; zero dependencies25.6 kB gzip incl. 10 dependencies (bundlephobia)
Multi-page / routesNot built in; official docs pattern saves the step, then resumes with drive(index). waitForElement since 1.8Not built in; you navigate (controlled mode or an async before hook) and it polls for the target
AccessibilityKeyboard-controllable; closeBtnLabel sets the close button's aria-label (1.9)alertdialog role, focus trap with focus restore, configurable Esc
StylingPlain CSS file, popoverClass, CSS variables, onPopoverRender hookoptions for colors, styles prop, or your own tooltip/beacon components
MaintenanceVery active — 1.5 through 1.9 shipped June–October 2026Active — v3 rewrite March 2026, 3.2.0 in July
npm downloads, 90 days22.4M16.5M

Versions, publish dates, licenses and download counts (July 11 – October 8, 2026) from the npm registry. Sizes are bundlephobia’s min+gzip measurement of the package and its own dependencies, excluding peers such as React — or the library’s own README figure where noted.

the libraries

Each one, honestly — with the code.

Driver.js — the lightweight, framework-agnostic one

License:
MIT
Version:
driver.js 1.9.0

Driver.js does one thing very well: dim the page, cut a spotlight around an element, and attach a popover to it. Tours are a sequence of those highlights. Its README describes it as about 5 kB gzipped with no external dependencies, and that minimalism is the point — it works identically in React, Vue, Angular, Svelte or a server-rendered page, because it never touches your framework.

The project has been unusually busy in 2026. Version 1.8 added waitForElement (wait up to N ms for a step's element to appear), advanceOnClick (advance when the user clicks the highlighted element) and a separate hints module; 1.9.0 in October added closeBtnLabel for translated aria-labels, made keyboard handling follow the visible buttons, and made destroy() on an inactive instance a safe no-op — which matters for React Strict Mode's double effects.

The cost of being framework-agnostic is that in React, Driver.js is imperative. You create the tour in an effect, it renders its own DOM outside React, and popover content is strings or HTML rather than components. If you want a React component inside a step, you will be reaching for onPopoverRender and manual DOM work.

"use client";
import { useEffect } from "react";
import { driver } from "driver.js";
import "driver.js/dist/driver.css";

export function OnboardingTour() {
  useEffect(() => {
    const tour = driver({
      showProgress: true,
      steps: [
        { element: "#new-project", popover: { title: "Create a project", description: "Everything starts here." } },
        { element: "#invite", popover: { title: "Invite your team", description: "Tours work better with company." } },
      ],
    });
    tour.drive();
    return () => tour.destroy(); // safe: destroy() on an inactive tour is a no-op
  }, []);

  return null;
}
Driver.js 1.9.0 inside a React client component. The same driver() config works unchanged in any framework.

React Joyride — the React-native one

License:
MIT
Version:
react-joyride 3.2.0

React Joyride treats a tour as React state. Steps are objects whose content can be any React node; the tooltip, beacon, arrow and loader can each be replaced with your own component; and every transition — step start, target not found, tour end — arrives through onEvent(data, controls). Its accessibility story is the strongest of the pair: the tooltip renders as an alertdialog with aria-modal, focus is trapped inside it and restored afterwards, and Esc behavior is configurable.

Version 3 (March 2026) is a breaking release, and most tutorials online still show v2. The component is now a named export (import { Joyride } from "react-joyride"); run defaults to false; callback became onEvent; props such as showProgress and spotlightPadding moved into an options prop; showSkipButton and hideBackButton were replaced by an options.buttons array; and getHelpers was replaced by a useJoyride() hook that returns { controls, state, Tour }. continuous remains a top-level prop. A codemod handles most of it: npx react-joyride-migrate v3 src/.

The trade is weight and scope. Bundlephobia measures 3.2.0 at about 25.6 kB gzipped including its ten dependencies, several times Driver.js, and it only runs in React. If your marketing site, admin panel, or a legacy page is not React, Joyride can't follow you there.

"use client";
import { useState } from "react";
import { Joyride, STATUS, type EventData, type Step } from "react-joyride";

const steps: Step[] = [
  { target: "#new-project", title: "Create a project", content: "Everything starts here." },
  { target: "#invite", title: "Invite your team", content: "Tours work better with company." },
];

export function OnboardingTour() {
  const [run, setRun] = useState(true); // v3: run defaults to false

  return (
    <Joyride
      steps={steps}
      run={run}
      continuous
      options={{ showProgress: true, buttons: ["back", "skip", "primary"] }}
      onEvent={(data: EventData) => {
        if (data.status === STATUS.FINISHED || data.status === STATUS.SKIPPED) setRun(false);
      }}
    />
  );
}
React Joyride 3.2.0 — the v3 API: named import, explicit run, options.buttons, onEvent.
multi-page tours

Tours that cross pages: the part neither library does for you.

A tour instance lives on one page. A full page load destroys it, and in a single-page app the next route's elements don't exist yet while the current route is showing. Neither library has router integration, so the pattern is the same for both: treat the tour as resumable, persist the step to resume at, let the app navigate normally, and start again from the saved step.

Driver.js documents this officially: on the step whose click navigates away, combine advanceOnClick with an onNextClick override that saves the next index and calls destroy(), then on the next page call drive(savedIndex). waitForElement covers the render delay after a client-side route change. With React Joyride you do the equivalent in state — controlled mode with stepIndex, or an async before hook that navigates, while Joyride polls for the target. If route-aware steps are central to your tour, look at NextStep, which has per-step nextRoute built in (see React Joyride alternatives).

import { driver } from "driver.js";
import "driver.js/dist/driver.css";

const KEY = "tour-step";

export function createTour() {
  let navigating = false;
  const tour = driver({
    steps: [
      { element: "#reports-nav", popover: { title: "Reports", description: "Your reports live here." } },
      {
        element: "#settings-link",
        advanceOnClick: true, // the link's own click still navigates
        popover: {
          title: "Open Settings",
          description: "Click to continue on the settings page.",
          showButtons: ["close"],
          onNextClick: () => {
            navigating = true;
            localStorage.setItem(KEY, "2"); // resume at step index 2
            tour.destroy();
          },
        },
      },
      // Lives on /settings — waitForElement absorbs the route's render delay.
      { element: "#profile-form", waitForElement: 5000, popover: { title: "Your profile", description: "Update your details here." } },
    ],
    onDestroyed: () => {
      if (!navigating) localStorage.removeItem(KEY); // finished or dismissed
    },
  });
  return tour;
}

// On every page load (or route mount):
const saved = localStorage.getItem(KEY);
if (saved !== null) createTour().drive(Number(saved));
The resumable multi-page pattern, adapted from Driver.js’s official docs (driverjs.com/docs/multi-page-tour).
how to choose

Which should you pick?

Your app is React and you want tour steps to render React components

→ React Joyride

Step content, tooltips and beacons are components; tour state is observable through onEvent or the useJoyride hook.

Bundle size is a hard constraint

→ Driver.js

Zero dependencies and a fraction of Joyride's gzipped weight.

Some pages aren't React (marketing site, legacy views, Vue micro-frontends)

→ Driver.js

One config shape works everywhere; there's no React dependency to carry.

Accessibility review is part of your release process

→ React Joyride

Focus trap, focus restore and dialog semantics are documented defaults rather than something you add.

You only need to spotlight one new feature

→ Driver.js

highlight() on a single element, or its new hints module, is less ceremony than a full tour component.

You're still on Joyride v2 and React 18

→ Stay, then migrate

v2's last release (2.9.3) declares React 15–18 as peers; v3 is the path to React 19 — run the codemod when you upgrade React.

honest limits

When a no-code tool makes more sense.

Both libraries solve rendering a tour. Neither solves running one: deciding which users see which tour, remembering who finished it across devices, measuring where people drop off, or letting a product manager fix a typo without a deploy. If those needs show up on your roadmap, you are building an onboarding system, and the library is the smallest part of it.

That is where a no-code tool usually wins. Wakeline, for example, installs as one script snippet plus an identify() call and lets non-engineers build tours on the live app; its free plan covers 3 flows and 1,000 monthly active users, and paid plans start at $49/month. If one engineer-owned tour is all you will ever need, though, either library above is the cheaper and simpler answer.

questions

Driver.js vs React Joyride: common questions.

Still stuck? Read the docs or talk to us.

Is Driver.js or React Joyride better?

Neither is better in general. Driver.js is smaller, dependency-free and works in any framework; React Joyride is React-only but integrates with React state, lets steps render components, and ships stronger accessibility defaults. Choose by whether your tour should be a DOM side effect or a piece of React UI.

Are Driver.js and React Joyride free for commercial use?

Yes. Both are MIT-licensed, which allows commercial use without a paid license. That is not true of every tour library: Shepherd.js and Intro.js are AGPL-3.0 and sell commercial licenses.

Does React Joyride support React 19?

Version 3 does: react-joyride 3.x declares React 16.8 through 19 as supported peers. The last v2 release, 2.9.3, declares React 15 through 18, which is why installs on React 19 hit peer-dependency errors before v3.

What changed in React Joyride v3?

The component became a named export, run now defaults to false, callback was renamed onEvent and receives a controls object, many props moved into an options prop, the skip/back/close buttons are set with an options.buttons array, and getHelpers was replaced by the useJoyride hook. The react-joyride-migrate codemod automates most of the changes.

Can I use Driver.js in React or Next.js?

Yes. Create the tour inside a useEffect in a client component, call drive(), and call destroy() in the cleanup. Since 1.9.0, destroy() on an instance that is not active is a no-op, so Strict Mode's double effects are harmless.

Which one supports multi-page tours?

Neither has a built-in router integration. Driver.js documents a resumable pattern: save the next step index, destroy the tour, and call drive(index) on the next page. With React Joyride you control navigation yourself, using controlled mode or an async before hook, while it waits for the next target to appear.

Rather not maintain a tour library?

The free plan covers 3 flows and 1,000 monthly active users — enough to compare against the library route on your own app.

Free plan available · No credit card · No demo call