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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@robertlinde/react-tour-kit

v0.2.2

Published

Cross-platform guided tour library for React and React Native. Build interactive onboarding experiences with customizable tooltips, smart positioning, cross-page navigation, and async step actions. Fully themeable with TypeScript support.

Readme

react-tour-kit

Cross-platform guided tour library for React and React Native. Build interactive onboarding experiences with customizable tooltips, smart positioning, cross-page navigation, and async step actions. Fully themeable with TypeScript support.

Features

  • Cross-Platform: Works with React (web) and React Native
  • Cross-Page Tours: Navigate between pages/screens during a tour
  • Themeable: Customize colors without building custom components
  • Fully Customizable: Replace the default tooltip and overlay with your own components
  • Smart Positioning: Automatic tooltip positioning with viewport boundary detection
  • Keyboard Navigation: Arrow keys and Escape (web) or hardware back button (Android)
  • Async Step Actions: Run async code before each step (e.g., navigate, switch tabs, open dialogs)
  • TypeScript First: Full TypeScript support

Installation

npm install @robertlinde/react-tour-kit

Live Demos

Try the demos locally:

# Web demo (React + React Router)
cd demos/react
npm install
npm run dev

# React Native demo (Expo + React Navigation)
cd demos/react-native
npm install
npm run ios  # or npm run android

Platform Guide

| Platform | Import from | Target elements with | | ---------------- | ----------------------------- | --------------------------------------------- | | React (Web) | react-tour-kit/react | CSS selectors (e.g., [data-tour="welcome"]) | | React Native | react-tour-kit/react-native | useTourTarget hook or refs |


React (Web)

Quick Start

1. Wrap your app with TourProvider

import {TourProvider} from '@robertlinde/react-tour-kit/react';

function App() {
  return (
    <TourProvider>
      <YourApp />
    </TourProvider>
  );
}

2. Add data attributes to target elements

function Dashboard() {
  return (
    <div>
      <header data-tour="welcome">
        <h1>Dashboard</h1>
      </header>

      <nav data-tour="sidebar">{/* Navigation items */}</nav>

      <button data-tour="settings">Settings</button>
    </div>
  );
}

3. Start a tour

import {useTour, type TourStep} from '@robertlinde/react-tour-kit/react';

const tourSteps: TourStep[] = [
  {
    target: '[data-tour="welcome"]', // CSS selector
    title: 'Welcome!',
    content: 'This is your dashboard. Let me show you around.',
    placement: 'bottom',
  },
  {
    target: '[data-tour="sidebar"]',
    title: 'Navigation',
    content: 'Use the sidebar to navigate between different sections.',
    placement: 'right',
  },
  {
    target: '[data-tour="settings"]',
    title: 'Settings',
    content: 'Click here to customize your preferences.',
    placement: 'left',
  },
];

function WelcomeButton() {
  const {startTour} = useTour();

  return <button onClick={() => startTour(tourSteps, 'onboarding')}>Start Tour</button>;
}

Keyboard Navigation (Web)

| Key | Action | | --------------- | ------------- | | Arrow Right | Next step | | Arrow Left | Previous step | | Escape | Close tour |


React Native

Quick Start

1. Wrap your app with TourProvider

import {TourProvider} from '@robertlinde/react-tour-kit/react-native';

function App() {
  return (
    <TourProvider>
      <YourApp />
    </TourProvider>
  );
}

2. Register target elements with useTourTarget

import {View} from 'react-native';
import {useTourTarget} from '@robertlinde/react-tour-kit/react-native';

function MyComponent() {
  const welcomeRef = useTourTarget<View>('welcome-button');
  const settingsRef = useTourTarget<View>('settings-button');

  return (
    <View>
      <TouchableOpacity ref={welcomeRef}>
        <Text>Welcome</Text>
      </TouchableOpacity>

      <TouchableOpacity ref={settingsRef}>
        <Text>Settings</Text>
      </TouchableOpacity>
    </View>
  );
}

3. Start a tour

import {useTour, type TourStep} from '@robertlinde/react-tour-kit/react-native';

