Stop Reinventing the Onboarding Wheel. Start Building.

Your users need smooth flows. You need clean code, faster. OnboardJS handles the messy state stuff so that you can focus on building.

WelcomeStep.tsx
stepsConfig.ts
function WelcomeStep() {
const { next } = useOnboarding()
return (
<div>
<button onClick={next}>Next</button>
</div>
)
}

React

Examples & Recipes

This page provides practical examples and recipes for using OnboardJS in your React applications. See how to implement common onboarding patterns, customize step rendering, persist progress, and more.


1. Basic Linear Onboarding Flow

tsx
63 lines
1import { type OnboardingStep, OnboardingProvider, useOnboarding } from '@onboardjs/react'
2
3// Define the onboarding steps
4const steps: OnboardingStep[] = [
5  {
6    id: 'welcome',
7    payload: { title: 'Welcome!' },
8    nextStep: 'profile',
9  },
10  {
11    id: 'profile',
12    type: 'SINGLE_CHOICE',
13    payload: {
14      question: 'Your role?',
15      options: [{ id: 'dev', label: 'Developer', value: 'dev' }],
16    },
17    nextStep: 'done',
18  },
19  { id: 'done', payload: { title: 'All set!' }, nextStep: null },
20]
21
22const componentRegistry = {
23  welcome: ({ payload }) => <div>{payload.title}</div>,
24  done: ({ payload }) => <div>{payload.title}</div>,
25  SINGLE_CHOICE: ({ payload, next }) => (
26    <div>
27      <h2>{payload.question}</h2>
28      {payload.options.map((option) => (
29        <button key={option.id} onClick={() => next({ answer: option.value })}>
30          {option.label}
31        </button>
32      ))}
33    </div>
34  ),
35}
36
37function OnboardingUI() {
38  const { currentStep, state, next, previous, renderStep } = useOnboarding()
39
40  if (state.isCompleted) return <div>Onboarding complete!</div>
41
42  return (
43    <div>
44      {renderStep()}
45      <div>
46        <button onClick={() => previous()} disabled={!state.canGoPrevious}>
47          Back
48        </button>
49        <button onClick={() => next()} disabled={!state.canGoNext}>
50          Next
51        </button>
52      </div>
53    </div>
54  )
55}
56
57export default function App() {
58  return (
59    <OnboardingProvider steps={steps} componentRegistry={componentRegistry}>
60      <OnboardingUI />
61    </OnboardingProvider>
62  )
63}

2. Branching Flow Based on User Input

tsx
35 lines
1const steps = [
2  {
3    id: 'start',
4    type: 'INFORMATION',
5    payload: { title: 'Start' },
6    nextStep: 'choose',
7  },
8  {
9    id: 'choose',
10    type: 'SINGLE_CHOICE',
11    payload: {
12      question: 'Are you a developer?',
13      dataKey: 'isDeveloper',
14      options: [
15        { id: 'yes', label: 'Yes', value: true },
16        { id: 'no', label: 'No', value: false },
17      ],
18    },
19    nextStep: (context) =>
20      context.flowData.answers?.isDeveloper ? 'dev-setup' : 'user-setup',
21  },
22  {
23    id: 'dev-setup',
24    type: 'INFORMATION',
25    payload: { title: 'Dev Setup' },
26    nextStep: 'done',
27  },
28  {
29    id: 'user-setup',
30    type: 'INFORMATION',
31    payload: { title: 'User Setup' },
32    nextStep: 'done',
33  },
34  { id: 'done', type: 'INFORMATION', payload: { title: 'Finished!' } },
35]

3. Custom Step Component

