OnboardJS vs Intro.js
Intro.js Alternative: Why Devs Switch to OnboardJS
Intro.js pioneered product tours, but its jQuery-era architecture shows its age. If you're building with React, Next.js, or any modern framework, OnboardJS gives you a native experience.
At a Glance
Feature Comparison
| Feature | Intro.js | OnboardJS |
|---|---|---|
| Architecture | DOM manipulation | Headless/native |
| Step Types | Tooltips only | Modals, tooltips, inline, custom |
| TypeScript | Community types | Native TypeScript |
| Analytics | None | Built-in tracking |
| Design System | Fixed CSS themes | Your components (Tailwind/Shadcn) |
| Maintenance | Legacy codebase | Modern React patterns |
Why Switch?
The Architectural Differences
Why Intro.js Doesn't Fit Modern React
Intro.js manipulates the DOM directly, fighting against React's virtual DOM. This creates race conditions, stale references, and hydration mismatches in SSR apps.
introJs().start() runs outside React lifecycle, causing ref issues
OnboardJS is a React hook—it respects component lifecycle and state
The Styling Prison
Intro.js comes with its own CSS that you must override. Want to use Tailwind? You'll be writing !important rules. Want Shadcn components? Not possible.
introjs.css must be imported and heavily overridden
No CSS required—render your own Tailwind-styled components
No State Management Integration
Intro.js has no concept of React state. Persisting progress, conditional steps, or integrating with your app's state management requires complex workarounds.
Manual localStorage calls to save progress
Built-in persistence, or bring your own state adapter
Migration
Before & After
See the difference in code. OnboardJS lets you use your own components while keeping the API simple.
import introJs from 'intro.js';
import 'intro.js/introjs.css';
function App() {
useEffect(() => {
introJs()
.setOptions({
steps: [
{
element: document.querySelector('.step-1'),
intro: 'Welcome to the app!',
},
],
showProgress: true,
showBullets: false,
})
.oncomplete(() => {
localStorage.setItem('tour_complete', 'true');
})
.start();
}, []);
return <div className="step-1">...</div>;
}
import { OnboardingProvider, useOnboarding } from '@onboardjs/react';
const steps = [
{
id: 'welcome',
component: () => (
<Card className="p-4"> {/* Your Shadcn Card */}
<h3>Welcome to the app!</h3>
</Card>
),
},
];
function App() {
return (
<OnboardingProvider
steps={steps}
persist={{ key: 'onboarding', storage: 'localStorage' }}
>
<YourApp />
</OnboardingProvider>
);
}
Ready to drop the overlays?
Start building onboarding that feels native to your app.
npm install @onboardjs/core @onboardjs/react