@nextsparkjs/plugin-walkme
v0.1.0-beta.113
Published
Guided tours and onboarding system for NextSpark applications. Supports declarative tour definitions, multiple step types (tooltip, modal, spotlight, beacon), cross-page navigation, conditional triggers, localStorage persistence, full keyboard accessibili
Downloads
852
Readme
@nextsparkjs/plugin-walkme
Guided tours and onboarding system for NextSpark applications. Supports declarative tour definitions, multiple step types (tooltip, modal, spotlight, beacon), cross-page navigation, conditional triggers, localStorage persistence, full keyboard accessibility, and i18n.
Installation
The plugin is available as an npm package or can be copied directly into your project.
npm (recommended)
pnpm add @nextsparkjs/plugin-walkmeManual
Copy the plugins/walkme/ directory to contents/plugins/walkme/ in your NextSpark project.
Register the plugin
Add 'walkme' to your theme configuration:
// contents/themes/<your-theme>/config/theme.config.ts
export const themeConfig: ThemeConfig = {
plugins: ['walkme'],
}Rebuild the registry:
node core/scripts/build/registry.mjsQuick Start
1. Define a tour
import type { Tour } from '@nextsparkjs/plugin-walkme/types/walkme.types'
const onboardingTour: Tour = {
id: 'getting-started',
name: 'Getting Started',
trigger: { type: 'onFirstVisit', delay: 1000 },
steps: [
{
id: 'welcome',
type: 'modal',
title: 'Welcome!',
content: 'Let us show you around the application.',
actions: ['next', 'skip'],
},
{
id: 'sidebar',
type: 'tooltip',
target: '[data-cy="sidebar-nav"]',
title: 'Navigation',
content: 'Use the sidebar to navigate between sections.',
position: 'right',
actions: ['next', 'prev', 'skip'],
},
{
id: 'create',
type: 'spotlight',
target: '[data-cy="create-button"]',
title: 'Create New Item',
content: 'Click here to create your first item.',
actions: ['complete', 'prev'],
},
],
}2. Wrap your app with the provider
import { WalkmeProvider } from '@nextsparkjs/plugin-walkme/components/WalkmeProvider'
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<WalkmeProvider tours={[onboardingTour]} autoStart>
{children}
</WalkmeProvider>
)
}3. (Optional) Control tours programmatically
import { useWalkme } from '@nextsparkjs/plugin-walkme/hooks/useWalkme'
function HelpButton() {
const { startTour, isActive } = useWalkme()
return (
<button onClick={() => startTour('getting-started')} disabled={isActive}>
Start Tour
</button>
)
}Configuration
Tour
| Property | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Unique identifier |
| name | string | Yes | Human-readable name |
| description | string | No | Tour description |
| trigger | TourTrigger | Yes | When and how the tour activates |
| conditions | TourConditions | No | Conditions that must be met to show the tour |
| steps | TourStep[] | Yes | Ordered list of steps (minimum 1) |
| onComplete | () => void | No | Callback on tour completion |
| onSkip | () => void | No | Callback on tour skip |
| priority | number | No | Auto-trigger ordering (lower = higher priority) |
TourStep
| Property | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Unique step identifier |
| type | StepType | Yes | 'tooltip' | 'modal' | 'spotlight' | 'beacon' | 'floating' |
| title | string | Yes | Step title |
| content | string | Yes | Step body text |
| target | string | Conditional | CSS selector or data-walkme-target value. Required for tooltip, spotlight, beacon |
| route | string | No | Route path for cross-page steps |
| position | StepPosition | No | 'top' | 'bottom' | 'left' | 'right' | 'auto'. Default: 'auto' |
| actions | StepAction[] | Yes | Available actions: 'next', 'prev', 'skip', 'complete', 'close' |
| delay | number | No | Delay in ms before showing |
| autoAdvance | number | No | Auto-advance after this many ms |
| beforeShow | () => void | No | Callback before the step renders |
| afterShow | () => void | No | Callback after the step renders |
Step Types
modal- Centered overlay modal. No target element required. Use for welcome screens and informational messages.tooltip- Anchored tooltip positioned next to a target element. Requirestarget.spotlight- Overlay with a cutout around the target element plus a tooltip. Requirestarget.beacon- Pulsing indicator on a target element that expands on click. Requirestarget.floating- Same as modal. Alias for centered content without a target.
TourTrigger
| Property | Type | Required | Description |
|---|---|---|---|
| type | TriggerType | Yes | 'onFirstVisit' | 'onRouteEnter' | 'onEvent' | 'manual' | 'scheduled' |
| delay | number | No | Delay in ms before activating |
| route | string | No | Route pattern for onRouteEnter (supports * and ** wildcards) |
| event | string | No | Event name for onEvent trigger |
| afterVisits | number | No | Activate after N visits (for scheduled) |
| afterDays | number | No | Activate after N days since first visit (for scheduled) |
Trigger types:
onFirstVisit- Fires on the user's first page visit (visitCount === 1).onRouteEnter- Fires when the user navigates to a matching route. Supports exact matches (/dashboard), wildcard (/admin/*), and glob (/docs/**).onEvent- Fires when a custom event is emitted viaemitEvent().manual- Never auto-triggers. Start programmatically withstartTour(tourId).scheduled- Fires after a number of visits (afterVisits) or days since first visit (afterDays).
TourConditions
All conditions use AND logic. Every specified condition must pass.
| Property | Type | Description |
|---|---|---|
| userRole | string[] | User must have one of these roles |
| featureFlags | string[] | All specified flags must be active |
| completedTours | string[] | All specified tours must be completed first |
| notCompletedTours | string[] | None of these tours should be completed |
| custom | (ctx: ConditionContext) => boolean | Custom condition function |
WalkmeProvider Props
| Prop | Type | Default | Description |
|---|---|---|---|
| tours | Tour[] | Required | Array of tour definitions (validated with Zod at runtime) |
| children | ReactNode | Required | Application content |
| debug | boolean | false | Enable debug logging to console |
| autoStart | boolean | true | Auto-start eligible tours based on triggers |
| autoStartDelay | number | 1000 | Default delay before auto-starting tours (ms) |
| persistState | boolean | true | Persist tour state in localStorage |
| onTourStart | (event: TourEvent) => void | - | Callback when a tour starts |
| onTourComplete | (event: TourEvent) => void | - | Callback when a tour completes |
| onTourSkip | (event: TourEvent) => void | - | Callback when a tour is skipped |
| onStepChange | (event: TourEvent) => void | - | Callback when the active step changes |
| conditionContext | Partial<ConditionContext> | - | External context for condition evaluation (userRole, featureFlags) |
Hooks
useWalkme()
Main hook for controlling tours. Must be used within a <WalkmeProvider>.
const {
// Tour control
startTour, // (tourId: string) => void
pauseTour, // () => void
resumeTour, // () => void
skipTour, // () => void
completeTour, // () => void
resetTour, // (tourId: string) => void
resetAllTours, // () => void
// Step navigation
nextStep, // () => void
prevStep, // () => void
goToStep, // (stepIndex: number) => void
// State
isActive, // boolean - whether any tour is active
activeTourId, // string | null
currentStepIndex, // number
// Queries
getActiveTour, // () => Tour | null
getActiveStep, // () => TourStep | null
isTourCompleted, // (tourId: string) => boolean
isTourActive, // (tourId: string) => boolean
// Events
emitEvent, // (eventName: string) => void
} = useWalkme()useTour(tourId)
Hook for tracking the state of a specific tour.
const {
tour, // Tour | null - full tour definition
isActive, // boolean
isCompleted, // boolean
isSkipped, // boolean
currentStep, // number (-1 if not active)
totalSteps, // number
progress, // number (0-100)
start, // () => void - start this tour
reset, // () => void - reset this tour
} = useTour('getting-started')useTourProgress()
Hook for tracking global completion progress across all tours.
const {
completedTours, // number
totalTours, // number
percentage, // number (0-100)
completedTourIds, // string[]
skippedTourIds, // string[]
remainingTours, // number
} = useTourProgress()Examples
Single-Page Tour
A basic onboarding flow on one page:
const basicTour: Tour = {
id: 'getting-started',
name: 'Getting Started',
trigger: { type: 'onFirstVisit', delay: 1000 },
steps: [
{
id: 'welcome',
type: 'modal',
title: 'Welcome!',
content: 'Let us show you around.',
actions: ['next', 'skip'],
},
{
id: 'sidebar',
type: 'tooltip',
target: '[data-cy="sidebar-nav"]',
title: 'Navigation',
content: 'Use the sidebar to navigate.',
position: 'right',
actions: ['next', 'prev', 'skip'],
},
{
id: 'create',
type: 'spotlight',
target: '[data-cy="create-button"]',
title: 'Create Item',
content: 'Click here to create your first item.',
actions: ['complete', 'prev'],
},
],
}Cross-Page Tour
Navigate users between pages during a tour:
const crossPageTour: Tour = {
id: 'explore-app',
name: 'Explore the App',
trigger: { type: 'manual' },
conditions: { completedTours: ['getting-started'] },
steps: [
{
id: 'dashboard',
type: 'tooltip',
target: '[data-cy="dashboard-stats"]',
title: 'Your Stats',
content: 'Key metrics at a glance.',
position: 'bottom',
route: '/dashboard',
actions: ['next', 'skip'],
},
{
id: 'profile',
type: 'spotlight',
target: '[data-cy="profile-form"]',
title: 'Your Profile',
content: 'Complete your profile.',
route: '/settings/profile',
actions: ['complete', 'prev'],
},
],
}When the tour reaches a step with a route that differs from the current page, the provider automatically navigates using router.push() and waits for the target element to appear.
Conditional Tour
Show tours only to specific user roles or when feature flags are active:
const adminTour: Tour = {
id: 'admin-features',
name: 'Admin Features',
priority: 10,
trigger: { type: 'onRouteEnter', route: '/admin/*', delay: 500 },
conditions: {
userRole: ['admin', 'superadmin'],
completedTours: ['getting-started'],
featureFlags: ['admin-panel-v2'],
},
steps: [
{
id: 'admin-welcome',
type: 'modal',
title: 'Admin Dashboard',
content: 'Here are the key admin features.',
actions: ['next', 'skip'],
},
// ...more steps
],
}Pass the user context to the provider:
<WalkmeProvider
tours={[adminTour]}
conditionContext={{
userRole: currentUser.role,
featureFlags: activeFlags,
}}
>
<App />
</WalkmeProvider>Programmatic Tour Control
Start tours on demand and track completion:
function OnboardingDashboard() {
const { startTour } = useWalkme()
const { completedTours, totalTours, percentage } = useTourProgress()
const intro = useTour('getting-started')
return (
<div>
<h2>Onboarding Progress: {percentage}%</h2>
<p>{completedTours} of {totalTours} tours completed</p>
{!intro.isCompleted && (
<button onClick={intro.start}>Start Getting Started Tour</button>
)}
<button onClick={() => startTour('advanced-features')}>
Show Advanced Features
</button>
</div>
)
}Analytics Integration
Track tour events for analytics:
<WalkmeProvider
tours={tours}
onTourStart={(event) => {
analytics.track('tour_started', {
tourId: event.tourId,
tourName: event.tourName,
})
}}
onTourComplete={(event) => {
analytics.track('tour_completed', {
tourId: event.tourId,
totalSteps: event.totalSteps,
})
}}
onTourSkip={(event) => {
analytics.track('tour_skipped', {
tourId: event.tourId,
stepIndex: event.stepIndex,
})
}}
onStepChange={(event) => {
analytics.track('step_changed', {
tourId: event.tourId,
stepId: event.stepId,
stepIndex: event.stepIndex,
})
}}
>Customization
Element Targeting
Steps can target elements using:
- CSS selectors -
#my-id,.my-class,[data-cy="value"] - data-walkme-target - Add
data-walkme-target="name"to any element, then reference astarget: "name"(plain string without special CSS chars) - data-cy - Standard test selectors:
target: '[data-cy="create-button"]'
If the target element is not found within 5 seconds, the step renders without an anchor and a warning is logged in debug mode.
Internationalization
The plugin ships with English and Spanish translations under plugins/walkme/messages/. To add more languages, create a JSON file following the same structure:
{
"walkme": {
"next": "Next",
"prev": "Previous",
"skip": "Skip tour",
"complete": "Complete",
"close": "Close",
"progress": "Step {current} of {total}",
"tourAvailable": "Tour available",
"beaconLabel": "Click to start guided tour",
"modalTitle": "Guided Tour",
"tooltipLabel": "Tour step",
"spotlightLabel": "Highlighted element",
"keyboardHint": "Press Arrow Right for next step, Escape to skip",
"tourCompleted": "Tour completed!",
"tourSkipped": "Tour skipped",
"errorTargetNotFound": "Element not found, skipping step",
"resumeTour": "Resume tour",
"restartTour": "Restart tour"
}
}CSS Variables (Theming)
All walkme components use CSS custom properties for styling. Override them in your global CSS or scoped styles:
:root {
--walkme-bg: #ffffff;
--walkme-text: #111827;
--walkme-text-muted: #6b7280;
--walkme-primary: #3b82f6;
--walkme-border: #e5e7eb;
--walkme-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1);
--walkme-beacon-color: #3b82f6;
--walkme-overlay-color: rgba(0, 0, 0, 0.5);
}
/* Dark mode */
.dark {
--walkme-bg: #1f2937;
--walkme-text: #f9fafb;
--walkme-text-muted: #9ca3af;
--walkme-primary: #60a5fa;
--walkme-border: #374151;
--walkme-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3);
--walkme-beacon-color: #60a5fa;
--walkme-overlay-color: rgba(0, 0, 0, 0.7);
}Keyboard Navigation
When a tour is active:
| Key | Action | |---|---| | Arrow Right | Next step | | Arrow Left | Previous step | | Escape | Skip tour | | Tab | Cycle focus within modals (focus trap) |
Accessibility
- All interactive elements have ARIA labels and roles
- Focus is trapped within modals
- Focus is restored to the previously focused element when a tour ends
- Progress bar uses
role="progressbar"witharia-valuenow/aria-valuemin/aria-valuemax - Keyboard navigation is fully supported
Environment Variables
| Variable | Default | Description |
|---|---|---|
| WALKME_ENABLED | true | Enable/disable the plugin |
| WALKME_DEBUG | false | Debug mode |
| WALKME_AUTO_START | true | Auto-start tours on first visit |
| WALKME_AUTO_START_DELAY | 1000 | Delay before auto-starting (ms) |
| WALKME_PERSIST_STATE | true | Persist state in localStorage |
| WALKME_ANALYTICS_ENABLED | false | Enable analytics event emission |
Troubleshooting
Tour does not start automatically
- Check that
autoStartistrueon the provider (default). - Verify the trigger type matches the situation (e.g.,
onFirstVisitonly fires whenvisitCount === 1). - Check that all conditions are met (role, feature flags, completed tours).
- Open debug mode (
debug={true}) to see console logs. - If using
persistState, the tour may already be marked as completed or skipped in localStorage. CallresetTour(tourId)or clearwalkme-statefrom localStorage.
Target element not found
- Ensure the target selector is correct and the element exists in the DOM.
- For dynamically rendered elements, the plugin waits up to 5 seconds using MutationObserver + polling.
- Use
data-walkme-target="name"for elements that are hard to select with CSS. - Enable debug mode to see warnings about missing targets.
Cross-page navigation not working
- Verify the
routeproperty on each step matches the actual path. - The provider uses
router.push()fromnext/navigation. Ensure you are using the App Router. - If navigation fails, the step is automatically skipped and the tour advances.
State not persisting
- Check that
persistStateistrue(default). - Verify localStorage is available (not in private browsing mode or incognito with storage disabled).
- The state is stored under the key
walkme-statein localStorage.
File Structure
plugins/walkme/
plugin.config.ts # Plugin configuration
package.json # Dependencies and metadata
.env.example # Environment variable template
types/
walkme.types.ts # All TypeScript type definitions
lib/
core.ts # Pure-function state machine (reducer + helpers)
validation.ts # Zod schemas for tour config validation
storage.ts # localStorage persistence adapter
targeting.ts # DOM element targeting (CSS, data-walkme, data-cy)
positioning.ts # @floating-ui/react wrapper for smart positioning
triggers.ts # Tour trigger evaluation
conditions.ts # Tour condition evaluation (AND logic)
plugin-env.ts # Environment variable loader
hooks/
useWalkme.ts # Main public hook (tour control + navigation)
useTour.ts # Per-tour state hook
useTourProgress.ts # Global completion progress hook
useTourState.ts # Internal: state machine + localStorage sync
providers/
walkme-context.ts # React Context definition
components/
WalkmeProvider.tsx # Main provider (state, triggers, rendering)
WalkmeOverlay.tsx # Semi-transparent backdrop overlay
WalkmeTooltip.tsx # Positioned tooltip step
WalkmeModal.tsx # Centered modal step with focus trap
WalkmeSpotlight.tsx # Overlay with cutout + tooltip
WalkmeBeacon.tsx # Pulsing indicator
WalkmeProgress.tsx # Progress bar
WalkmeControls.tsx # Navigation buttons (Next/Prev/Skip/Complete)
messages/
en.json # English translations
es.json # Spanish translations
examples/
basic-tour.ts # Single-page tour example
cross-window-tour.ts # Multi-page tour example
conditional-tour.ts # Role-based conditional tour example
tests/
setup.ts # Jest test setup
tsconfig.json # Test-specific TypeScript config
lib/ # Unit tests for all lib modules (165 tests)
jest.config.cjs # Jest configurationDependencies
@floating-ui/react^0.27.0 - Smart positioning for tooltips and popovers
Peer dependencies (provided by the host project): react, react-dom, next, zod, next-intl, lucide-react, class-variance-authority, clsx, tailwind-merge.