const tourSteps: TourStep[] = [
  {
    target: 'welcome-button', // String ID from useTourTarget
    title: 'Welcome!',
    content: 'This is your dashboard. Let me show you around.',
    placement: 'bottom',
  },
  {
    target: 'settings-button',
    title: 'Settings',
    content: 'Tap here to customize your preferences.',
    placement: 'left',
  },
];

function StartTourButton() {
  const {startTour} = useTour();

  return (
    <TouchableOpacity onPress={() => startTour(tourSteps, 'onboarding')}>
      <Text>Start Tour</Text>
    </TouchableOpacity>
  );
}

Using Refs Directly (React Native)

You can also pass refs directly instead of string IDs:

import {useRef} from 'react';
import {useTour, type TourStep} from '@robertlinde/react-tour-kit/react-native';

function MyComponent() {
  const buttonRef = useRef(null);

  const steps: TourStep[] = [
    {
      target: buttonRef, // Pass ref directly
      title: 'Welcome!',
      content: 'Tap here to get started.',
    },
  ];

  return <TouchableOpacity ref={buttonRef}>...</TouchableOpacity>;
}

Navigation (React Native)

| Action | Trigger | | ------------- | ------------------------------------------------- | | Next step | Tap "Next" button | | Previous step | Tap "Back" button | | Close tour | Tap close button, overlay, or Android back button |


Cross-Page/Cross-Screen Tours

Tours can span multiple pages (web) or screens (React Native). The key is to:

  1. Wrap your router/navigator with TourProvider
  2. Use onBeforeStep to navigate before showing a step

Web (React Router)

import {BrowserRouter, useNavigate} from 'react-router-dom';
import {TourProvider, useTour, type TourStep} from '@robertlinde/react-tour-kit/react';

// TourProvider wraps the router
function App() {
  return (
    <TourProvider>
      <BrowserRouter>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/settings" element={<Settings />} />
        </Routes>
      </BrowserRouter>
    </TourProvider>
  );
}

// Define steps with navigation
function Home() {
  const navigate = useNavigate();
  const {startTour} = useTour();

  const steps: TourStep[] = [
    {
      target: '[data-tour="welcome"]',
      title: 'Welcome',
      content: 'Let me show you around.',
      placement: 'bottom',
    },
    {
      target: '[data-tour="nav-settings"]',
      title: 'Settings Link',
      content: "Now let's visit the settings page.",
      placement: 'bottom',
      // Navigate before the NEXT step shows
      onBeforeStep: async () => {
        navigate('/settings');
        await new Promise((r) => setTimeout(r, 100)); // Wait for navigation
      },
    },
    {
      target: '[data-tour="theme-toggle"]',
      title: 'Theme Settings',
      content: 'This element is on the Settings page!',
      placement: 'right',
    },
    {
      target: '[data-tour="welcome"]',
      title: 'Back Home',
      content: 'And we can navigate back.',
      placement: 'bottom',
      onBeforeStep: async () => {
        navigate('/');
        await new Promise((r) => setTimeout(r, 100));
      },
    },
  ];

  return <button onClick={() => startTour(steps, 'cross-page')}>Start Tour</button>;
}

React Native (React Navigation)

import {NavigationContainer, useNavigation} from '@react-navigation/native';
import {createNativeStackNavigator} from '@react-navigation/native-stack';
import {TourProvider, useTour, useTourTarget, type TourStep} from '@robertlinde/react-tour-kit/react-native';

const Stack = createNativeStackNavigator();

// TourProvider wraps the navigator
function App() {
  return (
    <TourProvider>
      <NavigationContainer>
        <Stack.Navigator>
          <Stack.Screen name="Home" component={HomeScreen} />
          <Stack.Screen name="Settings" component={SettingsScreen} />
        </Stack.Navigator>
      </NavigationContainer>
    </TourProvider>
  );
}

