npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

r-tour-guide

v1.0.1

Published

A comprehensive React tour guide library with TypeScript support, works with Next.js

Downloads

282

Readme

React Tour Guide

A comprehensive React tour guide / product-onboarding library with full TypeScript support. Works with Next.js , Vite, Remix, and any React 17+ app.

  • 🎯 Precise element targeting — CSS selectors or data-tour-guide attributes
  • 🎨 Beautiful, customizable tooltips — colors, labels, and render-props for every part
  • 🔄 Real-time position tracking — follows the target during scroll and resize
  • 🪄 Fluid movement — optionally eases between steps and stays matched to scroll
  • 🧩 Self-contained CSS — no Tailwind or any framework required
  • SSR-safe — renders nothing on the server, no window/document until mounted
  • 🧭 Imperative + declarative — control it via a ref or auto-start
  • 🔧 Full TypeScript — every prop, step and callback is typed

Installation

npm install r-tour-guide

CSS

Import the stylesheet once (it provides the arrows, animations and overlay):

import "r-tour-guide/style.css";

Where you import it depends on the framework — see the setup sections below.

Live demo

🎮 Try it out

Quick start

React (Vite / CRA)

import { useRef } from "react";
import {
  TourGuideManager,
  type TourGuideStep,
  type TourGuideManagerRef,
} from "r-tour-guide";
import "r-tour-guide/style.css";

const steps: TourGuideStep[] = [
  {
    id: "welcome",
    title: "Welcome 👋",
    content: "This is your first step in the tour.",
    target: "welcome", // matches [data-tour-guide="welcome"] or a CSS selector
    direction: "bottom",
    showAction: true,
  },
  {
    id: "cta",
    title: "Get started",
    content: "Click here to begin.",
    target: "#cta-button",
    direction: "top",
    showAction: true,
  },
];

export default function App() {
  const tour = useRef<TourGuideManagerRef>(null);

  return (
    <div>
      <div data-tour-guide="welcome">Welcome card</div>
      <button id="cta-button">Get started</button>

      <button onClick={() => tour.current?.startTourGuide()}>Start tour</button>

      <TourGuideManager ref={tour} steps={steps} />
    </div>
  );
}

Next.js — App Router

TourGuideManager is a client component (it ships with "use client"), so render it inside a client component. It emits nothing on the server and only touches the DOM after mount, so it is fully SSR- and static-export-safe.

Import the CSS once in your root layout:

// app/layout.tsx  (Server Component — importing CSS here is fine)
import "r-tour-guide/style.css";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}

Then use the tour from a client component:

// app/tour.tsx
"use client";

import { useRef } from "react";
import {
  TourGuideManager,
  type TourGuideStep,
  type TourGuideManagerRef,
} from "r-tour-guide";

const steps: TourGuideStep[] = [
  {
    id: "welcome",
    title: "Welcome 👋",
    content: "Let's take a quick tour.",
    target: "welcome",
    showAction: true,
  },
];

export function Tour() {
  const tour = useRef<TourGuideManagerRef>(null);
  return (
    <>
      <button onClick={() => tour.current?.startTourGuide()}>Start tour</button>
      <TourGuideManager ref={tour} steps={steps} autoStart />
    </>
  );
}
// app/page.tsx
import { Tour } from "./tour";

export default function Page() {
  return (
    <main>
      <div data-tour-guide="welcome">Welcome card</div>
      <Tour />
    </main>
  );
}

Next.js — Pages Router

Import the CSS in pages/_app.tsx (Next only allows global CSS there):

// pages/_app.tsx
import type { AppProps } from "next/app";
import "r-tour-guide/style.css";

export default function App({ Component, pageProps }: AppProps) {
  return <Component {...pageProps} />;
}

Then use TourGuideManager in any page/component as usual. No dynamic(() => …, { ssr: false }) wrapper is required — the component is already SSR-safe.

API reference

<TourGuideManager> props

| Prop | Type | Default | Description | | ------------------- | ------------------------------- | ------- | ----------------------------------------------------------- | | steps | TourGuideStep[] | [] | The tour steps, run in order | | autoStart | boolean | false | Start automatically on mount | | showOverlay | boolean | true | Show the dimming overlay around the highlight | | allowSkip | boolean | true | Show the skip button | | allowHtml | boolean | false | Render content as HTML (per-step override available) | | highlightPadding | number | 4 | Padding around the highlighted element (px) | | labels | TourGuideLabels | — | Global button labels | | allowInteractions | boolean | false | Allow interacting with the rest of the page during the tour | | viewportMargin | number | 16 | Minimum distance from the viewport edges | | scrollToView | boolean | true | Auto-scroll the target into view (per-step override) | | trackAnimations | boolean | false | Poll target position each frame to follow CSS animations | | fluid | boolean | false | Ease between steps (matched to scroll); off = instant jump | | fluidDuration | number | 300 | Fluid transition duration in ms | | tooltip | TourGuideTooltipCustomization | — | Global tooltip styling (per-step override available) |

Callbacks

| Prop | Type | Description | | -------------- | ----------------------- | ------------------------------- | | onStart | () => void | Tour has started | | onComplete | () => void | Tour finished the last step | | onSkip | () => void | Tour was skipped | | onStepChange | (step, index) => void | Fired whenever the step changes |

Render-props — each replaces part of the tooltip (the React equivalent of slots): renderContent, renderHeader, renderSkipButton, renderProgress, renderActions. See Render-props.

Imperative ref — TourGuideManagerRef

const tour = useRef<TourGuideManagerRef>(null);

tour.current?.startTourGuide();
tour.current?.nextStep();
tour.current?.previousStep();
tour.current?.goToStep(2);
tour.current?.skipTourGuide();
tour.current?.completeTourGuide();

