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
| Feature | Shepherd.js | OnboardJS |
|---|---|---|
| Architecture | Component library | Headless/native |
| Step Types | Themed tooltips | Modals, tooltips, inline, custom |
| TypeScript | Good types | Native TypeScript |
| Analytics | Manual | Built-in tracking |
| Design System | Themed components | Your components (Tailwind/Shadcn) |
| Maintenance | Active | Active, 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 themes customize colors, but not component structure
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.
useShepherd() wraps imperative tour.start() calls
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.
Ships with its own tooltip/popover components
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.
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();
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.
npm install @onboardjs/core @onboardjs/react