// Define steps with navigation
function HomeScreen() {
  const navigation = useNavigation();
  const {startTour} = useTour();
  const headerRef = useTourTarget<View>('header');

  const steps: TourStep[] = [
    {
      target: 'header',
      title: 'Welcome',
      content: 'Let me show you around.',
      placement: 'bottom',
    },
    {
      target: 'settings-header',
      title: 'Settings Screen',
      content: 'This element is on the Settings screen!',
      placement: 'bottom',
      onBeforeStep: async () => {
        navigation.navigate('Settings');
        await new Promise((r) => setTimeout(r, 300)); // Wait for animation
      },
    },
    {
      target: 'header',
      title: 'Back Home',
      content: 'And we can navigate back.',
      placement: 'bottom',
      onBeforeStep: async () => {
        navigation.navigate('Home');
        await new Promise((r) => setTimeout(r, 300));
      },
    },
  ];

  return (
    <View>
      <View ref={headerRef}>{/* ... */}</View>
      <Button onPress={() => startTour(steps, 'cross-screen')} title="Start Tour" />
    </View>
  );
}

function SettingsScreen() {
  const headerRef = useTourTarget<View>('settings-header');

  return <View ref={headerRef}>{/* ... */}</View>;
}

Important Notes for Cross-Page Tours

  1. TourProvider placement: Must wrap your router/navigator so tour state persists across navigation
  2. Target availability: Targets must be mounted when their step is active. Use onBeforeStep to navigate first
  3. Wait for navigation: Add a small delay after navigation to ensure the new page/screen is rendered
  4. Persistent elements: Elements like navigation bars that exist on all pages don't need onBeforeStep

API Reference

TourProvider

The provider component that enables tour functionality throughout your app.

import {TourProvider} from '@robertlinde/react-tour-kit/react';

<TourProvider
  TooltipComponent={CustomTooltip} // Optional: custom tooltip component
  OverlayComponent={CustomOverlay} // Optional: custom overlay component
  theme={theme} // Optional: theme configuration
  onTourEnd={(tourId) => {
    // Optional: callback when tour ends
    console.log(`Tour ${tourId} completed`);
  }}
>
  {children}
</TourProvider>;

Props

| Prop | Type | Description | | ------------------ | --------------------------------------------- | --------------------------------------------------- | | children | ReactNode | Your application content | | theme | TourTheme | Theme configuration for colors (see Theming below) | | TooltipComponent | ForwardRefExoticComponent<TourTooltipProps> | Custom tooltip component (must use forwardRef) | | OverlayComponent | ComponentType<TourOverlayProps> | Custom overlay component | | onTourEnd | (tourId: string \| null) => void | Callback fired when tour ends (completed or closed) |

useTour Hook

Access the tour context from any component.

import {useTour} from '@robertlinde/react-tour-kit/react';

function MyComponent() {
  const {
    isActive, // boolean: is a tour currently running?
    currentStep, // number: current step index (0-based)
    steps, // TourStep[]: all steps in current tour
    currentTourId, // string | null: ID of current tour
    startTour, // (steps: TourStep[], tourId?: string) => void
    endTour, // () => void
    nextStep, // () => void
    prevStep, // () => void
    goToStep, // (step: number) => void
  } = useTour();
}

TourStep

Define the steps in your tour.

type TourStep = {
  target: TourTarget; // Target element (see below)
  title: string; // Step title
  content: string; // Step description/content
  placement?: 'top' | 'bottom' | 'left' | 'right'; // Tooltip position (default: 'bottom')
  onBeforeStep?: () => void | Promise<void>; // Async action before showing step
};

// Target types differ by platform:
// Web: CSS selector string (e.g., '[data-tour="welcome"]', '#my-button')
// React Native: String ID (registered via useTourTarget) or RefObject
type TourTarget = string | RefObject<unknown>;

Theming

Customize the look of the default tooltip and overlay without building custom components.

Theme Options

import {TourProvider, type TourTheme} from '@robertlinde/react-tour-kit/react';

const theme: TourTheme = {
  primaryColor: '#10b981', // Buttons, step badge, highlight border (default: '#3b82f6')
  tooltipBackground: '#ffffff', // Tooltip background color (default: '#ffffff')
  titleColor: '#1f2937', // Title text color (default: '#1f2937')
  contentColor: '#4b5563', // Body text color (default: '#4b5563')
  overlayColor: 'rgba(0, 0, 0, 0.5)', // Semi-transparent overlay (default: 'rgba(0, 0, 0, 0.5)')
};

