Nextstep tours
Build product tours, onboarding flows, and interactive tutorials/lessons in Next.js with NextStep v2 (nextstepjs). Use when adding guided tours, walkthroughs, onboarding overlays, feature callouts, interactive lessons, or gated step-by-step tutorials to a Next.js App Router or Pages Router app. Covers NextStepProvider setup, multi-tour configuration, DOM-anchored and modal steps, multi-page tours with nextRoute/prevRoute, NextStepViewport for scrollable containers, custom card components, validation-gated progression, dark mode, analytics callbacks, and localization. Triggers: nextstep, nextstepjs, product tour, onboarding, walkthrough, guided tour, feature tour, tutorial overlay, interactive lesson, step-by-step guide, useNextStep, NextStepProvider, NextStepViewport, CardComponentProps.From its SKILL.md
npx -y skills add jakerains/AgentSkills --skill nextstep-toursAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
15.5 KB, as published. Nobody here has run it
NextStep Tours Skill
Build product tours, onboarding flows, and β most importantly β interactive tutorials with NextStep v2 (nextstepjs) in Next.js. This skill makes the action-gated lesson pattern the default rather than generic welcome popups.
Quick Reference
| Task | Approach | Reference |
|---|---|---|
| Install & wire provider | 3-step Quick Start | this file |
| Define a tour | Tour / Step schema | this file |
| Multi-page tour | nextRoute / prevRoute | references/multi-page-tours.md |
| Scrollable container | NextStepViewport | references/multi-page-tours.md |
| Custom card UI | cardComponent prop | references/custom-card.md |
| Gate progress on user action | Custom card + validation registry | references/tutorial-authoring.md |
| Full props & types | NextStep / Step / CardComponentProps | references/api-reference.md |
| Element not found, SSR, z-index | Troubleshooting table | references/troubleshooting.md |
Core principle: A tour that just shows tooltips teaches nothing. A tutorial that requires the user to perform each action before advancing teaches. Reach for the validation-gated pattern whenever the word "tutorial" or "lesson" appears.
Install
npm i nextstepjs motion
# or: pnpm add nextstepjs motion / yarn add nextstepjs motion / bun add nextstepjs motion
motion is the peer dependency (the library formerly known as Framer Motion, used for step transitions).
Pages Router users only: if you hit ES module errors at runtime, see references/troubleshooting.md for the next.config.js fix.
Minimal Working Example (App Router)
Three files. Drop-in ready.
1. lib/tours.ts
import type { Tour } from 'nextstepjs';
export const tours: Tour[] = [
{
tour: 'welcome',
steps: [
{
icon: 'π',
title: 'Welcome',
content: "Let's take a quick look around.",
selector: '#welcome-banner',
side: 'bottom',
showControls: true,
showSkip: true,
pointerPadding: 10,
pointerRadius: 8,
},
{
icon: 'π§',
title: 'Navigation',
content: 'Your main nav lives here.',
selector: '#main-nav',
side: 'right',
showControls: true,
showSkip: true,
},
{
icon: 'π',
title: 'All set',
content: "You're ready. Explore freely.",
showControls: true,
},
],
},
];
2. app/layout.tsx
import { NextStepProvider, NextStep } from 'nextstepjs';
import { tours } from '@/lib/tours';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<NextStepProvider>
<NextStep steps={tours}>
{children}
</NextStep>
</NextStepProvider>
</body>
</html>
);
}
3. components/start-tour-button.tsx (client component that triggers the tour)
'use client';
import { useNextStep } from 'nextstepjs';
export function StartTourButton() {
const { startNextStep } = useNextStep();
return <button onClick={() => startNextStep('welcome')}>Take a tour</button>;
}
Render <StartTourButton /> anywhere inside the provider tree. Add id="welcome-banner" and id="main-nav" to the elements the steps target.
The Step Shape
All props a single step supports β required ones first, optional below. Source of truth for every example in this skill.
type Step = {
// ββ Required ββββββββββββββββββββββββββββββββββββββββββββββ
icon: React.ReactNode | string | null; // emoji string, JSX, or null
title: string; // step heading
content: React.ReactNode; // body (string or JSX)
// ββ Targeting βββββββββββββββββββββββββββββββββββββββββββββ
selector?: string; // CSS #id of the element to anchor to. Omit for a centered modal step.
side?: 'top' | 'bottom' | 'left' | 'right'
| 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
| 'left-top' | 'left-bottom' | 'right-top' | 'right-bottom';
// ββ Pointer (the keyhole highlight) βββββββββββββββββββββββ
pointerPadding?: number; // px of breathing room around target
pointerRadius?: number; // border-radius of keyhole (px)
// ββ Default-card controls (ignored if using a custom card) β
showControls?: boolean; // show Next / Prev buttons
showSkip?: boolean; // show Skip button
// ββ Behavior ββββββββββββββββββββββββββββββββββββββββββββββ
blockKeyboardControl?: boolean; // disable β/β/Esc for this step (use on form-input steps)
disableInteraction?: boolean; // block clicks/hover on the highlighted element (view-only steps)
// ββ Multi-page routing ββββββββββββββββββββββββββββββββββββ
nextRoute?: string; // route to push when the user clicks Next
prevRoute?: string; // route to push when the user clicks Prev
// ββ Scrollable containers βββββββββββββββββββββββββββββββββ
viewportID?: string; // id of a <NextStepViewport> ancestor; positions relative to that container
};
Gotcha: showControls and showSkip only affect NextStep's default card. If you use a custom cardComponent, you must read step.showControls / step.showSkip yourself and render buttons accordingly (see references/custom-card.md).
Step Patterns
DOM-anchored (most common)
{
icon: 'βοΈ',
title: 'Settings live here',
content: 'Click the gear to manage your account.',
selector: '#settings-gear',
side: 'left',
showControls: true,
showSkip: true,
}
Unanchored modal (intro / outro)
// No `selector` β renders as a centered modal at the top of the document body.
{
icon: 'π',
title: 'Welcome aboard',
content: "This 60-second walkthrough will get you started.",
showControls: true,
}
Multi-page / routed step
// First page β Next pushes /settings, NextStep waits for #settings-header to mount before advancing.
{
icon: 'π',
title: 'Open settings',
content: 'Click Next to head to the settings page.',
selector: '#dashboard-header',
side: 'bottom',
showControls: true,
nextRoute: '/settings',
}
Viewport-confined step (inside a scrollable container)
// Wrap the scrollable element:
<div className="overflow-auto max-h-[400px]">
<NextStepViewport id="data-table">
<table>
<tr id="row-42">β¦</tr>
</table>
</NextStepViewport>
</div>
{
icon: 'π',
title: 'Row 42',
content: 'Scroll position is handled automatically inside the viewport.',
selector: '#row-42',
side: 'top',
viewportID: 'data-table',
showControls: true,
}
Action-gated tutorial step (use for lessons)
A step that does not advance until the user performs a real action. Requires a custom card backed by a validation registry. See references/tutorial-authoring.md for the full pattern and assets/validation-card.tsx + assets/validation-registry.ts for drop-in code.
// The step itself looks normal:
{
icon: 'βοΈ',
title: 'Type your username',
content: 'Enter at least 3 characters in the field above.',
selector: '#username-field',
side: 'bottom',
showControls: true,
blockKeyboardControl: true, // prevent accidental Esc while typing
}
// The magic lives in the validation registry β keyed by [tourName][stepIndex].
// See references/tutorial-authoring.md.
The useNextStep Hook
Every method and property the hook returns. Import from nextstepjs; must be called inside a 'use client' component.
'use client';
import { useNextStep } from 'nextstepjs';
const {
startNextStep, // (tourName: string) => void β start a specific tour by id
closeNextStep, // () => void β exit the current tour
currentTour, // string | null β id of the active tour, or null
currentStep, // number β 0-indexed step pointer
setCurrentStep, // (step: number, delay?: number) => void β jump to a step (optional ms delay)
isNextStepVisible, // boolean β is the overlay currently rendered
} = useNextStep();
Start on user click
'use client';
import { useNextStep } from 'nextstepjs';
export function HelpButton() {
const { startNextStep } = useNextStep();
return <button onClick={() => startNextStep('welcome')} aria-label="Start tour">Help</button>;
}
Auto-start for first-time users only
'use client';
import { useEffect } from 'react';
import { useNextStep } from 'nextstepjs';
export function OnboardingAutostart({ userId }: { userId: string }) {
const { startNextStep } = useNextStep();
useEffect(() => {
const key = `onboarded:${userId}`;
if (localStorage.getItem(key) === 'true') return;
startNextStep('welcome');
localStorage.setItem(key, 'true');
}, [userId, startNextStep]);
return null;
}
Never call startNextStep during render β always inside useEffect or an event handler.
Analytics & Lifecycle Callbacks
NextStep accepts four lifecycle props. Wire them into your analytics pipeline for funnel visibility.
'use client';
import { NextStep, NextStepProvider } from 'nextstepjs';
import { tours } from '@/lib/tours';
import { track } from '@/lib/analytics';
export function NextStepShell({ children }: { children: React.ReactNode }) {
return (
<NextStepProvider>
<NextStep
steps={tours}
onStart={(tourName) => track('tour_started', { tourName })}
onStepChange={(step, tourName) => track('tour_step_viewed', { tourName, step })}
onComplete={(tourName) => track('tour_completed', { tourName })}
onSkip={(step, tourName) => track('tour_skipped', { tourName, step })}
>
{children}
</NextStep>
</NextStepProvider>
);
}
Use onComplete to persist a "tutorialDone" flag so the tour never auto-replays. Use onSkip with the step index to learn where users abandon β that's your tutorial's weakest link.
Designing Tutorials vs. Welcome Tours
The difference between a forgettable popup parade and a real lesson:
- Gate progression on action, not clicks. If the user can spam Next without doing the thing, they will. Use a custom card + validation registry (
references/tutorial-authoring.md) so "Next" actually requires the user to complete the step's task. - One sentence per step. The UI is the teacher; the card is just the label. If content needs more than one sentence, split the step.
- Always include
showSkip: true. People who know the product hate tours. Let them out instantly. - Set
blockKeyboardControl: trueon input steps. Prevents accidental Esc-to-dismiss while the user is typing into a field the step is highlighting. - Use
disableInteraction: truefor view-only steps. Stops the user from clicking the highlighted thing when you want them to read about it, not activate it. - Multi-page lessons need placeholder anchors. If step N+1 lives on
/settingsand that page fetches data async, render a minimal placeholder element with the step'sidbefore the fetch resolves β otherwisenextRoutenavigates but the step never appears. Details inreferences/multi-page-tours.md. - Branch with separate tours, not conditional steps. For novice-vs-expert paths, define two
Tourobjects and pick which one tostartNextStep()based on user state. One tour = one linear flow. - Resume with
setCurrentStep(index, delay). On mount, read a persisted step index fromlocalStorageand jump there with a small delay to let the DOM settle. Pair withonStepChangeto save progress. - Write analytics events per step, not per tour.
tour_step_viewed+step_failed_validationtells you which specific step is frustrating users.tour_completedalone doesn't. - Localize by generating steps inside a hook. NextStep has no built-in i18n; wrap your
toursdefinition in a function that acceptst()from your i18n library and regenerates when locale changes.
Deeper coverage of each principle, with the full validation registry pattern and 4 real validator archetypes, is in references/tutorial-authoring.md.
Copy-Paste Starters
Drop-in files in assets/:
assets/provider-layout.tsxβ complete App Routerlayout.tsxwrapping children inNextStepProvider+NextStepwith a custom card, callbacks, and all overlay props set to sensible defaults.assets/tours.tsβ representative multi-tour file: one welcome tour, one feature tour withnextRoute, one gated-lesson tour paired with the validation registry.assets/custom-card.tsxβ Tailwind + dark-mode aware card, exercises everyCardComponentPropsfield.assets/validation-card.tsxβ variant that reads fromvalidation-registry.ts, awaits the validator, shows inline error on fail.assets/validation-registry.tsβ typed[tourName][stepIndex]map with three real validator archetypes (DOM input, async fetch, viewport size).
Copy the files, wire the imports, and adjust selectors/IDs to match your app.
References
references/api-reference.mdβ Every prop onNextStep,Step,Tour,CardComponentProps, andNavigationAdapter, with types, defaults, and the full exports list. Read this when you need the authoritative signature for any prop.references/custom-card.mdβ Full paste-ready Tailwind + dark-mode card, thearrowslot, overlay tuning (shadowRgb,shadowOpacity,overlayZIndex,clickThroughOverlay), pointer styling, and accessibility notes.references/multi-page-tours.mdβnextRoute/prevRoutelifecycle, the placeholder-anchor pattern for async pages,NextStepViewportfor scrollable containers, and when a customnavigationAdapteris needed.references/tutorial-authoring.mdβ The differentiating content. Validation registry pattern with four validator archetypes (input, click-tracked, async API, viewport gate), pacing rules, branching strategy, resume-later withlocalStorage+setCurrentStep, analytics taxonomy, and localization viagetLocalizedSteps().references/troubleshooting.mdβ Problem β Fix table covering element-not-found timing, Pages Router ESM errors, dark mode scope, tour re-firing, z-index conflicts, Strict Mode double-invoke, and hydration.