react-joyride vs react-shepherd
Implementing User Onboarding Tours in React Applications
react-joyridereact-shepherdSimilar Packages:

Implementing User Onboarding Tours in React Applications

react-joyride and react-shepherd are both React libraries designed to create interactive product tours and onboarding walkthroughs. react-joyride is a React-specific implementation that manages tour state internally, offering a declarative API where steps are defined as props. It handles positioning, scrolling, and beacon interactions automatically within the React lifecycle. react-shepherd is the official React wrapper for Shepherd.js, a framework-agnostic tour library. It relies on the underlying Shepherd instance for logic, requiring developers to manage the tour instance explicitly while providing deep customization options inherited from the core library.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
react-joyride07,865720 kB52 months agoMIT
react-shepherd013,815159 kB3725 days agoAGPL-3.0

React Joyride vs React Shepherd: Architecting User Onboarding

Building effective user onboarding requires more than just showing tooltips; it demands precise control over DOM elements, scroll positions, and application state. Both react-joyride and react-shepherd solve this, but they approach the problem from different architectural angles. One embraces React's declarative nature fully, while the other acts as a bridge to a powerful, framework-agnostic engine.

šŸ—ļø Initialization: Declarative Props vs Imperative Instances

The most significant difference lies in how you start and manage a tour. This choice dictates how your component code looks and how state flows through your application.

react-joyride uses a declarative approach. You pass an array of steps and configuration options directly as props to the component. The library handles the internal state machine, knowing exactly when to open, close, or reset the tour based on prop changes.

// react-joyride: Declarative configuration
import Joyride from 'react-joyride';

function Onboarding() {
  const [run, setRun] = useState(false);

  return (
    <>
      <button onClick={() => setRun(true)}>Start Tour</button>
      <Joyride
        run={run}
        steps={[
          {
            target: '.my-first-step',
            content: 'Welcome to our app!',
          },
          {
            target: '.my-second-step',
            content: 'Here is another feature.',
          },
        ]}
        scrollToFirstStep={true}
      />
    </>
  );
}

react-shepherd requires you to instantiate a Tour object from the underlying Shepherd library. You manage this instance imperatively, adding steps and controlling the flow via methods on the tour object. The React component primarily serves as a renderer for the tour UI.

// react-shepherd: Imperative instance management
import { useTour } from 'react-shepherd';
import { Tour } from 'shepherd.js';