function App() {
  return (
    <TourProvider theme={theme}>
      <YourApp />
    </TourProvider>
  );
}

Theme Examples

Dark theme:

const darkTheme: TourTheme = {
  primaryColor: '#8b5cf6',
  tooltipBackground: '#1f2937',
  titleColor: '#f9fafb',
  contentColor: '#d1d5db',
  overlayColor: 'rgba(0, 0, 0, 0.7)',
};

Brand colors:

const brandTheme: TourTheme = {
  primaryColor: '#your-brand-color',
};
// Other values use defaults

React Native

Theming works the same way in React Native:

import {TourProvider, type TourTheme} from '@robertlinde/react-tour-kit/react-native';

const theme: TourTheme = {
  primaryColor: '#10b981',
  overlayColor: 'rgba(0, 0, 0, 0.7)',
};

function App() {
  return (
    <TourProvider theme={theme}>
      <YourApp />
    </TourProvider>
  );
}

Advanced Usage

Async Step Actions

Run code before a step is shown. Useful for opening dialogs, switching tabs, navigating, etc.

const steps: TourStep[] = [
  {
    target: '[data-tour="settings-tab"]',
    title: 'Settings Tab',
    content: 'Click here to access settings.',
  },
  {
    target: '[data-tour="notifications-panel"]',
    title: 'Notifications',
    content: 'Configure your notification preferences here.',
    onBeforeStep: async () => {
      // Open the settings tab before showing this step
      document.querySelector('[data-tour="settings-tab"]')?.click();
      // Wait for the panel to appear
      await new Promise((resolve) => setTimeout(resolve, 300));
    },
  },
];

Custom Tooltip Component

Create your own tooltip with your design system.

Web:

import {forwardRef} from 'react';
import {TourProvider, type TourTooltipProps} from '@robertlinde/react-tour-kit/react';

const CustomTooltip = forwardRef<HTMLDivElement, TourTooltipProps>(
  ({title, content, currentStep, totalSteps, position, isPositioned, onClose, onNext, onPrev}, ref) => (
    <div
      ref={ref}
      className="custom-tooltip"
      style={{
        position: 'fixed',
        top: position.top,
        left: position.left,
        opacity: isPositioned ? 1 : 0,
      }}
    >
      <h3>{title}</h3>
      <p>{content}</p>
      <div className="tooltip-footer">
        <span>
          {currentStep + 1} of {totalSteps}
        </span>
        <button onClick={onPrev} disabled={currentStep === 0}>
          Back
        </button>
        <button onClick={onNext}>{currentStep === totalSteps - 1 ? 'Finish' : 'Next'}</button>
        <button onClick={onClose}>×</button>
      </div>
    </div>
  ),
);

function App() {
  return (
    <TourProvider TooltipComponent={CustomTooltip}>
      <YourApp />
    </TourProvider>
  );
}

React Native:

import {forwardRef} from 'react';
import {View, Text, TouchableOpacity} from 'react-native';
import {TourProvider, type TourTooltipProps} from '@robertlinde/react-tour-kit/react-native';

const CustomTooltip = forwardRef<View, TourTooltipProps>(
  ({title, content, position, isPositioned, onNext, onClose}, ref) => (
    <View
      ref={ref}
      style={{
        position: 'absolute',
        top: position.top,
        left: position.left,
        opacity: isPositioned ? 1 : 0,
        backgroundColor: '#fff',
        padding: 16,
        borderRadius: 8,
      }}
    >
      <Text style={{fontWeight: 'bold'}}>{title}</Text>
      <Text>{content}</Text>
      <TouchableOpacity onPress={onNext}>
        <Text>Next</Text>
      </TouchableOpacity>
    </View>
  ),
);

function App() {
  return (
    <TourProvider TooltipComponent={CustomTooltip}>
      <YourApp />
    </TourProvider>
  );
}

