onboardjs-react

Integrate OnboardJS into React projects for building user onboarding flows. Use when: (1) Setting up OnboardJS in a React/Next.js project (2) Creating multi-step onboarding, wizards, or guided flows (3) Implementing conditional navigation, data persistence, or step validation (4) Working with OnboardingProvider, useOnboarding hook, or step components Triggers: "onboarding", "onboardjs", "wizard flow", "user onboarding", "guided tour", "multi-step form"

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "onboardjs-react" with this command: npx skills add onboardjs/onboardjs-skills/onboardjs-onboardjs-skills-onboardjs-react

OnboardJS React Integration

OnboardJS is a headless library for building user onboarding experiences. You control the UI; OnboardJS handles flow logic, state, persistence, and navigation.

Before Starting

1. Verify React project - Check for package.json with React dependencies. If not a React project, inform the user.

2. Detect package manager - Check for lock files to determine the correct install command:

Lock FilePackage ManagerInstall Command
pnpm-lock.yamlpnpmpnpm add @onboardjs/core @onboardjs/react
yarn.lockyarnyarn add @onboardjs/core @onboardjs/react
bun.lockbbunbun add @onboardjs/core @onboardjs/react
package-lock.json or nonenpmnpm install @onboardjs/core @onboardjs/react

3. Detect Next.js - Check for next.config.js, next.config.mjs, or next in dependencies. If Next.js, see Next.js Setup section.

Installation

# npm
npm install @onboardjs/core @onboardjs/react

# pnpm
pnpm add @onboardjs/core @onboardjs/react

# yarn
yarn add @onboardjs/core @onboardjs/react

# bun
bun add @onboardjs/core @onboardjs/react

Quick Setup

1. Define Steps with Components

// steps.tsx
import { OnboardingStep } from '@onboardjs/react'
import { WelcomeStep } from './components/WelcomeStep'
import { ProfileFormStep } from './components/ProfileFormStep'
import { CompleteStep } from './components/CompleteStep'

const steps: OnboardingStep[] = [
  {
    id: 'welcome',
    component: WelcomeStep,
    payload: { title: 'Welcome!', description: 'Let\'s get started' },
    nextStep: 'profile'
  },
  {
    id: 'profile',
    component: ProfileFormStep,
    nextStep: 'complete'
  },
  {
    id: 'complete',
    component: CompleteStep,
    payload: { title: 'All done!' },
    nextStep: null
  }
]

2. Wrap with Provider

import { OnboardingProvider } from '@onboardjs/react'
import { steps } from './steps'

function App() {
  return (
    <OnboardingProvider
      steps={steps}
      onFlowComplete={(ctx) => console.log('Done!', ctx)}
    >
      <OnboardingUI />
    </OnboardingProvider>
  )
}

3. Use the Hook

import { useOnboarding } from '@onboardjs/react'

function OnboardingUI() {
  const { renderStep, next, previous, state, loading } = useOnboarding()

  if (loading.isHydrating) return <Spinner />
  if (state?.isCompleted) return <CompletedScreen />

  return (
    <div>
      {renderStep()}
      <div>
        <button onClick={previous} disabled={state?.isFirstStep}>Back</button>
        <button onClick={() => next()} disabled={!state?.canGoNext}>
          {state?.isLastStep ? 'Finish' : 'Next'}
        </button>
      </div>
    </div>
  )
}

Step Indicator / Progress

Important: The state object does NOT have a steps array. Use currentStepNumber and totalSteps, or calculate from step IDs.

Option 1: Use Built-in Properties (if available)

function OnboardingUI() {
  const { state, renderStep } = useOnboarding()

  return (
    <div>
      {state?.currentStepNumber && state?.totalSteps && (
        <div>Step {state.currentStepNumber} of {state.totalSteps}</div>
      )}
      <progress
        value={state?.currentStepNumber ?? 0}
        max={state?.totalSteps ?? 1}
      />
      {renderStep()}
    </div>
  )
}

Option 2: Calculate from Step IDs (recommended)

For reliable progress tracking, define step IDs once and calculate the index:

// steps.tsx - export your step IDs
export const STEP_IDS = ['welcome', 'profile', 'preferences', 'complete'] as const

export const steps: OnboardingStep[] = [
  { id: 'welcome', component: WelcomeStep, nextStep: 'profile' },
  { id: 'profile', component: ProfileStep, nextStep: 'preferences' },
  { id: 'preferences', component: PreferencesStep, nextStep: 'complete' },
  { id: 'complete', component: CompleteStep, nextStep: null }
]
// OnboardingUI.tsx
import { STEP_IDS } from './steps'

function OnboardingUI() {
  const { state, renderStep } = useOnboarding()

  const currentIndex = STEP_IDS.findIndex(id => id === state?.currentStep?.id)
  const currentStepNumber = currentIndex + 1
  const totalSteps = STEP_IDS.length

  return (
    <div>
      <div>Step {currentStepNumber} of {totalSteps}</div>
      <progress value={currentStepNumber} max={totalSteps} />
      {renderStep()}
    </div>
  )
}

Step Indicator Component

interface StepIndicatorProps {
  stepIds: readonly string[]
  currentStepId: string | undefined
}

function StepIndicator({ stepIds, currentStepId }: StepIndicatorProps) {
  const currentIndex = stepIds.findIndex(id => id === currentStepId)

  return (
    <div className="flex gap-2">
      {stepIds.map((id, index) => (
        <div
          key={id}
          className={`w-3 h-3 rounded-full ${
            index < currentIndex ? 'bg-green-500' :
            index === currentIndex ? 'bg-blue-500' :
            'bg-gray-300'
          }`}
        />
      ))}
    </div>
  )
}