function Onboarding() {
  const { tour } = useTour();

  const startTour = () => {
    tour.addStep({
      id: 'step-1',
      text: 'Welcome to our app!',
      attachTo: { element: '.my-first-step', on: 'bottom' },
    });
    
    tour.addStep({
      id: 'step-2',
      text: 'Here is another feature.',
      attachTo: { element: '.my-second-step', on: 'left' },
    });

    tour.start();
  };

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

šŸ“ Targeting Elements: Auto-Scrolling vs Manual Attachment

Handling elements that are off-screen or inside scrollable containers is a common pain point in tours. The libraries handle this differently.

react-joyride has robust auto-scrolling built into its core logic. When a step targets an element not in the viewport, it automatically calculates the scroll position and smooth-scrolls the container to make the element visible. You rarely need to write custom logic for this.

// react-joyride: Automatic scroll handling
<Joyride
  steps={[
    {
      target: '.deeply-nested-component',
      content: 'This element is far down the page.',
      // No extra config needed; it scrolls automatically
    },
  ]}
  scrollOffset={200} // Customize the offset if needed
  scrollToFirstStep={true}
/>

react-shepherd relies on the configuration of the attachTo property. While Shepherd supports scrolling, it often requires explicit definition of the scrollable container or specific options to behave correctly in complex React layouts, especially those using CSS transforms or fixed headers.

// react-shepherd: Explicit attachment configuration
tour.addStep({
  id: 'scroll-step',
  text: 'This element might be hidden.',
  attachTo: {
    element: '.deeply-nested-component',
    on: 'top',
  },
  scrollTo: { behavior: 'smooth', block: 'center' }, // Explicit scroll config
});

šŸŽØ Styling and Theming: CSS Modules vs Core Themes

Customizing the look of the tour bubbles and buttons is critical for matching your brand.

react-joyride provides a set of default styles that are easy to override using standard CSS classes or styled-components. It exposes specific class names for every part of the tooltip (beacon, overlay, button), allowing granular control without fighting the library's structure.

/* react-joyride: Overriding specific parts */
.react-joyride__overlay {
  background: rgba(0, 0, 0, 0.8);
}

.react-joyride__tooltip button {
  background-color: #007bff;
  color: white;
}
// react-joyride: Applying custom classes
<Joyride
  steps={steps}
  styles={{
    options: {
      primaryColor: '#007bff',
      zIndex: 1000,
    },
  }}
/>

react-shepherd inherits the theming system of Shepherd.js. It encourages using pre-built themes or writing custom CSS that targets Shepherd's specific class hierarchy. Because it is framework-agnostic, the styling approach is identical to using Shepherd in vanilla JavaScript.

/* react-shepherd: Using Shepherd theme classes */
.shepherd-theme-arrows .shepherd-content {
  background: #fff;
  border-radius: 8px;
}

.shepherd-theme-arrows .shepherd-button {
  background: #007bff;
}
// react-shepherd: Setting the theme on the instance
const tour = new Tour({
  useModalOverlay: true,
  defaultStepOptions: {
    classes: 'shepherd-theme-arrows custom-shadow',
  },
});

⚔ React Integration: Portals and Re-renders

How the library renders its UI in the React DOM tree affects performance and z-index management.

react-joyride uses React Portals internally to render the tooltip and overlay outside the main component hierarchy. This prevents parent CSS rules (like overflow: hidden or z-index) from breaking the tour UI. It seamlessly integrates with React's re-render cycle.

// react-joyride: Handles portals automatically
// The tooltip renders at document.body level by default
<Joyride steps={steps} />

react-shepherd also supports rendering via portals, but since the logic lives in the Shepherd instance, you must ensure the DOM nodes it tries to attach to exist when the tour runs. In highly dynamic React apps where components mount/unmount rapidly, you may need to add checks to ensure the target element is present before adding a step.

// react-shepherd: Checking element existence
useEffect(() => {
  const element = document.querySelector('.dynamic-target');
  if (element) {
    tour.addStep({
      attachTo: { element: '.dynamic-target', on: 'bottom' },
      // ...
    });
  }
}, [tour]);

šŸ”„ Similarities: Shared Capabilities

Despite their architectural differences, both libraries cover the fundamental requirements of a modern onboarding tour.

1. šŸ›‘ Lifecycle Control

Both allow you to pause, resume, or cancel tours based on user actions or application state.

// react-joyride: Controlled via props
<Joyride run={isTourActive} callback={handleCallback} />

// react-shepherd: Controlled via methods
tour.pause();
tour.cancel();

2. šŸ–±ļø Interactive Beacons

Both support showing a "beacon" (a pulsing dot) before opening the full tooltip, giving users a chance to ignore the tour if they choose.

// react-joyride
<Joyride steps={[{ target: '.btn', content: 'Click me', disableBeacon: false }]} />

// react-shepherd
tour.addStep({
  showOn: () => true,
  when: {
    show: () => {
      // Custom beacon logic can be injected here
    }
  }
});

3. āŒØļø Accessibility Support

Both libraries strive to meet accessibility standards by managing focus and supporting keyboard navigation (Esc to close, Tab to navigate buttons).

// Both libraries handle focus trapping internally
// react-joyride: <Joyride disableCloseOnEsc={false} />
// react-shepherd: defaultStepOptions: { cancelIcon: { enabled: true } }

šŸ“Š Summary: Key Differences

Featurereact-joyridereact-shepherd
ArchitectureDeclarative (React-native)Imperative (Wrapper around Shepherd.js)
Setup ComplexityLow (Props-based)Medium (Instance management)
ScrollingAutomatic and robustConfigurable, requires explicit setup
StylingReact-friendly style propsCSS class-based (Shepherd themes)
EcosystemReact-specific pluginsCross-framework (Vue, Angular, Vanilla)
Bundle SourceBuilt for ReactDepends on Shepherd.js core

šŸ’” The Big Picture

react-joyride is the pragmatic choice for most React teams. It removes the friction of managing tour state and DOM calculations, letting you define tours as data. It feels like a natural extension of React, handling edge cases like scrolling and portals so you don't have to. Use it when you want to ship a reliable onboarding experience quickly.

react-shepherd is the strategic choice if you are already invested in the Shepherd.js ecosystem or need to maintain consistent tour behavior across a polyglot frontend (e.g., a dashboard with React and Angular parts). It offers raw power and fidelity to the Shepherd API but demands more boilerplate to integrate cleanly with React's lifecycle.

Final Thought: If your team speaks React fluently, react-joyride will feel like home. If your team values framework independence and already knows Shepherd.js, react-shepherd will feel like a familiar tool.

How to Choose: react-joyride vs react-shepherd

  • react-joyride:

    Choose react-joyride if you want a 'batteries-included' solution that feels native to React. It is ideal for teams that prefer declarative configuration over imperative instance management and need built-in support for React-specific patterns like portals and controlled state without extra setup. It simplifies complex scenarios like scrolling to elements and handling dynamic content updates automatically.

  • react-shepherd:

    Choose react-shepherd if your project requires strict adherence to the Shepherd.js API or if you need to share tour logic across different frameworks (e.g., React and Vue) using the same core configuration. It is suitable for teams comfortable with imperative patterns who need maximum flexibility in theming and step manipulation that matches the vanilla Shepherd.js documentation exactly.

README for react-joyride

React Joyride

CI Quality Gate Status Coverage

Create awesome tours for your app!

Showcase your app to new users or explain the functionality of new features.

Joyride example image

Highlights

  • šŸŽØ Easy to use: Just set the steps and you're good to go
  • šŸ”§ Customizable: Use your own components and styles
  • ♿ Accessible: Focus trapping, keyboard navigation, and ARIA support
  • šŸ“¦ Lightweight: ~30% smaller bundle than v2
  • šŸ”„ Broad compatibility: React 16.8+ through React 19
  • šŸ–„ļø SSR-safe: Works with Next.js, Remix, and other server-rendering frameworks

Resources

Setup

npm i react-joyride

Quick Start

Component API

import { Joyride } from 'react-joyride';

const steps = [
  { target: '.my-first-step', content: 'This is my awesome feature!' },
  { target: '.my-other-step', content: 'This is another awesome feature!' },
];

export function App() {
  return (
    <div>
      <Joyride run steps={steps} />
      {/* your app */}
    </div>
  );
}

Hook API

import { useJoyride } from 'react-joyride';

const steps = [
  { target: '.my-first-step', content: 'This is my awesome feature!' },
  { target: '.my-other-step', content: 'This is another awesome feature!' },
];

export function App() {
  const { controls, state, Tour } = useJoyride({ steps });

  return (
    <div>
      <button onClick={() => controls.start()}>Start Tour</button>
      {Tour}
      {/* your app */}
    </div>
  );
}

Agent Skill

Enable AI assistants to implement guided tours with React Joyride.

The skill gives your AI assistant comprehensive knowledge of the API, step configuration, events, custom components, and common patterns.

npx skills add gilbarbara/react-joyride

Migration from v2

Check the migration guide for detailed instructions on upgrading from v2.

License

MIT