Custom Overlay Component

Customize the backdrop overlay.

Web:

import {TourProvider, type TourOverlayProps} from '@robertlinde/react-tour-kit/react';

function CustomOverlay({highlightRect, onClose}: TourOverlayProps) {
  return (
    <div className="custom-overlay" onClick={onClose}>
      <div
        className="highlight-border"
        style={{
          position: 'absolute',
          top: highlightRect.top - 4,
          left: highlightRect.left - 4,
          width: highlightRect.width + 8,
          height: highlightRect.height + 8,
          border: '2px solid #your-brand-color',
          borderRadius: '8px',
        }}
      />
    </div>
  );
}

function App() {
  return (
    <TourProvider OverlayComponent={CustomOverlay}>
      <YourApp />
    </TourProvider>
  );
}

React Native:

import {View, TouchableWithoutFeedback, StyleSheet} from 'react-native';
import {TourProvider, type TourOverlayProps} from '@robertlinde/react-tour-kit/react-native';

function CustomOverlay({highlightRect, onClose}: TourOverlayProps) {
  return (
    <TouchableWithoutFeedback onPress={onClose}>
      <View style={StyleSheet.absoluteFill}>
        {/* Your custom overlay with cutout */}
        <View
          style={{
            position: 'absolute',
            top: highlightRect.top - 4,
            left: highlightRect.left - 4,
            width: highlightRect.width + 8,
            height: highlightRect.height + 8,
            borderWidth: 2,
            borderColor: '#your-brand-color',
            borderRadius: 8,
          }}
        />
      </View>
    </TouchableWithoutFeedback>
  );
}

function App() {
  return (
    <TourProvider OverlayComponent={CustomOverlay}>
      <YourApp />
    </TourProvider>
  );
}

Persisting Tour Completion

Track which tours users have completed:

import {TourProvider} from '@robertlinde/react-tour-kit/react';

function App() {
  const handleTourEnd = async (tourId: string | null) => {
    if (tourId) {
      // Save to your backend
      await fetch('/api/users/me/completed-tours/' + tourId, {
        method: 'POST',
      });
    }
  };

  return (
    <TourProvider onTourEnd={handleTourEnd}>
      <YourApp />
    </TourProvider>
  );
}

Conditional Tour Start

Start tours based on user state:

function TourTrigger({tourId, steps, autoStart = true}) {
  const {startTour} = useTour();
  const {user} = useUser(); // Your auth hook

  useEffect(() => {
    if (autoStart && user && !user.completedTours?.[tourId]) {
      // Auto-start if user hasn't completed this tour
      setTimeout(() => startTour(steps, tourId), 500);
    }
  }, [user, tourId, autoStart]);

  return <button onClick={() => startTour(steps, tourId)}>Start Tour</button>;
}

Styling

The default components use inline styles for zero-dependency styling. You have three options for customization:

  1. Theming: Use the theme prop to customize colors (see Theming section)
  2. Custom Components: Provide your own TooltipComponent and OverlayComponent for full control
  3. CSS Overrides (Web only): Override styles via CSS selectors

Platform Support

Web:

  • Chrome, Firefox, Safari, Edge (latest versions)

React Native:

  • iOS 13+
  • Android 5.0+ (API 21+)
  • React Native 0.70+

Requires React 18+.

TypeScript

Full TypeScript support is included. Import types as needed:

import type {
  TourStep,
  TourContextType,
  TourProviderProps,
  TourTooltipProps,
  TourOverlayProps,
  TourTheme,
} from '@robertlinde/react-tour-kit/react';

Utilities

The package exports utility functions for custom implementations:

import {
  calculateTooltipPosition, // Calculate optimal tooltip position
  findVisibleElement, // Find first visible element matching selector
} from '@robertlinde/react-tour-kit/react';

Development

# Install dependencies
npm install

# Build
npm run build

# Watch mode
npm run start:dev

# Lint
npm run lint

# Format
npm run format

# Run web demo
cd demos/react && npm install && npm run dev

# Run native demo
cd demos/react-native && npm install && npm run ios

License

MIT