// Usage
<StepIndicator stepIds={STEP_IDS} currentStepId={state?.currentStep?.id} />

Step Component Pattern

import { StepComponentProps } from '@onboardjs/react'

interface ProfilePayload {
  title: string
  fields: string[]
}

const ProfileFormStep: React.FC<StepComponentProps<ProfilePayload>> = ({
  payload,
  context,
  onDataChange,
  initialData
}) => {
  const [name, setName] = useState(initialData?.name || '')

  const handleChange = (value: string) => {
    setName(value)
    onDataChange?.({ name: value }, value.length > 0)
  }

  return (
    <div>
      <h2>{payload.title}</h2>
      <input value={name} onChange={(e) => handleChange(e.target.value)} />
    </div>
  )
}

Next.js Setup

OnboardJS uses React hooks and browser APIs, requiring client-side rendering in Next.js App Router.

Step Components - Add "use client"

All step components must be client components:

// components/WelcomeStep.tsx
'use client'

import { StepComponentProps } from '@onboardjs/react'

export const WelcomeStep: React.FC<StepComponentProps> = ({ payload }) => {
  return <h1>{payload.title}</h1>
}

Provider Wrapper - Add "use client"

Create a client wrapper for the provider:

// components/OnboardingWrapper.tsx
'use client'

import { OnboardingProvider } from '@onboardjs/react'
import { steps } from './steps'

export function OnboardingWrapper({ children }: { children: React.ReactNode }) {
  return (
    <OnboardingProvider
      steps={steps}
      onFlowComplete={(ctx) => console.log('Done!', ctx)}
    >
      {children}
    </OnboardingProvider>
  )
}

Use in Page (App Router)

// app/onboarding/page.tsx
import { OnboardingWrapper } from '@/components/OnboardingWrapper'
import { OnboardingUI } from '@/components/OnboardingUI'

export default function OnboardingPage() {
  return (
    <OnboardingWrapper>
      <OnboardingUI />
    </OnboardingWrapper>
  )
}

Dynamic Import (Optional - for code splitting)

Use dynamic imports to reduce initial bundle size:

// app/onboarding/page.tsx
import dynamic from 'next/dynamic'

const OnboardingWrapper = dynamic(
  () => import('@/components/OnboardingWrapper').then(mod => mod.OnboardingWrapper),
  {
    ssr: false,
    loading: () => <div>Loading onboarding...</div>
  }
)

export default function OnboardingPage() {
  return <OnboardingWrapper><OnboardingUI /></OnboardingWrapper>
}

Dynamic Step Components (Optional - for large flows)

Lazy-load step components to reduce bundle size:

// steps.tsx
'use client'

import dynamic from 'next/dynamic'
import { OnboardingStep } from '@onboardjs/react'

const WelcomeStep = dynamic(() => import('./components/WelcomeStep').then(m => m.WelcomeStep))
const ProfileStep = dynamic(() => import('./components/ProfileStep').then(m => m.ProfileStep))
const CompleteStep = dynamic(() => import('./components/CompleteStep').then(m => m.CompleteStep))

export const steps: OnboardingStep[] = [
  { id: 'welcome', component: WelcomeStep, nextStep: 'profile' },
  { id: 'profile', component: ProfileStep, nextStep: 'complete' },
  { id: 'complete', component: CompleteStep, nextStep: null }
]

Pages Router (Legacy)

For Next.js Pages Router, no "use client" needed but disable SSR:

// pages/onboarding.tsx
import dynamic from 'next/dynamic'

const OnboardingFlow = dynamic(
  () => import('@/components/OnboardingFlow'),
  { ssr: false }
)

export default function OnboardingPage() {
  return <OnboardingFlow />
}

Persistence

localStorage (Simple)

<OnboardingProvider
  steps={steps}
  localStoragePersistence={{ key: 'onboarding_v1', ttl: 604800000 }}
>

Custom Backend

<OnboardingProvider
  steps={steps}
  customOnDataLoad={async () => await fetchFromAPI()}
  customOnDataPersist={async (ctx) => await saveToAPI(ctx)}
  customOnClearPersistedData={async () => await clearAPI()}
>

Conditional Navigation

import { RoleSelectStep } from './components/RoleSelectStep'
import { AdminSetupStep } from './components/AdminSetupStep'
import { UserSetupStep } from './components/UserSetupStep'

{
  id: 'role-select',
  component: RoleSelectStep,
  payload: {
    options: [
      { value: 'admin', label: 'Admin' },
      { value: 'user', label: 'User' }
    ]
  },
  nextStep: (ctx) => ctx.flowData.role === 'admin' ? 'admin-setup' : 'user-setup'
}

Conditional Step Visibility

{
  id: 'admin-setup',
  component: AdminSetupStep,
  condition: (ctx) => ctx.flowData.role === 'admin'
}

Advanced: See References

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

ll-feishu-audio

飞书语音交互技能。支持语音消息自动识别、AI 处理、语音回复全流程。需要配置 FEISHU_APP_ID 和 FEISHU_APP_SECRET 环境变量。使用 faster-whisper 进行语音识别,Edge TTS 进行语音合成,自动转换 OPUS 格式并通过飞书发送。适用于飞书平台的语音对话场景。

Archived SourceRecently Updated
General

test_skill

import json import tkinter as tk from tkinter import messagebox, simpledialog

Archived SourceRecently Updated
General

51mee-resume-profile

简历画像。触发场景:用户要求生成候选人画像;用户想了解候选人的多维度标签和能力评估。

Archived SourceRecently Updated
General

51mee-resume-parse

简历解析。触发场景:用户上传简历文件要求解析、提取结构化信息。

Archived SourceRecently Updated