tsx
31 lines
1function CustomSurveyStep({ step, payload, next, updateContext }) {
2  const [answer, setAnswer] = React.useState('')
3
4  const handleSubmit = () => {
5    updateContext({ flowData: { survey: answer } })
6  }
7
8  return (
9    <div>
10      <h2>{payload.title}</h2>
11      <input value={answer} onChange={(e) => setAnswer(e.target.value)} />
12      <button onClick={handleSubmit}>Continue</button>
13    </div>
14  )
15}
16
17const steps: OnboardingStep[] = [
18  {
19    id: 'custom-survey',
20  },
21]
22
23const componentRegistry = {
24  'custom-component': CustomSurveyStep,
25  // ...other mappings
26}
27
28<OnboardingProvider steps={steps} componentRegistry={componentRegistry}>
29  {/** Render the onboarding UI with custom components like above */}
30  <OnboardingUI />
31</OnboardingProvider>

4. Persisting Progress with localStorage

tsx
9 lines
1<OnboardingProvider
2  steps={steps}
3  localStoragePersistence={{
4    key: 'onboardjs:my-onboarding',
5    ttl: 1000 * 60 * 60 * 24, // 1 day
6  }}
7>
8  <OnboardingUI />
9</OnboardingProvider>

5. Integrating remote data sources

tsx
19 lines
1<OnboardingProvider
2  steps={steps}
3  customOnDataLoad={async () => {
4    const { data } = await supabase
5      .from('onboarding')
6      .select('context')
7      .eq('user_id', user.id)
8      .single()
9    return data?.context || undefined
10  }}
11  customOnDataPersist={async (context) => {
12    await supabase.from('onboarding').upsert({ user_id: user.id, context })
13  }}
14  customOnClearPersistedData={async () => {
15    await supabase.from('onboarding').delete().eq('user_id', user.id)
16  }}
17>
18  <OnboardingUI />
19</OnboardingProvider>

6. Handling Flow Completion

tsx
13 lines
1<OnboardingProvider
2  steps={steps}
3  onFlowComplete={(context) => {
4    // Send analytics, redirect, or show a custom message
5    console.log('Onboarding finished!', context)
6    toast('Onboarding Complete!', {
7      description: `Welcome, ${context.flowData?.userName || 'friend'}! You're all set.`,
8      duration: 3000,
9    })
10  }}
11>
12  <OnboardingUI />
13</OnboardingProvider>

7. Using Checklist Steps

tsx
16 lines
1const steps = [
2  {
3    id: 'checklist',
4    type: 'CHECKLIST',
5    payload: {
6      dataKey: 'setupTasks',
7      items: [
8        { id: 'profile', label: 'Complete your profile', isMandatory: true },
9        { id: 'invite', label: 'Invite a teammate', isMandatory: false },
10      ],
11      minItemsToComplete: 1,
12    },
13    nextStep: 'done',
14  },
15  { id: 'done', type: 'INFORMATION', payload: { title: 'All done!' } },
16]

8. Customizing Navigation Buttons

tsx
15 lines
1function CustomNav() {
2  const { state, next, previous, skip } = useOnboarding()
3
4  return (
5    <div>
6      <button onClick={() => previous()} disabled={!state.canGoPrevious}>
7        Back
8      </button>
9      {state.isSkippable && <button onClick={() => skip()}>Skip</button>}
10      <button onClick={() => next()} disabled={!state.canGoNext}>
11        Next
12      </button>
13    </div>
14  )
15}

9. Accessing and Updating Context

tsx
12 lines
1function ShowUserName() {
2  const { state, updateContext } = useOnboarding()
3
4  return (
5    <div>
6      <p>User: {state.context.currentUser?.name}</p>
7      <button onClick={() => updateContext({ currentUser: { name: 'Soma' } })}>
8        Set Name to Soma
9      </button>
10    </div>
11  )
12}

10. Resetting the Onboarding Flow

tsx
5 lines
1function ResetButton() {
2  const { reset } = useOnboarding()
3
4  return <button onClick={() => reset()}>Restart Onboarding</button>
5}

More Recipes

More recipes are coming soon! If you have specific use cases or patterns you'd like to see, please open an issue on our GitHub repository.

Previous
Render step content