tour.current?.isActive; // boolean
tour.current?.currentStepIndex; // number

TourGuideStep

interface TourGuideStep {
  id: string; // Unique step id
  title: string; // Tooltip title
  content?: string; // Tooltip body
  allowHtml?: boolean; // Render content as HTML (overrides the manager prop)
  target: string; // CSS selector or data-tour-guide value to highlight
  tooltipTarget?: string; // Separate element to position the tooltip against
  direction?: "top" | "bottom" | "left" | "right";
  offsetX?: number; // Fine-tune tooltip x (px)
  offsetY?: number; // Fine-tune tooltip y (px)
  radius?: number; // Highlight cut-out radius (px, default 8)
  scrollToView?: boolean; // Per-step auto-scroll (overrides the manager prop)
  showAction?: boolean; // Show the prev/next buttons
  skipLabel?: string;
  nextLabel?: string;
  prevLabel?: string;
  finishLabel?: string;
  tooltip?: TourGuideTooltipCustomization; // Per-step styling
  beforeShow?: () => void | Promise<void>;
  afterShow?: () => void;
  beforeHide?: () => void | Promise<void>;
}

TourGuideLabels

interface TourGuideLabels {
  skip?: string;
  next?: string;
  previous?: string;
  finish?: string;
}

TourGuideTooltipCustomization

interface TourGuideTooltipCustomization {
  backgroundColor?: string; // solid color OR a CSS gradient
  textColor?: string;
  borderRadius?: string;
  padding?: string;
  maxWidth?: string;
  minWidth?: string;
  boxShadow?: string;
  buttonBackgroundColor?: string;
  buttonTextColor?: string;
  buttonHoverColor?: string;
  skipButtonColor?: string;
  skipButtonHoverColor?: string;
  progressActiveColor?: string;
  progressInactiveColor?: string;
  tooltipClassName?: string;
  headerClassName?: string;
  contentClassName?: string;
  actionsClassName?: string;
}

Element targeting

target (and tooltipTarget) resolve in two ways:

  1. As a CSS selector#id, .class, [data-x], etc.
  2. As a data-tour-guide valuetarget: "welcome" matches <div data-tour-guide="welcome">.
<div data-tour-guide="welcome-card">…</div>       // target: "welcome-card"
<button id="cta">Go</button>                       // target: "#cta"

Tooltip customization

Style all tooltips globally, or override per step (per-step wins):

<TourGuideManager
  steps={steps}
  tooltip={{
    backgroundColor: "#0f172a",
    textColor: "#fff",
    progressActiveColor: "#60a5fa",
  }}
/>
const steps = [
  {
    id: "custom",
    title: "Fancy",
    content: "Gradient tooltip",
    target: "card",
    tooltip: {
      backgroundColor: "linear-gradient(135deg, #6366f1, #d946ef)",
      textColor: "#fff",
    },
  },
];

Render-props

Replace any part of the tooltip with your own JSX:

<TourGuideManager
  steps={steps}
  renderContent={({ step }) => (
    <div>
      <p>{step?.content}</p>
      <a href="/docs">Read the docs →</a>
    </div>
  )}
  renderActions={({ isLastStep, showPrevious, onPrevious, onNext }) => (
    <>
      {showPrevious && <button onClick={onPrevious}>Back</button>}
      <button onClick={onNext}>{isLastStep ? "Done" : "Next"}</button>
    </>
  )}
/>

Available: renderHeader, renderContent, renderSkipButton, renderProgress, renderActions.

Advanced

Separate tooltip positioning

Highlight one element while pointing the tooltip at another:

{
  id: "advanced",
  title: "Heads up",
  content: "Highlights the checkbox, tooltip points at the save button.",
  target: "notification-checkbox",
  tooltipTarget: "#save-settings",
  direction: "top",
}

Lifecycle hooks

{
  id: "welcome",
  title: "Welcome",
  target: "card",
  beforeShow: async () => { await loadData(); },
  afterShow: () => track("tour_step_shown"),
  beforeHide: async () => { /* cleanup */ },
}

Fluid movement

fluid eases the highlight and tooltip between steps and while the target moves, and suppresses the transition during active scrolling so it stays matched to the page. Tune the speed with fluidDuration (ms):

<TourGuideManager steps={steps} fluid fluidDuration={500} />

Following animated targets

If a target moves under a CSS animation while its step is showing, enable trackAnimations (it polls each frame — off by default because it forces a layout per frame):

<TourGuideManager steps={steps} trackAnimations />

Rich content

  • Line breaks work in a plain content string — newlines are preserved.
  • HTML via allowHtml (per-step or on the manager). Not sanitized — only use it for content you control.
  • Anything interactive via the renderContent render-prop.
{ id: "a", title: "Tips", target: "x", content: "Line one.\nLine two." }
{ id: "b", title: "Shortcut", target: "y", allowHtml: true,
  content: "Press <kbd>Cmd</kbd>+<kbd>K</kbd>." }

useTourGuide()

Shared, persisted tour state (localStorage-backed, SSR-safe). Any component can read it:

const { tourGuideState, resetTourGuide, isStepCompleted } = useTourGuide();

tourGuideState.isActive;
tourGuideState.completedSteps;
tourGuideState.hasSeenTourGuide;

Accessibility

  • The highlighted, interactive element gets a visible focus outline.
  • Honors prefers-reduced-motion (disables fluid transitions) and prefers-contrast.
  • Tour elements are hidden in print.

Next.js / SSR notes

  • TourGuideManager renders null on the server and until it mounts on the client, so hydration is safe.
  • No window/document access happens during render.
  • No dynamic(..., { ssr: false }) wrapper needed.

Examples

See examples/basic-usage.tsx for a complete, copy-pasteable example, and example/ for the full demo site.

License

MIT