OnboardJS vs Shepherd.js

Shepherd.js Alternative: Why Devs Switch to OnboardJS

Shepherd.js is well-maintained and capable, but it still renders its own UI. If you want your onboarding to be indistinguishable from your app, OnboardJS's fully headless approach is the answer.

At a Glance

Feature Comparison

FeatureShepherd.jsOnboardJS
ArchitectureComponent libraryHeadless/native
Step TypesThemed tooltipsModals, tooltips, inline, custom
TypeScriptGood typesNative TypeScript
AnalyticsManualBuilt-in tracking
Design SystemThemed componentsYour components (Tailwind/Shadcn)
MaintenanceActiveActive, React-first

Why Switch?

The Architectural Differences

Themed vs Truly Headless

Shepherd provides theming options, but you're still working within their component structure. OnboardJS gives you a blank canvas—your components, your layout, your rules.

Shepherd.js

Shepherd themes customize colors, but not component structure

OnboardJS

Render a Drawer, Modal, Tooltip, or inline content—your choice

React Integration Depth

Shepherd's React wrapper works, but it's a wrapper around a vanilla JS library. OnboardJS is React from the ground up—hooks, context, and Suspense-ready.

Shepherd.js

useShepherd() wraps imperative tour.start() calls

OnboardJS

useOnboarding() gives you reactive state and declarative control

Bring Your Own UI

Shepherd ships with Popper.js and its own rendering layer. OnboardJS is headless—you bring the UI components you already have, so there's no visual dependency to manage.

Shepherd.js

Ships with its own tooltip/popover components

OnboardJS

Use your existing Shadcn/Radix/custom components

Migration

Before & After

See the difference in code. OnboardJS lets you use your own components while keeping the API simple.

BeforeShepherd.js: Object-oriented, themed components
typescript
20 lines
import Shepherd from 'shepherd.js';
import 'shepherd.js/dist/css/shepherd.css';

const tour = new Shepherd.Tour({
  defaultStepOptions: {
    cancelIcon: { enabled: true },
    classes: 'custom-class',
  },
});

tour.addStep({
  id: 'intro',
  text: 'Welcome!',
  attachTo: { element: '.intro', on: 'bottom' },
  buttons: [
    { text: 'Next', action: tour.next },
  ],
});

tour.start();
AfterOnboardJS: Headless, your components
tsx
20 lines
import { OnboardingProvider } from '@onboardjs/react';
import { Popover } from '@/components/ui/popover'; // Your component

const steps = [
  {
    id: 'intro',
    target: '.intro',
    component: ({ onNext, onDismiss }) => (
      <Popover>
        <p>Welcome!</p>
        <Button onClick={onNext}>Next</Button>
        <Button variant="ghost" onClick={onDismiss}>×</Button>
      </Popover>
    ),
  },
];

<OnboardingProvider steps={steps}>
  <App />
</OnboardingProvider>

Ready to drop the overlays?

Start building onboarding that feels native to your app.

Terminal
1 lines
npm install @onboardjs/core @onboardjs/react