Write it once.
Ship it twice.

tourkit is a product tour and onboarding walkthrough library for React and React Native. A tour is a TourConfig array with no renderer imports. @tourkit/react draws it in the DOM. @tourkit/native draws it with Reanimated and SVG. Same file, same copy, same steps.

Get started
npm i @tourkit/react
app.example.com/rides
RidesInbox 3Post a ride
Mumbai → Pune2 seats
Andheri → BKC1 seat
Thane → Powai3 seats
Vashi → Colaba1 seat

Post a ride

Offer a seat here.

export const onboarding = { id: "onboarding", version: 1, steps: [ { target: "post-ride", title: "Post a ride" }, { target: "inbox", title: "Messages" }, { target: null, title: "That is the tour" }, ],}; // click a step, both frames follow

Three things worth the install

Web and native, one file

A tour is a plain array with no renderer imports. @tourkit/react draws it in the DOM, @tourkit/native draws it with Reanimated and SVG. driver.js and shepherd.js are DOM libraries, so on mobile you write the tour a second time.

Accessible without asking

Focus is trapped in the step card, arrow keys move between steps, and Escape always ends the tour. There is no closeOnEscape flag, because a tour a keyboard user cannot leave is a defect rather than a setting. The card is a real role="dialog", and reduced motion is read on both platforms.

Restyle it, or replace it

Three levels, in order: theme tokens, then swap the Card, Backdrop and Progress components for your own, then take raw state from useTourState() and render everything yourself. There is no stylesheet to import, because a package that ships CSS breaks in several bundlers and in server rendering.

A working tour, top to bottom

01 Install
terminal
# web
npm i @tourkit/react

# react native, same steps file
npm i @tourkit/native react-native-svg react-native-reanimated
02 Describe the tour as data
src/tour/onboarding.ts
import type { TourConfig } from "@tourkit/core";

type AppContext = { plan: "free" | "pro" };

export const onboarding: TourConfig<AppContext> = {
  id: "onboarding",
  version: 1,
  steps: [
    { id: "post",  target: "post-ride", title: "Post a ride", body: "Offer a seat here." },
    { id: "inbox", target: "inbox",     title: "Messages",    body: "Riders reach you here." },
    { id: "done",  target: null,        title: "That is the tour" },
  ],
};
03 Wrap the app once, mark what to point at
src/App.tsx
import { TourProvider } from "@tourkit/react";

<TourProvider tours={[onboarding]} context={{ plan: "pro" }}>
  <App />
</TourProvider>;

// web: any element carrying the id
<button data-tour-id="post-ride">Post a ride</button>

// react native: the same id, wrapped
<TourTarget id="post-ride">
  <Button title="Post a ride" />
</TourTarget>;
04 Start it
src/Help.tsx
import { useTour } from "@tourkit/react";

function Help() {
  const { start, running } = useTour();
  return <button onClick={() => start("onboarding")}>
    {running ? "Touring" : "Show me around"}
  </button>;
}

Against driver.js and shepherd.js

tourkit 0.2.0driver.js 1.8.0shepherd.js 15.2.3
LicenceMITMITAGPL-3.0, plus a paid commercial licence for revenue-generating use
Runs on React NativeYes, @tourkit/nativeNo, DOM onlyNo, DOM only
Works outside ReactNo, React onlyYes, vanilla, any frameworkYes, vanilla, plus official React, Vue, Angular and Ember wrappers
Runtime dependencies0 in core, 1 in the React renderer02
Stylesheet you must importNone, inline stylesdriver.cssshepherd.css
Hold a step on your own async workgate, a promise, with a per-step timeout and three expiry policieswaitForElement, milliseconds onlybeforeShowPromise
Conditional stepswhen(context)Only skipMissingElementshowOn()
Target survives a markup changefingerprint, scored by label, text, role and neighbourNot in the APINot in the API
Record a tour by clicking the appnpx tourkit recordNot in the APINot in the API
Resume where the user stoppedBuilt in, keyed by tour versionNot in the APINot in the API
Turn a user question into a tour@tourkit/ai, constrained to targets that existNot in the APINot in the API
Published package, npm unpacked tarball441 kB, @tourkit/react156 kB655 kB
Downloads, week of 15 Aug 2026new package1,704,815310,301
Releases to date175152

Stop writing it twice.

One steps file. Two renderers. Five minutes to a working tour.