Repository files navigation

NextStep

NextStep

NextStep is a lightweight onboarding library for Next.js / React applications. It utilizes motion for smooth animations and supports multiple React frameworks including Next.js, React Router, and Remix.

Some of the use cases:

  • Easier Onboarding: Guide new users with step-by-step tours
  • Engagement Boost: Make help docs interactive, so users learn by doing.
  • Better Error Handling: Skip generic toasters—show users exactly what to fix with tailored tours.
  • Event-Based Tours: Trigger custom tours after key actions to keep users coming back.

The library allows users to use custom cards (tooltips) for easier integration.

If you like the project, please leave a star! ⭐️⭐️⭐️⭐️⭐️

Getting Started

# npm
npm i nextstepjs motion
# pnpm
pnpm add nextstepjs motion
# yarn
yarn add nextstepjs motion
# bun
bun add nextstepjs motion

Navigation Adapters (v2.0+)

NextStep 2.0 introduces a framework-agnostic routing system through navigation adapters. Each adapter is packaged separately to minimize bundle size - only the adapter you import will be included in your bundle.

Important: Make sure to import the adapter you need in your app in order to access full functionality. Without an adapter, navigation features like nextRoute and prevRoute may not work properly.

Built-in Adapters

Next.js

NextStep uses Next.js adapter as default, therefore you don't need to import it.

// app/layout.tsx or pages/_app.tsximport{NextStep,NextStepProvider}from'nextstepjs';exportdefaultfunctionLayout({ children }){return(<NextStepProvider><NextStepsteps={steps}>{children}</NextStep></NextStepProvider>);}
React Router as a Framework
//app/root.tsximport{NextStepProvider,NextStepReact,typeTour}from'nextstepjs';import{useReactRouterAdapter}from'nextstepjs/adapters/react-router';exportdefaultfunctionApp(){return(<NextStepProvider><NextStepReactnavigationAdapter={useReactRouterAdapter}steps={steps}><Outlet/></NextStepReact></NextStepProvider>);}
Remix
// root.tsximport{NextStepProvider,NextStepReact}from'nextstepjs';import{useRemixAdapter}from'nextstepjs/adapters/remix';exportdefaultfunctionApp(){return(<NextStepProvider><NextStepReactnavigationAdapter={useRemixAdapter}steps={steps}><Outlet/></NextStepReact></NextStepProvider>);}
Important Configuration for Vite (React Router or Remix)

If you're using Vite with React Router or Remix, add the following configuration to your vite.config.ts:

exportdefaultdefineConfig({ssr: {noExternal: ['nextstepjs','motion'],},});

Vite also requires next/navigation to be mocked in order to work properly.

  1. Create a mock file for Next, such as next-navigation.ts, and place it in /src/mocks
// Mock for Next.js navigation to prevent build errors with nextstepjs// This file is used to mock Next.js imports when using nextstepjs in a Vite appexportconstuseRouter=()=>{return{push: ()=>{},replace: ()=>{},prefetch: ()=>{},back: ()=>{},forward: ()=>{},refresh: ()=>{},};};exportconstusePathname=()=>{return'';};exportconstuseSearchParams=()=>{returnnewURLSearchParams();};exportconstuseParams=()=>{return{};};
  1. Update vite.config.mts to use the proper alias for Next.js navigation imports
importpathfrom'node:path';import{defineConfig}from'vite';importreactfrom'@vitejs/plugin-react';exportdefaultdefineConfig({plugins: [react()],resolve: {alias: [// Mock Next.js navigation imports that nextstepjs might try to access{find: 'next/navigation',replacement: path.join(process.cwd(),'src/mocks/next-navigation.ts'),},],},});
Custom Navigation Adapter

You can create your own navigation adapter for any routing solution by implementing the NavigationAdapter interface:

import{NextStepReact}from'nextstepjs';importtype{NavigationAdapter}from'nextstepjs';constuseCustomAdapter=(): NavigationAdapter=>{return{push: (path: string)=>{// Your navigation logic here// Example: history.push(path)},getCurrentPath: ()=>{// Your path retrieval logic here// Example: window.location.pathnamereturnwindow.location.pathname;},};};constApp=()=>{return(<NextStepReactnavigationAdapter={useCustomAdapter}steps={steps}>{children}</NextStepReact>);};

Troubleshooting

If you encounter an error related to module exports when using the Pages Router, it is likely due to a mismatch between ES modules (which use export statements) and CommonJS modules (which use module.exports). The nextstepjs package uses ES module syntax, but your Next.js project might be set up to use CommonJS.

To resolve this issue, ensure that your Next.js project is configured to support ES modules. You can do this by updating your next.config.js file to include the following configuration:

/** @type {import('next').NextConfig} */constnextConfig={reactStrictMode: true,experimental: {esmExternals: true,},transpilePackages: ['nextstepjs'],};exportdefaultnextConfig;

Custom Card

You can create a custom card component for greater control over the design:

PropTypeDescription
stepObjectThe current Step object from your steps array, including content, title, etc.
currentStepnumberThe index of the current step in the steps array.
totalStepsnumberThe total number of steps in the onboarding process.
nextStepA function to advance to the next step in the onboarding process.
prevStepA function to go back to the previous step in the onboarding process.
arrowReturns an SVG object, the orientation is controlled by the steps side prop
skipTourA function to skip the tour
'use client';importtype{CardComponentProps}from'nextstepjs';exportconstCustomCard=({
step,
currentStep,
totalSteps,
nextStep,
prevStep,
skipTour,
arrow,}: CardComponentProps)=>{return(<div><h1>{step.icon}{step.title}</h1><h2>{currentStep} of {totalSteps}</h2><p>{step.content}</p><buttononClick={prevStep}>Previous</button><buttononClick={nextStep}>Next</button><buttononClick={skipTour}>Skip</button>{arrow}</div>);};

Custom Arrow

By default NextStep renders a small SVG caret pointing from the card to the highlighted element. You can recolor/resize it with arrowStyle, or replace it entirely with arrowComponent. Both are provider-level props and fully optional (omit them for the default arrow).

import{NextStep}from'nextstepjs';importtype{ArrowComponentProps}from'nextstepjs';// Tweak the built-in caret:<NextStepsteps={steps}arrowStyle={{color: '#6d28d9'}}>{children}</NextStep>;// Or fully replace it. Spread the provided `style` so it stays anchored to the card,// and use `side` (the resolved placement, after any cut-off adjustment) to orient it:constMyArrow=({ side, style }: ArrowComponentProps)=>(<divstyle={{ ...style}}data-side={side}></div>);<NextStepsteps={steps}arrowComponent={MyArrow}>{children}</NextStep>;

Tours Array

NextStep supports multiple "tours", allowing you to create multiple product tours:

import{Tour}from'nextstepjs';conststeps: Tour[]=[{tour: 'firstTour',steps: [// Step objects],},{tour: 'secondTour',steps: [// Step objects],},];

Step Object

PropTypeDescription
iconReact.ReactNode, string, nullOptional. An icon or element to display alongside the step title (used by the default card).
titlestringThe title of your step
contentReact.ReactNodeThe main content or body of the step.
selectorstringOptional. A string used to target an id that this step refers to. If not provided, card will be displayed in the center top of the document body.
side"top", "bottom", "left", "right" (+ corner variants, e.g. "top-left")Optional. Determines where the tooltip should appear relative to the selector.
showControlsbooleanOptional. Determines whether control buttons (next, prev) should be shown if using the default card. Ignored when a custom cardComponent is provided.
showSkipbooleanOptional. Determines whether skip button should be shown if using the default card. Ignored when a custom cardComponent is provided.
blockKeyboardControlbooleanOptional. Determines whether keyboard control should be blocked
pointerPaddingnumberOptional. The padding around the pointer (keyhole) highlighting the target element.
pointerRadiusnumberOptional. The border-radius of the pointer (keyhole) highlighting the target element.
cardOffsetnumberOptional. Gap in pixels between the card and the spotlight highlight; the caret scales with it too (default: 25).
scrollOffsetnumberOptional. Extra clearance in pixels kept above/below the target when it is scrolled into view — useful when a fixed/sticky header would cover it (default: 0).
selectorRetryAttemptsnumberOptional. Extra attempts to find selector when it is missing on the first lookup (for asynchronously rendered targets). 0 keeps the single-lookup behavior (default: 0).
selectorRetryDelaynumberOptional. Delay in milliseconds between selector retry attempts (default: 200).
disableInteractionbooleanOptional. If true, prevents interaction with the highlighted element (default: false).
nextRoutestringOptional. The route to navigate to when moving to the next step.
prevRoutestringOptional. The route to navigate to when moving to the previous step.
viewportIDstringOptional. The id of the viewport element to use for positioning. If not provided, the document body will be used.

NoteNextStep handles card cutoff from screen sides. When the requested side does not have room, NextStep verifies the destination side actually has space before flipping, and otherwise falls back to the side with the most room — so the card stays on-screen instead of swapping into another cramped edge.

Target Anything

Target anything in your app using the element's id attribute.

<divid="nextstep-step1">Onboard Step</div>

Routing During a Tour

NextStep allows you to navigate between different routes during a tour using the nextRoute and prevRoute properties in the step object. These properties enable seamless transitions between different pages or sections of your application.

  • nextRoute: Specifies the route to navigate to when the "Next" button is clicked.
  • prevRoute: Specifies the route to navigate to when the "Previous" button is clicked.

When nextRoute or prevRoute is provided, NextStep will use Next.js's next/navigation to navigate to the specified route.

Using NextStepViewport and viewportID

When a selector is in a scrollable area, it is best to wrap the content of the scrollable area with NextStepViewport. This component takes children and an id as prop. By providing the viewportID to the step, NextStep will target this element within the viewport. This ensures that the step is anchored to the element even if the container is scrollable.

Here's an example of how to use NextStepViewport:

<divclassName="relative overflow-auto h-64"><NextStepViewportid="scrollable-viewport">{children}</NextStepViewport></div>

Example steps

[{tour: 'firsttour',steps: [{icon: <>👋</>,title: 'Tour 1, Step 1',content: <>First tour, first step</>,selector: '#tour1-step1',side: 'top',showControls: true,showSkip: true,pointerPadding: 10,pointerRadius: 10,nextRoute: '/foo',prevRoute: '/bar',},{icon: <>🎉</>,title: 'Tour 1, Step 2',content: <>First tour, second step</>,selector: '#tour1-step2',side: 'top',showControls: true,showSkip: true,pointerPadding: 10,pointerRadius: 10,viewportID: 'scrollable-viewport',},],},{tour: 'secondtour',steps: [{icon: <>🚀</>,title: 'Second tour, Step 1',content: <>Second tour, first step!</>,selector: '#nextstep-step1',side: 'top',showControls: true,showSkip: true,pointerPadding: 10,pointerRadius: 10,nextRoute: '/foo',prevRoute: '/bar',},],},];

NextStep & NextStepReact Props

PropertyTypeDescription
childrenReact.ReactNodeYour website or application content
stepsArray[]Array of Tour objects defining each step of the onboarding
navigationAdapterNavigationAdapterOptional. Router adapter for navigation (defaults to Next.js on NextStep and window adapter on NextStepReact)
showNextStepbooleanControls visibility of the onboarding overlay
shadowRgbstringRGB values for the shadow color surrounding the target area
shadowOpacitystringOpacity value for the shadow surrounding the target area
cardComponentReact.ComponentTypeCustom card component to replace the default one
cardTransitionTransitionMotion transition object for step transitions
onStart(tourName: string | null) => voidCallback function triggered when the tour starts
onStepChange(step: number, tourName: string | null) => voidCallback function triggered when the step changes
onComplete(tourName: string | null) => voidCallback function triggered when the tour completes
onSkip(step: number, tourName: string | null) => voidCallback function triggered when the user skips the tour
clickThroughOverlaybooleanOptional. If true, overlay background is clickable, default is false
disableConsoleLogsbooleanOptional. If true, console logs are disabled, default is false
scrollToTopbooleanOptional. If true, the page will scroll to the top when the tour ends, default is true
noInViewScrollbooleanOptional. If true, the page will not scroll to the target element when it is in view, default is false
overlayZIndexnumberOptional. Base z-index for overlay elements, useful for compatibility with UI libraries like MUI (default: 999)
arrowComponentReact.ComponentType<ArrowComponentProps>Optional. Render a fully custom arrow/caret. Receives the resolved side and the computed positioning style. When omitted, the built-in SVG arrow is used.
arrowStyleReact.CSSPropertiesOptional. Styles merged into the built-in arrow SVG (ignored when arrowComponent is set). Handy for tweaking the caret color or size.

Note When a custom cardComponent is provided, the per-step showControls / showSkip options only affect the built-in card, so they are ignored (and TypeScript omits them from the step type). Your custom card renders its own controls.

<NextStepsteps={steps}showNextStep={true}shadowRgb="55,48,163"shadowOpacity="0.8"cardComponent={CustomCard}cardTransition={{duration: 0.5,type: 'spring'}}onStepChange={(step,tourName)=>console.log(`Step changed to ${step} in ${tourName}`)}onComplete={(tourName)=>console.log(`Tour completed: ${tourName}`)}onSkip={(step,tourName)=>console.log(`Tour skipped: ${step} in ${tourName}`)}clickThroughOverlay={false}overlayZIndex={1400}// Set higher for MUI compatibility (MUI dialogs use 1300)>{children}</NextStep>

useNextStep Hook

useNextStep hook allows you to control the tour from anywhere in your app.

import{useNextStep}from'nextstepjs';
....const{ startNextStep, closeNextStep }=useNextStep();constonClickHandler=(tourName: string)=>{startNextStep(tourName);};

Keyboard Navigation

NextStep supports keyboard navigation:

  • Right Arrow: Next step
  • Left Arrow: Previous step
  • Escape: Skip tour

Localization

NextStep is a lightweight library and does not come with localization support. However, you can easily switch between languages by supplying the steps array based on locale.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

This project is licensed under the MIT License.

Credits

  • Onborda for the inspiration and some code snippets.

About

Lightweight onboarding library for Next.js

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

NextStep

NextStep

NextStep is a lightweight onboarding library for Next.js / React applications. It utilizes motion for smooth animations and supports multiple React frameworks including Next.js, React Router, and Remix.

Some of the use cases:

  • Easier Onboarding: Guide new users with step-by-step tours
  • Engagement Boost: Make help docs interactive, so users learn by doing.
  • Better Error Handling: Skip generic toasters—show users exactly what to fix with tailored tours.
  • Event-Based Tours: Trigger custom tours after key actions to keep users coming back.

The library allows users to use custom cards (tooltips) for easier integration.

If you like the project, please leave a star! ⭐️⭐️⭐️⭐️⭐️

Getting Started

# npm
npm i nextstepjs motion
# pnpm
pnpm add nextstepjs motion
# yarn
yarn add nextstepjs motion
# bun
bun add nextstepjs motion

Navigation Adapters (v2.0+)

NextStep 2.0 introduces a framework-agnostic routing system through navigation adapters. Each adapter is packaged separately to minimize bundle size - only the adapter you import will be included in your bundle.

Important: Make sure to import the adapter you need in your app in order to access full functionality. Without an adapter, navigation features like nextRoute and prevRoute may not work properly.

Built-in Adapters

Next.js

NextStep uses Next.js adapter as default, therefore you don't need to import it.

// app/layout.tsx or pages/_app.tsximport{NextStep,NextStepProvider}from'nextstepjs';exportdefaultfunctionLayout({ children }){return(<NextStepProvider><NextStepsteps={steps}>{children}</NextStep></NextStepProvider>);}
React Router as a Framework
//app/root.tsximport{NextStepProvider,NextStepReact,typeTour}from'nextstepjs';import{useReactRouterAdapter}from'nextstepjs/adapters/react-router';exportdefaultfunctionApp(){return(<NextStepProvider><NextStepReactnavigationAdapter={useReactRouterAdapter}steps={steps}><Outlet/></NextStepReact></NextStepProvider>);}
Remix
// root.tsximport{NextStepProvider,NextStepReact}from'nextstepjs';import{useRemixAdapter}from'nextstepjs/adapters/remix';exportdefaultfunctionApp(){return(<NextStepProvider><NextStepReactnavigationAdapter={useRemixAdapter}steps={steps}><Outlet/></NextStepReact></NextStepProvider>);}
Important Configuration for Vite (React Router or Remix)

If you're using Vite with React Router or Remix, add the following configuration to your vite.config.ts:

exportdefaultdefineConfig({ssr: {noExternal: ['nextstepjs','motion'],},});

Vite also requires next/navigation to be mocked in order to work properly.

  1. Create a mock file for Next, such as next-navigation.ts, and place it in /src/mocks
// Mock for Next.js navigation to prevent build errors with nextstepjs// This file is used to mock Next.js imports when using nextstepjs in a Vite appexportconstuseRouter=()=>{return{push: ()=>{},replace: ()=>{},prefetch: ()=>{},back: ()=>{},forward: ()=>{},refresh: ()=>{},};};exportconstusePathname=()=>{return'';};exportconstuseSearchParams=()=>{returnnewURLSearchParams();};exportconstuseParams=()=>{return{};};
  1. Update vite.config.mts to use the proper alias for Next.js navigation imports
importpathfrom'node:path';import{defineConfig}from'vite';importreactfrom'@vitejs/plugin-react';exportdefaultdefineConfig({plugins: [react()],resolve: {alias: [// Mock Next.js navigation imports that nextstepjs might try to access{find: 'next/navigation',replacement: path.join(process.cwd(),'src/mocks/next-navigation.ts'),},],},});
Custom Navigation Adapter

You can create your own navigation adapter for any routing solution by implementing the NavigationAdapter interface:

import{NextStepReact}from'nextstepjs';importtype{NavigationAdapter}from'nextstepjs';constuseCustomAdapter=(): NavigationAdapter=>{return{push: (path: string)=>{// Your navigation logic here// Example: history.push(path)},getCurrentPath: ()=>{// Your path retrieval logic here// Example: window.location.pathnamereturnwindow.location.pathname;},};};constApp=()=>{return(<NextStepReactnavigationAdapter={useCustomAdapter}steps={steps}>{children}</NextStepReact>);};

Troubleshooting

If you encounter an error related to module exports when using the Pages Router, it is likely due to a mismatch between ES modules (which use export statements) and CommonJS modules (which use module.exports). The nextstepjs package uses ES module syntax, but your Next.js project might be set up to use CommonJS.

To resolve this issue, ensure that your Next.js project is configured to support ES modules. You can do this by updating your next.config.js file to include the following configuration:

/** @type {import('next').NextConfig} */constnextConfig={reactStrictMode: true,experimental: {esmExternals: true,},transpilePackages: ['nextstepjs'],};exportdefaultnextConfig;

Custom Card

You can create a custom card component for greater control over the design:

PropTypeDescription
stepObjectThe current Step object from your steps array, including content, title, etc.
currentStepnumberThe index of the current step in the steps array.
totalStepsnumberThe total number of steps in the onboarding process.
nextStepA function to advance to the next step in the onboarding process.
prevStepA function to go back to the previous step in the onboarding process.
arrowReturns an SVG object, the orientation is controlled by the steps side prop
skipTourA function to skip the tour
'use client';importtype{CardComponentProps}from'nextstepjs';exportconstCustomCard=({
step,
currentStep,
totalSteps,
nextStep,
prevStep,
skipTour,
arrow,}: CardComponentProps)=>{return(<div><h1>{step.icon}{step.title}</h1><h2>{currentStep} of {totalSteps}</h2><p>{step.content}</p><buttononClick={prevStep}>Previous</button><buttononClick={nextStep}>Next</button><buttononClick={skipTour}>Skip</button>{arrow}</div>);};

Custom Arrow

By default NextStep renders a small SVG caret pointing from the card to the highlighted element. You can recolor/resize it with arrowStyle, or replace it entirely with arrowComponent. Both are provider-level props and fully optional (omit them for the default arrow).

import{NextStep}from'nextstepjs';importtype{ArrowComponentProps}from'nextstepjs';// Tweak the built-in caret:<NextStepsteps={steps}arrowStyle={{color: '#6d28d9'}}>{children}</NextStep>;// Or fully replace it. Spread the provided `style` so it stays anchored to the card,// and use `side` (the resolved placement, after any cut-off adjustment) to orient it:constMyArrow=({ side, style }: ArrowComponentProps)=>(<divstyle={{ ...style}}data-side={side}></div>);<NextStepsteps={steps}arrowComponent={MyArrow}>{children}</NextStep>;

Tours Array

NextStep supports multiple "tours", allowing you to create multiple product tours:

import{Tour}from'nextstepjs';conststeps: Tour[]=[{tour: 'firstTour',steps: [// Step objects],},{tour: 'secondTour',steps: [// Step objects],},];

Step Object

PropTypeDescription
iconReact.ReactNode, string, nullOptional. An icon or element to display alongside the step title (used by the default card).
titlestringThe title of your step
contentReact.ReactNodeThe main content or body of the step.
selectorstringOptional. A string used to target an id that this step refers to. If not provided, card will be displayed in the center top of the document body.
side"top", "bottom", "left", "right" (+ corner variants, e.g. "top-left")Optional. Determines where the tooltip should appear relative to the selector.
showControlsbooleanOptional. Determines whether control buttons (next, prev) should be shown if using the default card. Ignored when a custom cardComponent is provided.
showSkipbooleanOptional. Determines whether skip button should be shown if using the default card. Ignored when a custom cardComponent is provided.
blockKeyboardControlbooleanOptional. Determines whether keyboard control should be blocked
pointerPaddingnumberOptional. The padding around the pointer (keyhole) highlighting the target element.
pointerRadiusnumberOptional. The border-radius of the pointer (keyhole) highlighting the target element.
cardOffsetnumberOptional. Gap in pixels between the card and the spotlight highlight; the caret scales with it too (default: 25).
scrollOffsetnumberOptional. Extra clearance in pixels kept above/below the target when it is scrolled into view — useful when a fixed/sticky header would cover it (default: 0).
selectorRetryAttemptsnumberOptional. Extra attempts to find selector when it is missing on the first lookup (for asynchronously rendered targets). 0 keeps the single-lookup behavior (default: 0).
selectorRetryDelaynumberOptional. Delay in milliseconds between selector retry attempts (default: 200).
disableInteractionbooleanOptional. If true, prevents interaction with the highlighted element (default: false).
nextRoutestringOptional. The route to navigate to when moving to the next step.
prevRoutestringOptional. The route to navigate to when moving to the previous step.
viewportIDstringOptional. The id of the viewport element to use for positioning. If not provided, the document body will be used.

NoteNextStep handles card cutoff from screen sides. When the requested side does not have room, NextStep verifies the destination side actually has space before flipping, and otherwise falls back to the side with the most room — so the card stays on-screen instead of swapping into another cramped edge.

Target Anything

Target anything in your app using the element's id attribute.

<divid="nextstep-step1">Onboard Step</div>

Routing During a Tour

NextStep allows you to navigate between different routes during a tour using the nextRoute and prevRoute properties in the step object. These properties enable seamless transitions between different pages or sections of your application.

  • nextRoute: Specifies the route to navigate to when the "Next" button is clicked.
  • prevRoute: Specifies the route to navigate to when the "Previous" button is clicked.

When nextRoute or prevRoute is provided, NextStep will use Next.js's next/navigation to navigate to the specified route.

Using NextStepViewport and viewportID

When a selector is in a scrollable area, it is best to wrap the content of the scrollable area with NextStepViewport. This component takes children and an id as prop. By providing the viewportID to the step, NextStep will target this element within the viewport. This ensures that the step is anchored to the element even if the container is scrollable.

Here's an example of how to use NextStepViewport:

<divclassName="relative overflow-auto h-64"><NextStepViewportid="scrollable-viewport">{children}</NextStepViewport></div>

Example steps

[{tour: 'firsttour',steps: [{icon: <>👋</>,title: 'Tour 1, Step 1',content: <>First tour, first step</>,selector: '#tour1-step1',side: 'top',showControls: true,showSkip: true,pointerPadding: 10,pointerRadius: 10,nextRoute: '/foo',prevRoute: '/bar',},{icon: <>🎉</>,title: 'Tour 1, Step 2',content: <>First tour, second step</>,selector: '#tour1-step2',side: 'top',showControls: true,showSkip: true,pointerPadding: 10,pointerRadius: 10,viewportID: 'scrollable-viewport',},],},{tour: 'secondtour',steps: [{icon: <>🚀</>,title: 'Second tour, Step 1',content: <>Second tour, first step!</>,selector: '#nextstep-step1',side: 'top',showControls: true,showSkip: true,pointerPadding: 10,pointerRadius: 10,nextRoute: '/foo',prevRoute: '/bar',},],},];

NextStep & NextStepReact Props

PropertyTypeDescription
childrenReact.ReactNodeYour website or application content
stepsArray[]Array of Tour objects defining each step of the onboarding
navigationAdapterNavigationAdapterOptional. Router adapter for navigation (defaults to Next.js on NextStep and window adapter on NextStepReact)
showNextStepbooleanControls visibility of the onboarding overlay
shadowRgbstringRGB values for the shadow color surrounding the target area
shadowOpacitystringOpacity value for the shadow surrounding the target area
cardComponentReact.ComponentTypeCustom card component to replace the default one
cardTransitionTransitionMotion transition object for step transitions
onStart(tourName: string | null) => voidCallback function triggered when the tour starts
onStepChange(step: number, tourName: string | null) => voidCallback function triggered when the step changes
onComplete(tourName: string | null) => voidCallback function triggered when the tour completes
onSkip(step: number, tourName: string | null) => voidCallback function triggered when the user skips the tour
clickThroughOverlaybooleanOptional. If true, overlay background is clickable, default is false
disableConsoleLogsbooleanOptional. If true, console logs are disabled, default is false
scrollToTopbooleanOptional. If true, the page will scroll to the top when the tour ends, default is true
noInViewScrollbooleanOptional. If true, the page will not scroll to the target element when it is in view, default is false
overlayZIndexnumberOptional. Base z-index for overlay elements, useful for compatibility with UI libraries like MUI (default: 999)
arrowComponentReact.ComponentType<ArrowComponentProps>Optional. Render a fully custom arrow/caret. Receives the resolved side and the computed positioning style. When omitted, the built-in SVG arrow is used.
arrowStyleReact.CSSPropertiesOptional. Styles merged into the built-in arrow SVG (ignored when arrowComponent is set). Handy for tweaking the caret color or size.

Note When a custom cardComponent is provided, the per-step showControls / showSkip options only affect the built-in card, so they are ignored (and TypeScript omits them from the step type). Your custom card renders its own controls.

<NextStepsteps={steps}showNextStep={true}shadowRgb="55,48,163"shadowOpacity="0.8"cardComponent={CustomCard}cardTransition={{duration: 0.5,type: 'spring'}}onStepChange={(step,tourName)=>console.log(`Step changed to ${step} in ${tourName}`)}onComplete={(tourName)=>console.log(`Tour completed: ${tourName}`)}onSkip={(step,tourName)=>console.log(`Tour skipped: ${step} in ${tourName}`)}clickThroughOverlay={false}overlayZIndex={1400}// Set higher for MUI compatibility (MUI dialogs use 1300)>{children}</NextStep>

useNextStep Hook

useNextStep hook allows you to control the tour from anywhere in your app.

import{useNextStep}from'nextstepjs';
....const{ startNextStep, closeNextStep }=useNextStep();constonClickHandler=(tourName: string)=>{startNextStep(tourName);};

Keyboard Navigation

NextStep supports keyboard navigation:

  • Right Arrow: Next step
  • Left Arrow: Previous step
  • Escape: Skip tour

Localization

NextStep is a lightweight library and does not come with localization support. However, you can easily switch between languages by supplying the steps array based on locale.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

This project is licensed under the MIT License.

Credits

  • Onborda for the inspiration and some code snippets.

About

Lightweight onboarding library for Next.js

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

NextStep

NextStep

NextStep is a lightweight onboarding library for Next.js / React applications. It utilizes motion for smooth animations and supports multiple React frameworks including Next.js, React Router, and Remix.

Some of the use cases:

  • Easier Onboarding: Guide new users with step-by-step tours
  • Engagement Boost: Make help docs interactive, so users learn by doing.
  • Better Error Handling: Skip generic toasters—show users exactly what to fix with tailored tours.
  • Event-Based Tours: Trigger custom tours after key actions to keep users coming back.

The library allows users to use custom cards (tooltips) for easier integration.

If you like the project, please leave a star! ⭐️⭐️⭐️⭐️⭐️

Getting Started

# npm
npm i nextstepjs motion
# pnpm
pnpm add nextstepjs motion
# yarn
yarn add nextstepjs motion
# bun
bun add nextstepjs motion

Navigation Adapters (v2.0+)

NextStep 2.0 introduces a framework-agnostic routing system through navigation adapters. Each adapter is packaged separately to minimize bundle size - only the adapter you import will be included in your bundle.

Important: Make sure to import the adapter you need in your app in order to access full functionality. Without an adapter, navigation features like nextRoute and prevRoute may not work properly.

Built-in Adapters

Next.js

NextStep uses Next.js adapter as default, therefore you don't need to import it.

// app/layout.tsx or pages/_app.tsximport{NextStep,NextStepProvider}from'nextstepjs';exportdefaultfunctionLayout({ children }){return(<NextStepProvider><NextStepsteps={steps}>{children}</NextStep></NextStepProvider>);}
React Router as a Framework
//app/root.tsximport{NextStepProvider,NextStepReact,typeTour}from'nextstepjs';import{useReactRouterAdapter}from'nextstepjs/adapters/react-router';exportdefaultfunctionApp(){return(<NextStepProvider><NextStepReactnavigationAdapter={useReactRouterAdapter}steps={steps}><Outlet/></NextStepReact></NextStepProvider>);}
Remix
// root.tsximport{NextStepProvider,NextStepReact}from'nextstepjs';import{useRemixAdapter}from'nextstepjs/adapters/remix';exportdefaultfunctionApp(){return(<NextStepProvider><NextStepReactnavigationAdapter={useRemixAdapter}steps={steps}><Outlet/></NextStepReact></NextStepProvider>);}
Important Configuration for Vite (React Router or Remix)

If you're using Vite with React Router or Remix, add the following configuration to your vite.config.ts:

exportdefaultdefineConfig({ssr: {noExternal: ['nextstepjs','motion'],},});

Vite also requires next/navigation to be mocked in order to work properly.

  1. Create a mock file for Next, such as next-navigation.ts, and place it in /src/mocks
// Mock for Next.js navigation to prevent build errors with nextstepjs// This file is used to mock Next.js imports when using nextstepjs in a Vite appexportconstuseRouter=()=>{return{push: ()=>{},replace: ()=>{},prefetch: ()=>{},back: ()=>{},forward: ()=>{},refresh: ()=>{},};};exportconstusePathname=()=>{return'';};exportconstuseSearchParams=()=>{returnnewURLSearchParams();};exportconstuseParams=()=>{return{};};
  1. Update vite.config.mts to use the proper alias for Next.js navigation imports
importpathfrom'node:path';import{defineConfig}from'vite';importreactfrom'@vitejs/plugin-react';exportdefaultdefineConfig({plugins: [react()],resolve: {alias: [// Mock Next.js navigation imports that nextstepjs might try to access{find: 'next/navigation',replacement: path.join(process.cwd(),'src/mocks/next-navigation.ts'),},],},});
Custom Navigation Adapter

You can create your own navigation adapter for any routing solution by implementing the NavigationAdapter interface:

import{NextStepReact}from'nextstepjs';importtype{NavigationAdapter}from'nextstepjs';constuseCustomAdapter=(): NavigationAdapter=>{return{push: (path: string)=>{// Your navigation logic here// Example: history.push(path)},getCurrentPath: ()=>{// Your path retrieval logic here// Example: window.location.pathnamereturnwindow.location.pathname;},};};constApp=()=>{return(<NextStepReactnavigationAdapter={useCustomAdapter}steps={steps}>{children}</NextStepReact>);};

Troubleshooting

If you encounter an error related to module exports when using the Pages Router, it is likely due to a mismatch between ES modules (which use export statements) and CommonJS modules (which use module.exports). The nextstepjs package uses ES module syntax, but your Next.js project might be set up to use CommonJS.

To resolve this issue, ensure that your Next.js project is configured to support ES modules. You can do this by updating your next.config.js file to include the following configuration:

/** @type {import('next').NextConfig} */constnextConfig={reactStrictMode: true,experimental: {esmExternals: true,},transpilePackages: ['nextstepjs'],};exportdefaultnextConfig;

Custom Card

You can create a custom card component for greater control over the design:

PropTypeDescription
stepObjectThe current Step object from your steps array, including content, title, etc.
currentStepnumberThe index of the current step in the steps array.
totalStepsnumberThe total number of steps in the onboarding process.
nextStepA function to advance to the next step in the onboarding process.
prevStepA function to go back to the previous step in the onboarding process.
arrowReturns an SVG object, the orientation is controlled by the steps side prop
skipTourA function to skip the tour
'use client';importtype{CardComponentProps}from'nextstepjs';exportconstCustomCard=({
step,
currentStep,
totalSteps,
nextStep,
prevStep,
skipTour,
arrow,}: CardComponentProps)=>{return(<div><h1>{step.icon}{step.title}</h1><h2>{currentStep} of {totalSteps}</h2><p>{step.content}</p><buttononClick={prevStep}>Previous</button><buttononClick={nextStep}>Next</button><buttononClick={skipTour}>Skip</button>{arrow}</div>);};

Custom Arrow

By default NextStep renders a small SVG caret pointing from the card to the highlighted element. You can recolor/resize it with arrowStyle, or replace it entirely with arrowComponent. Both are provider-level props and fully optional (omit them for the default arrow).

import{NextStep}from'nextstepjs';importtype{ArrowComponentProps}from'nextstepjs';// Tweak the built-in caret:<NextStepsteps={steps}arrowStyle={{color: '#6d28d9'}}>{children}</NextStep>;// Or fully replace it. Spread the provided `style` so it stays anchored to the card,// and use `side` (the resolved placement, after any cut-off adjustment) to orient it:constMyArrow=({ side, style }: ArrowComponentProps)=>(<divstyle={{ ...style}}data-side={side}></div>);<NextStepsteps={steps}arrowComponent={MyArrow}>{children}</NextStep>;

Tours Array

NextStep supports multiple "tours", allowing you to create multiple product tours:

import{Tour}from'nextstepjs';conststeps: Tour[]=[{tour: 'firstTour',steps: [// Step objects],},{tour: 'secondTour',steps: [// Step objects],},];

Step Object

PropTypeDescription
iconReact.ReactNode, string, nullOptional. An icon or element to display alongside the step title (used by the default card).
titlestringThe title of your step
contentReact.ReactNodeThe main content or body of the step.
selectorstringOptional. A string used to target an id that this step refers to. If not provided, card will be displayed in the center top of the document body.
side"top", "bottom", "left", "right" (+ corner variants, e.g. "top-left")Optional. Determines where the tooltip should appear relative to the selector.
showControlsbooleanOptional. Determines whether control buttons (next, prev) should be shown if using the default card. Ignored when a custom cardComponent is provided.
showSkipbooleanOptional. Determines whether skip button should be shown if using the default card. Ignored when a custom cardComponent is provided.
blockKeyboardControlbooleanOptional. Determines whether keyboard control should be blocked
pointerPaddingnumberOptional. The padding around the pointer (keyhole) highlighting the target element.
pointerRadiusnumberOptional. The border-radius of the pointer (keyhole) highlighting the target element.
cardOffsetnumberOptional. Gap in pixels between the card and the spotlight highlight; the caret scales with it too (default: 25).
scrollOffsetnumberOptional. Extra clearance in pixels kept above/below the target when it is scrolled into view — useful when a fixed/sticky header would cover it (default: 0).
selectorRetryAttemptsnumberOptional. Extra attempts to find selector when it is missing on the first lookup (for asynchronously rendered targets). 0 keeps the single-lookup behavior (default: 0).
selectorRetryDelaynumberOptional. Delay in milliseconds between selector retry attempts (default: 200).
disableInteractionbooleanOptional. If true, prevents interaction with the highlighted element (default: false).
nextRoutestringOptional. The route to navigate to when moving to the next step.
prevRoutestringOptional. The route to navigate to when moving to the previous step.
viewportIDstringOptional. The id of the viewport element to use for positioning. If not provided, the document body will be used.

NoteNextStep handles card cutoff from screen sides. When the requested side does not have room, NextStep verifies the destination side actually has space before flipping, and otherwise falls back to the side with the most room — so the card stays on-screen instead of swapping into another cramped edge.

Target Anything

Target anything in your app using the element's id attribute.

<divid="nextstep-step1">Onboard Step</div>

Routing During a Tour

NextStep allows you to navigate between different routes during a tour using the nextRoute and prevRoute properties in the step object. These properties enable seamless transitions between different pages or sections of your application.

  • nextRoute: Specifies the route to navigate to when the "Next" button is clicked.
  • prevRoute: Specifies the route to navigate to when the "Previous" button is clicked.

When nextRoute or prevRoute is provided, NextStep will use Next.js's next/navigation to navigate to the specified route.

Using NextStepViewport and viewportID

When a selector is in a scrollable area, it is best to wrap the content of the scrollable area with NextStepViewport. This component takes children and an id as prop. By providing the viewportID to the step, NextStep will target this element within the viewport. This ensures that the step is anchored to the element even if the container is scrollable.

Here's an example of how to use NextStepViewport:

<divclassName="relative overflow-auto h-64"><NextStepViewportid="scrollable-viewport">{children}</NextStepViewport></div>

Example steps

[{tour: 'firsttour',steps: [{icon: <>👋</>,title: 'Tour 1, Step 1',content: <>First tour, first step</>,selector: '#tour1-step1',side: 'top',showControls: true,showSkip: true,pointerPadding: 10,pointerRadius: 10,nextRoute: '/foo',prevRoute: '/bar',},{icon: <>🎉</>,title: 'Tour 1, Step 2',content: <>First tour, second step</>,selector: '#tour1-step2',side: 'top',showControls: true,showSkip: true,pointerPadding: 10,pointerRadius: 10,viewportID: 'scrollable-viewport',},],},{tour: 'secondtour',steps: [{icon: <>🚀</>,title: 'Second tour, Step 1',content: <>Second tour, first step!</>,selector: '#nextstep-step1',side: 'top',showControls: true,showSkip: true,pointerPadding: 10,pointerRadius: 10,nextRoute: '/foo',prevRoute: '/bar',},],},];

NextStep & NextStepReact Props

PropertyTypeDescription
childrenReact.ReactNodeYour website or application content
stepsArray[]Array of Tour objects defining each step of the onboarding
navigationAdapterNavigationAdapterOptional. Router adapter for navigation (defaults to Next.js on NextStep and window adapter on NextStepReact)
showNextStepbooleanControls visibility of the onboarding overlay
shadowRgbstringRGB values for the shadow color surrounding the target area
shadowOpacitystringOpacity value for the shadow surrounding the target area
cardComponentReact.ComponentTypeCustom card component to replace the default one
cardTransitionTransitionMotion transition object for step transitions
onStart(tourName: string | null) => voidCallback function triggered when the tour starts
onStepChange(step: number, tourName: string | null) => voidCallback function triggered when the step changes
onComplete(tourName: string | null) => voidCallback function triggered when the tour completes
onSkip(step: number, tourName: string | null) => voidCallback function triggered when the user skips the tour
clickThroughOverlaybooleanOptional. If true, overlay background is clickable, default is false
disableConsoleLogsbooleanOptional. If true, console logs are disabled, default is false
scrollToTopbooleanOptional. If true, the page will scroll to the top when the tour ends, default is true
noInViewScrollbooleanOptional. If true, the page will not scroll to the target element when it is in view, default is false
overlayZIndexnumberOptional. Base z-index for overlay elements, useful for compatibility with UI libraries like MUI (default: 999)
arrowComponentReact.ComponentType<ArrowComponentProps>Optional. Render a fully custom arrow/caret. Receives the resolved side and the computed positioning style. When omitted, the built-in SVG arrow is used.
arrowStyleReact.CSSPropertiesOptional. Styles merged into the built-in arrow SVG (ignored when arrowComponent is set). Handy for tweaking the caret color or size.

Note When a custom cardComponent is provided, the per-step showControls / showSkip options only affect the built-in card, so they are ignored (and TypeScript omits them from the step type). Your custom card renders its own controls.

<NextStepsteps={steps}showNextStep={true}shadowRgb="55,48,163"shadowOpacity="0.8"cardComponent={CustomCard}cardTransition={{duration: 0.5,type: 'spring'}}onStepChange={(step,tourName)=>console.log(`Step changed to ${step} in ${tourName}`)}onComplete={(tourName)=>console.log(`Tour completed: ${tourName}`)}onSkip={(step,tourName)=>console.log(`Tour skipped: ${step} in ${tourName}`)}clickThroughOverlay={false}overlayZIndex={1400}// Set higher for MUI compatibility (MUI dialogs use 1300)>{children}</NextStep>

useNextStep Hook

useNextStep hook allows you to control the tour from anywhere in your app.

import{useNextStep}from'nextstepjs';
....const{ startNextStep, closeNextStep }=useNextStep();constonClickHandler=(tourName: string)=>{startNextStep(tourName);};

Keyboard Navigation

NextStep supports keyboard navigation:

  • Right Arrow: Next step
  • Left Arrow: Previous step
  • Escape: Skip tour

Localization

NextStep is a lightweight library and does not come with localization support. However, you can easily switch between languages by supplying the steps array based on locale.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

This project is licensed under the MIT License.

Credits

  • Onborda for the inspiration and some code snippets.

About

Lightweight onboarding library for Next.js

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

NextStep

NextStep

NextStep is a lightweight onboarding library for Next.js / React applications. It utilizes motion for smooth animations and supports multiple React frameworks including Next.js, React Router, and Remix.

Some of the use cases:

  • Easier Onboarding: Guide new users with step-by-step tours
  • Engagement Boost: Make help docs interactive, so users learn by doing.
  • Better Error Handling: Skip generic toasters—show users exactly what to fix with tailored tours.
  • Event-Based Tours: Trigger custom tours after key actions to keep users coming back.

The library allows users to use custom cards (tooltips) for easier integration.

If you like the project, please leave a star! ⭐️⭐️⭐️⭐️⭐️

Getting Started

# npm
npm i nextstepjs motion
# pnpm
pnpm add nextstepjs motion
# yarn
yarn add nextstepjs motion
# bun
bun add nextstepjs motion

Navigation Adapters (v2.0+)

NextStep 2.0 introduces a framework-agnostic routing system through navigation adapters. Each adapter is packaged separately to minimize bundle size - only the adapter you import will be included in your bundle.

Important: Make sure to import the adapter you need in your app in order to access full functionality. Without an adapter, navigation features like nextRoute and prevRoute may not work properly.

Built-in Adapters

Next.js

NextStep uses Next.js adapter as default, therefore you don't need to import it.

// app/layout.tsx or pages/_app.tsximport{NextStep,NextStepProvider}from'nextstepjs';exportdefaultfunctionLayout({ children }){return(<NextStepProvider><NextStepsteps={steps}>{children}</NextStep></NextStepProvider>);}
React Router as a Framework
//app/root.tsximport{NextStepProvider,NextStepReact,typeTour}from'nextstepjs';import{useReactRouterAdapter}from'nextstepjs/adapters/react-router';exportdefaultfunctionApp(){return(<NextStepProvider><NextStepReactnavigationAdapter={useReactRouterAdapter}steps={steps}><Outlet/></NextStepReact></NextStepProvider>);}
Remix
// root.tsximport{NextStepProvider,NextStepReact}from'nextstepjs';import{useRemixAdapter}from'nextstepjs/adapters/remix';exportdefaultfunctionApp(){return(<NextStepProvider><NextStepReactnavigationAdapter={useRemixAdapter}steps={steps}><Outlet/></NextStepReact></NextStepProvider>);}
Important Configuration for Vite (React Router or Remix)

If you're using Vite with React Router or Remix, add the following configuration to your vite.config.ts:

exportdefaultdefineConfig({ssr: {noExternal: ['nextstepjs','motion'],},});

Vite also requires next/navigation to be mocked in order to work properly.

  1. Create a mock file for Next, such as next-navigation.ts, and place it in /src/mocks
// Mock for Next.js navigation to prevent build errors with nextstepjs// This file is used to mock Next.js imports when using nextstepjs in a Vite appexportconstuseRouter=()=>{return{push: ()=>{},replace: ()=>{},prefetch: ()=>{},back: ()=>{},forward: ()=>{},refresh: ()=>{},};};exportconstusePathname=()=>{return'';};exportconstuseSearchParams=()=>{returnnewURLSearchParams();};exportconstuseParams=()=>{return{};};
  1. Update vite.config.mts to use the proper alias for Next.js navigation imports
importpathfrom'node:path';import{defineConfig}from'vite';importreactfrom'@vitejs/plugin-react';exportdefaultdefineConfig({plugins: [react()],resolve: {alias: [// Mock Next.js navigation imports that nextstepjs might try to access{find: 'next/navigation',replacement: path.join(process.cwd(),'src/mocks/next-navigation.ts'),},],},});
Custom Navigation Adapter

You can create your own navigation adapter for any routing solution by implementing the NavigationAdapter interface:

import{NextStepReact}from'nextstepjs';importtype{NavigationAdapter}from'nextstepjs';constuseCustomAdapter=(): NavigationAdapter=>{return{push: (path: string)=>{// Your navigation logic here// Example: history.push(path)},getCurrentPath: ()=>{// Your path retrieval logic here// Example: window.location.pathnamereturnwindow.location.pathname;},};};constApp=()=>{return(<NextStepReactnavigationAdapter={useCustomAdapter}steps={steps}>{children}</NextStepReact>);};

Troubleshooting

If you encounter an error related to module exports when using the Pages Router, it is likely due to a mismatch between ES modules (which use export statements) and CommonJS modules (which use module.exports). The nextstepjs package uses ES module syntax, but your Next.js project might be set up to use CommonJS.

To resolve this issue, ensure that your Next.js project is configured to support ES modules. You can do this by updating your next.config.js file to include the following configuration:

/** @type {import('next').NextConfig} */constnextConfig={reactStrictMode: true,experimental: {esmExternals: true,},transpilePackages: ['nextstepjs'],};exportdefaultnextConfig;

Custom Card

You can create a custom card component for greater control over the design:

PropTypeDescription
stepObjectThe current Step object from your steps array, including content, title, etc.
currentStepnumberThe index of the current step in the steps array.
totalStepsnumberThe total number of steps in the onboarding process.
nextStepA function to advance to the next step in the onboarding process.
prevStepA function to go back to the previous step in the onboarding process.
arrowReturns an SVG object, the orientation is controlled by the steps side prop
skipTourA function to skip the tour
'use client';importtype{CardComponentProps}from'nextstepjs';exportconstCustomCard=({
step,
currentStep,
totalSteps,
nextStep,
prevStep,
skipTour,
arrow,}: CardComponentProps)=>{return(<div><h1>{step.icon}{step.title}</h1><h2>{currentStep} of {totalSteps}</h2><p>{step.content}</p><buttononClick={prevStep}>Previous</button><buttononClick={nextStep}>Next</button><buttononClick={skipTour}>Skip</button>{arrow}</div>);};

Custom Arrow

By default NextStep renders a small SVG caret pointing from the card to the highlighted element. You can recolor/resize it with arrowStyle, or replace it entirely with arrowComponent. Both are provider-level props and fully optional (omit them for the default arrow).

import{NextStep}from'nextstepjs';importtype{ArrowComponentProps}from'nextstepjs';// Tweak the built-in caret:<NextStepsteps={steps}arrowStyle={{color: '#6d28d9'}}>{children}</NextStep>;// Or fully replace it. Spread the provided `style` so it stays anchored to the card,// and use `side` (the resolved placement, after any cut-off adjustment) to orient it:constMyArrow=({ side, style }: ArrowComponentProps)=>(<divstyle={{ ...style}}data-side={side}></div>);<NextStepsteps={steps}arrowComponent={MyArrow}>{children}</NextStep>;

Tours Array

NextStep supports multiple "tours", allowing you to create multiple product tours:

import{Tour}from'nextstepjs';conststeps: Tour[]=[{tour: 'firstTour',steps: [// Step objects],},{tour: 'secondTour',steps: [// Step objects],},];

Step Object

PropTypeDescription
iconReact.ReactNode, string, nullOptional. An icon or element to display alongside the step title (used by the default card).
titlestringThe title of your step
contentReact.ReactNodeThe main content or body of the step.
selectorstringOptional. A string used to target an id that this step refers to. If not provided, card will be displayed in the center top of the document body.
side"top", "bottom", "left", "right" (+ corner variants, e.g. "top-left")Optional. Determines where the tooltip should appear relative to the selector.
showControlsbooleanOptional. Determines whether control buttons (next, prev) should be shown if using the default card. Ignored when a custom cardComponent is provided.
showSkipbooleanOptional. Determines whether skip button should be shown if using the default card. Ignored when a custom cardComponent is provided.
blockKeyboardControlbooleanOptional. Determines whether keyboard control should be blocked
pointerPaddingnumberOptional. The padding around the pointer (keyhole) highlighting the target element.
pointerRadiusnumberOptional. The border-radius of the pointer (keyhole) highlighting the target element.
cardOffsetnumberOptional. Gap in pixels between the card and the spotlight highlight; the caret scales with it too (default: 25).
scrollOffsetnumberOptional. Extra clearance in pixels kept above/below the target when it is scrolled into view — useful when a fixed/sticky header would cover it (default: 0).
selectorRetryAttemptsnumberOptional. Extra attempts to find selector when it is missing on the first lookup (for asynchronously rendered targets). 0 keeps the single-lookup behavior (default: 0).
selectorRetryDelaynumberOptional. Delay in milliseconds between selector retry attempts (default: 200).
disableInteractionbooleanOptional. If true, prevents interaction with the highlighted element (default: false).
nextRoutestringOptional. The route to navigate to when moving to the next step.
prevRoutestringOptional. The route to navigate to when moving to the previous step.
viewportIDstringOptional. The id of the viewport element to use for positioning. If not provided, the document body will be used.

NoteNextStep handles card cutoff from screen sides. When the requested side does not have room, NextStep verifies the destination side actually has space before flipping, and otherwise falls back to the side with the most room — so the card stays on-screen instead of swapping into another cramped edge.

Target Anything

Target anything in your app using the element's id attribute.

<divid="nextstep-step1">Onboard Step</div>

Routing During a Tour

NextStep allows you to navigate between different routes during a tour using the nextRoute and prevRoute properties in the step object. These properties enable seamless transitions between different pages or sections of your application.

  • nextRoute: Specifies the route to navigate to when the "Next" button is clicked.
  • prevRoute: Specifies the route to navigate to when the "Previous" button is clicked.

When nextRoute or prevRoute is provided, NextStep will use Next.js's next/navigation to navigate to the specified route.

Using NextStepViewport and viewportID

When a selector is in a scrollable area, it is best to wrap the content of the scrollable area with NextStepViewport. This component takes children and an id as prop. By providing the viewportID to the step, NextStep will target this element within the viewport. This ensures that the step is anchored to the element even if the container is scrollable.

Here's an example of how to use NextStepViewport:

<divclassName="relative overflow-auto h-64"><NextStepViewportid="scrollable-viewport">{children}</NextStepViewport></div>

Example steps

[{tour: 'firsttour',steps: [{icon: <>👋</>,title: 'Tour 1, Step 1',content: <>First tour, first step</>,selector: '#tour1-step1',side: 'top',showControls: true,showSkip: true,pointerPadding: 10,pointerRadius: 10,nextRoute: '/foo',prevRoute: '/bar',},{icon: <>🎉</>,title: 'Tour 1, Step 2',content: <>First tour, second step</>,selector: '#tour1-step2',side: 'top',showControls: true,showSkip: true,pointerPadding: 10,pointerRadius: 10,viewportID: 'scrollable-viewport',},],},{tour: 'secondtour',steps: [{icon: <>🚀</>,title: 'Second tour, Step 1',content: <>Second tour, first step!</>,selector: '#nextstep-step1',side: 'top',showControls: true,showSkip: true,pointerPadding: 10,pointerRadius: 10,nextRoute: '/foo',prevRoute: '/bar',},],},];

NextStep & NextStepReact Props

PropertyTypeDescription
childrenReact.ReactNodeYour website or application content
stepsArray[]Array of Tour objects defining each step of the onboarding
navigationAdapterNavigationAdapterOptional. Router adapter for navigation (defaults to Next.js on NextStep and window adapter on NextStepReact)
showNextStepbooleanControls visibility of the onboarding overlay
shadowRgbstringRGB values for the shadow color surrounding the target area
shadowOpacitystringOpacity value for the shadow surrounding the target area
cardComponentReact.ComponentTypeCustom card component to replace the default one
cardTransitionTransitionMotion transition object for step transitions
onStart(tourName: string | null) => voidCallback function triggered when the tour starts
onStepChange(step: number, tourName: string | null) => voidCallback function triggered when the step changes
onComplete(tourName: string | null) => voidCallback function triggered when the tour completes
onSkip(step: number, tourName: string | null) => voidCallback function triggered when the user skips the tour
clickThroughOverlaybooleanOptional. If true, overlay background is clickable, default is false
disableConsoleLogsbooleanOptional. If true, console logs are disabled, default is false
scrollToTopbooleanOptional. If true, the page will scroll to the top when the tour ends, default is true
noInViewScrollbooleanOptional. If true, the page will not scroll to the target element when it is in view, default is false
overlayZIndexnumberOptional. Base z-index for overlay elements, useful for compatibility with UI libraries like MUI (default: 999)
arrowComponentReact.ComponentType<ArrowComponentProps>Optional. Render a fully custom arrow/caret. Receives the resolved side and the computed positioning style. When omitted, the built-in SVG arrow is used.
arrowStyleReact.CSSPropertiesOptional. Styles merged into the built-in arrow SVG (ignored when arrowComponent is set). Handy for tweaking the caret color or size.

Note When a custom cardComponent is provided, the per-step showControls / showSkip options only affect the built-in card, so they are ignored (and TypeScript omits them from the step type). Your custom card renders its own controls.

<NextStepsteps={steps}showNextStep={true}shadowRgb="55,48,163"shadowOpacity="0.8"cardComponent={CustomCard}cardTransition={{duration: 0.5,type: 'spring'}}onStepChange={(step,tourName)=>console.log(`Step changed to ${step} in ${tourName}`)}onComplete={(tourName)=>console.log(`Tour completed: ${tourName}`)}onSkip={(step,tourName)=>console.log(`Tour skipped: ${step} in ${tourName}`)}clickThroughOverlay={false}overlayZIndex={1400}// Set higher for MUI compatibility (MUI dialogs use 1300)>{children}</NextStep>

useNextStep Hook

useNextStep hook allows you to control the tour from anywhere in your app.

import{useNextStep}from'nextstepjs';
....const{ startNextStep, closeNextStep }=useNextStep();constonClickHandler=(tourName: string)=>{startNextStep(tourName);};

Keyboard Navigation

NextStep supports keyboard navigation:

  • Right Arrow: Next step
  • Left Arrow: Previous step
  • Escape: Skip tour

Localization

NextStep is a lightweight library and does not come with localization support. However, you can easily switch between languages by supplying the steps array based on locale.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

This project is licensed under the MIT License.

Credits

  • Onborda for the inspiration and some code snippets.

About

Lightweight onboarding library for Next.js

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

NextStep

NextStep

NextStep is a lightweight onboarding library for Next.js / React applications. It utilizes motion for smooth animations and supports multiple React frameworks including Next.js, React Router, and Remix.

Some of the use cases:

  • Easier Onboarding: Guide new users with step-by-step tours
  • Engagement Boost: Make help docs interactive, so users learn by doing.
  • Better Error Handling: Skip generic toasters—show users exactly what to fix with tailored tours.
  • Event-Based Tours: Trigger custom tours after key actions to keep users coming back.

The library allows users to use custom cards (tooltips) for easier integration.

If you like the project, please leave a star! ⭐️⭐️⭐️⭐️⭐️

Getting Started

# npm
npm i nextstepjs motion
# pnpm
pnpm add nextstepjs motion
# yarn
yarn add nextstepjs motion
# bun
bun add nextstepjs motion

Navigation Adapters (v2.0+)

NextStep 2.0 introduces a framework-agnostic routing system through navigation adapters. Each adapter is packaged separately to minimize bundle size - only the adapter you import will be included in your bundle.

Important: Make sure to import the adapter you need in your app in order to access full functionality. Without an adapter, navigation features like nextRoute and prevRoute may not work properly.

Built-in Adapters

Next.js

NextStep uses Next.js adapter as default, therefore you don't need to import it.

// app/layout.tsx or pages/_app.tsximport{NextStep,NextStepProvider}from'nextstepjs';exportdefaultfunctionLayout({ children }){return(<NextStepProvider><NextStepsteps={steps}>{children}</NextStep></NextStepProvider>);}
React Router as a Framework
//app/root.tsximport{NextStepProvider,NextStepReact,typeTour}from'nextstepjs';import{useReactRouterAdapter}from'nextstepjs/adapters/react-router';exportdefaultfunctionApp(){return(<NextStepProvider><NextStepReactnavigationAdapter={useReactRouterAdapter}steps={steps}><Outlet/></NextStepReact></NextStepProvider>);}
Remix
// root.tsximport{NextStepProvider,NextStepReact}from'nextstepjs';import{useRemixAdapter}from'nextstepjs/adapters/remix';exportdefaultfunctionApp(){return(<NextStepProvider><NextStepReactnavigationAdapter={useRemixAdapter}steps={steps}><Outlet/></NextStepReact></NextStepProvider>);}
Important Configuration for Vite (React Router or Remix)

If you're using Vite with React Router or Remix, add the following configuration to your vite.config.ts:

exportdefaultdefineConfig({ssr: {noExternal: ['nextstepjs','motion'],},});

Vite also requires next/navigation to be mocked in order to work properly.

  1. Create a mock file for Next, such as next-navigation.ts, and place it in /src/mocks
// Mock for Next.js navigation to prevent build errors with nextstepjs// This file is used to mock Next.js imports when using nextstepjs in a Vite appexportconstuseRouter=()=>{return{push: ()=>{},replace: ()=>{},prefetch: ()=>{},back: ()=>{},forward: ()=>{},refresh: ()=>{},};};exportconstusePathname=()=>{return'';};exportconstuseSearchParams=()=>{returnnewURLSearchParams();};exportconstuseParams=()=>{return{};};
  1. Update vite.config.mts to use the proper alias for Next.js navigation imports
importpathfrom'node:path';import{defineConfig}from'vite';importreactfrom'@vitejs/plugin-react';exportdefaultdefineConfig({plugins: [react()],resolve: {alias: [// Mock Next.js navigation imports that nextstepjs might try to access{find: 'next/navigation',replacement: path.join(process.cwd(),'src/mocks/next-navigation.ts'),},],},});
Custom Navigation Adapter

You can create your own navigation adapter for any routing solution by implementing the NavigationAdapter interface:

import{NextStepReact}from'nextstepjs';importtype{NavigationAdapter}from'nextstepjs';constuseCustomAdapter=(): NavigationAdapter=>{return{push: (path: string)=>{// Your navigation logic here// Example: history.push(path)},getCurrentPath: ()=>{// Your path retrieval logic here// Example: window.location.pathnamereturnwindow.location.pathname;},};};constApp=()=>{return(<NextStepReactnavigationAdapter={useCustomAdapter}steps={steps}>{children}</NextStepReact>);};

Troubleshooting

If you encounter an error related to module exports when using the Pages Router, it is likely due to a mismatch between ES modules (which use export statements) and CommonJS modules (which use module.exports). The nextstepjs package uses ES module syntax, but your Next.js project might be set up to use CommonJS.

To resolve this issue, ensure that your Next.js project is configured to support ES modules. You can do this by updating your next.config.js file to include the following configuration:

/** @type {import('next').NextConfig} */constnextConfig={reactStrictMode: true,experimental: {esmExternals: true,},transpilePackages: ['nextstepjs'],};exportdefaultnextConfig;

Custom Card

You can create a custom card component for greater control over the design:

PropTypeDescription
stepObjectThe current Step object from your steps array, including content, title, etc.
currentStepnumberThe index of the current step in the steps array.
totalStepsnumberThe total number of steps in the onboarding process.
nextStepA function to advance to the next step in the onboarding process.
prevStepA function to go back to the previous step in the onboarding process.
arrowReturns an SVG object, the orientation is controlled by the steps side prop
skipTourA function to skip the tour
'use client';importtype{CardComponentProps}from'nextstepjs';exportconstCustomCard=({
step,
currentStep,
totalSteps,
nextStep,
prevStep,
skipTour,
arrow,}: CardComponentProps)=>{return(<div><h1>{step.icon}{step.title}</h1><h2>{currentStep} of {totalSteps}</h2><p>{step.content}</p><buttononClick={prevStep}>Previous</button><buttononClick={nextStep}>Next</button><buttononClick={skipTour}>Skip</button>{arrow}</div>);};

Custom Arrow

By default NextStep renders a small SVG caret pointing from the card to the highlighted element. You can recolor/resize it with arrowStyle, or replace it entirely with arrowComponent. Both are provider-level props and fully optional (omit them for the default arrow).

import{NextStep}from'nextstepjs';importtype{ArrowComponentProps}from'nextstepjs';// Tweak the built-in caret:<NextStepsteps={steps}arrowStyle={{color: '#6d28d9'}}>{children}</NextStep>;// Or fully replace it. Spread the provided `style` so it stays anchored to the card,// and use `side` (the resolved placement, after any cut-off adjustment) to orient it:constMyArrow=({ side, style }: ArrowComponentProps)=>(<divstyle={{ ...style}}data-side={side}></div>);<NextStepsteps={steps}arrowComponent={MyArrow}>{children}</NextStep>;

Tours Array

NextStep supports multiple "tours", allowing you to create multiple product tours:

import{Tour}from'nextstepjs';conststeps: Tour[]=[{tour: 'firstTour',steps: [// Step objects],},{tour: 'secondTour',steps: [// Step objects],},];

Step Object

PropTypeDescription
iconReact.ReactNode, string, nullOptional. An icon or element to display alongside the step title (used by the default card).
titlestringThe title of your step
contentReact.ReactNodeThe main content or body of the step.
selectorstringOptional. A string used to target an id that this step refers to. If not provided, card will be displayed in the center top of the document body.
side"top", "bottom", "left", "right" (+ corner variants, e.g. "top-left")Optional. Determines where the tooltip should appear relative to the selector.
showControlsbooleanOptional. Determines whether control buttons (next, prev) should be shown if using the default card. Ignored when a custom cardComponent is provided.
showSkipbooleanOptional. Determines whether skip button should be shown if using the default card. Ignored when a custom cardComponent is provided.
blockKeyboardControlbooleanOptional. Determines whether keyboard control should be blocked
pointerPaddingnumberOptional. The padding around the pointer (keyhole) highlighting the target element.
pointerRadiusnumberOptional. The border-radius of the pointer (keyhole) highlighting the target element.
cardOffsetnumberOptional. Gap in pixels between the card and the spotlight highlight; the caret scales with it too (default: 25).
scrollOffsetnumberOptional. Extra clearance in pixels kept above/below the target when it is scrolled into view — useful when a fixed/sticky header would cover it (default: 0).
selectorRetryAttemptsnumberOptional. Extra attempts to find selector when it is missing on the first lookup (for asynchronously rendered targets). 0 keeps the single-lookup behavior (default: 0).
selectorRetryDelaynumberOptional. Delay in milliseconds between selector retry attempts (default: 200).
disableInteractionbooleanOptional. If true, prevents interaction with the highlighted element (default: false).
nextRoutestringOptional. The route to navigate to when moving to the next step.
prevRoutestringOptional. The route to navigate to when moving to the previous step.
viewportIDstringOptional. The id of the viewport element to use for positioning. If not provided, the document body will be used.

NoteNextStep handles card cutoff from screen sides. When the requested side does not have room, NextStep verifies the destination side actually has space before flipping, and otherwise falls back to the side with the most room — so the card stays on-screen instead of swapping into another cramped edge.

Target Anything

Target anything in your app using the element's id attribute.

<divid="nextstep-step1">Onboard Step</div>

Routing During a Tour

NextStep allows you to navigate between different routes during a tour using the nextRoute and prevRoute properties in the step object. These properties enable seamless transitions between different pages or sections of your application.

  • nextRoute: Specifies the route to navigate to when the "Next" button is clicked.
  • prevRoute: Specifies the route to navigate to when the "Previous" button is clicked.

When nextRoute or prevRoute is provided, NextStep will use Next.js's next/navigation to navigate to the specified route.

Using NextStepViewport and viewportID

When a selector is in a scrollable area, it is best to wrap the content of the scrollable area with NextStepViewport. This component takes children and an id as prop. By providing the viewportID to the step, NextStep will target this element within the viewport. This ensures that the step is anchored to the element even if the container is scrollable.

Here's an example of how to use NextStepViewport:

<divclassName="relative overflow-auto h-64"><NextStepViewportid="scrollable-viewport">{children}</NextStepViewport></div>

Example steps

[{tour: 'firsttour',steps: [{icon: <>👋</>,title: 'Tour 1, Step 1',content: <>First tour, first step</>,selector: '#tour1-step1',side: 'top',showControls: true,showSkip: true,pointerPadding: 10,pointerRadius: 10,nextRoute: '/foo',prevRoute: '/bar',},{icon: <>🎉</>,title: 'Tour 1, Step 2',content: <>First tour, second step</>,selector: '#tour1-step2',side: 'top',showControls: true,showSkip: true,pointerPadding: 10,pointerRadius: 10,viewportID: 'scrollable-viewport',},],},{tour: 'secondtour',steps: [{icon: <>🚀</>,title: 'Second tour, Step 1',content: <>Second tour, first step!</>,selector: '#nextstep-step1',side: 'top',showControls: true,showSkip: true,pointerPadding: 10,pointerRadius: 10,nextRoute: '/foo',prevRoute: '/bar',},],},];

NextStep & NextStepReact Props

PropertyTypeDescription
childrenReact.ReactNodeYour website or application content
stepsArray[]Array of Tour objects defining each step of the onboarding
navigationAdapterNavigationAdapterOptional. Router adapter for navigation (defaults to Next.js on NextStep and window adapter on NextStepReact)
showNextStepbooleanControls visibility of the onboarding overlay
shadowRgbstringRGB values for the shadow color surrounding the target area
shadowOpacitystringOpacity value for the shadow surrounding the target area
cardComponentReact.ComponentTypeCustom card component to replace the default one
cardTransitionTransitionMotion transition object for step transitions
onStart(tourName: string | null) => voidCallback function triggered when the tour starts
onStepChange(step: number, tourName: string | null) => voidCallback function triggered when the step changes
onComplete(tourName: string | null) => voidCallback function triggered when the tour completes
onSkip(step: number, tourName: string | null) => voidCallback function triggered when the user skips the tour
clickThroughOverlaybooleanOptional. If true, overlay background is clickable, default is false
disableConsoleLogsbooleanOptional. If true, console logs are disabled, default is false
scrollToTopbooleanOptional. If true, the page will scroll to the top when the tour ends, default is true
noInViewScrollbooleanOptional. If true, the page will not scroll to the target element when it is in view, default is false
overlayZIndexnumberOptional. Base z-index for overlay elements, useful for compatibility with UI libraries like MUI (default: 999)
arrowComponentReact.ComponentType<ArrowComponentProps>Optional. Render a fully custom arrow/caret. Receives the resolved side and the computed positioning style. When omitted, the built-in SVG arrow is used.
arrowStyleReact.CSSPropertiesOptional. Styles merged into the built-in arrow SVG (ignored when arrowComponent is set). Handy for tweaking the caret color or size.

Note When a custom cardComponent is provided, the per-step showControls / showSkip options only affect the built-in card, so they are ignored (and TypeScript omits them from the step type). Your custom card renders its own controls.

<NextStepsteps={steps}showNextStep={true}shadowRgb="55,48,163"shadowOpacity="0.8"cardComponent={CustomCard}cardTransition={{duration: 0.5,type: 'spring'}}onStepChange={(step,tourName)=>console.log(`Step changed to ${step} in ${tourName}`)}onComplete={(tourName)=>console.log(`Tour completed: ${tourName}`)}onSkip={(step,tourName)=>console.log(`Tour skipped: ${step} in ${tourName}`)}clickThroughOverlay={false}overlayZIndex={1400}// Set higher for MUI compatibility (MUI dialogs use 1300)>{children}</NextStep>

useNextStep Hook

useNextStep hook allows you to control the tour from anywhere in your app.

import{useNextStep}from'nextstepjs';
....const{ startNextStep, closeNextStep }=useNextStep();constonClickHandler=(tourName: string)=>{startNextStep(tourName);};

Keyboard Navigation

NextStep supports keyboard navigation:

  • Right Arrow: Next step
  • Left Arrow: Previous step
  • Escape: Skip tour

Localization

NextStep is a lightweight library and does not come with localization support. However, you can easily switch between languages by supplying the steps array based on locale.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

This project is licensed under the MIT License.

Credits

  • Onborda for the inspiration and some code snippets.

About

Lightweight onboarding library for Next.js

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

NextStep

NextStep

NextStep is a lightweight onboarding library for Next.js / React applications. It utilizes motion for smooth animations and supports multiple React frameworks including Next.js, React Router, and Remix.

Some of the use cases:

  • Easier Onboarding: Guide new users with step-by-step tours
  • Engagement Boost: Make help docs interactive, so users learn by doing.
  • Better Error Handling: Skip generic toasters—show users exactly what to fix with tailored tours.
  • Event-Based Tours: Trigger custom tours after key actions to keep users coming back.

The library allows users to use custom cards (tooltips) for easier integration.

If you like the project, please leave a star! ⭐️⭐️⭐️⭐️⭐️

Getting Started

# npm
npm i nextstepjs motion
# pnpm
pnpm add nextstepjs motion
# yarn
yarn add nextstepjs motion
# bun
bun add nextstepjs motion

Navigation Adapters (v2.0+)

NextStep 2.0 introduces a framework-agnostic routing system through navigation adapters. Each adapter is packaged separately to minimize bundle size - only the adapter you import will be included in your bundle.

Important: Make sure to import the adapter you need in your app in order to access full functionality. Without an adapter, navigation features like nextRoute and prevRoute may not work properly.

Built-in Adapters

Next.js

NextStep uses Next.js adapter as default, therefore you don't need to import it.

// app/layout.tsx or pages/_app.tsximport{NextStep,NextStepProvider}from'nextstepjs';exportdefaultfunctionLayout({ children }){return(<NextStepProvider><NextStepsteps={steps}>{children}</NextStep></NextStepProvider>);}
React Router as a Framework
//app/root.tsximport{NextStepProvider,NextStepReact,typeTour}from'nextstepjs';import{useReactRouterAdapter}from'nextstepjs/adapters/react-router';exportdefaultfunctionApp(){return(<NextStepProvider><NextStepReactnavigationAdapter={useReactRouterAdapter}steps={steps}><Outlet/></NextStepReact></NextStepProvider>);}
Remix
// root.tsximport{NextStepProvider,NextStepReact}from'nextstepjs';import{useRemixAdapter}from'nextstepjs/adapters/remix';exportdefaultfunctionApp(){return(<NextStepProvider><NextStepReactnavigationAdapter={useRemixAdapter}steps={steps}><Outlet/></NextStepReact></NextStepProvider>);}
Important Configuration for Vite (React Router or Remix)

If you're using Vite with React Router or Remix, add the following configuration to your vite.config.ts:

exportdefaultdefineConfig({ssr: {noExternal: ['nextstepjs','motion'],},});

Vite also requires next/navigation to be mocked in order to work properly.

  1. Create a mock file for Next, such as next-navigation.ts, and place it in /src/mocks
// Mock for Next.js navigation to prevent build errors with nextstepjs// This file is used to mock Next.js imports when using nextstepjs in a Vite appexportconstuseRouter=()=>{return{push: ()=>{},replace: ()=>{},prefetch: ()=>{},back: ()=>{},forward: ()=>{},refresh: ()=>{},};};exportconstusePathname=()=>{return'';};exportconstuseSearchParams=()=>{returnnewURLSearchParams();};exportconstuseParams=()=>{return{};};
  1. Update vite.config.mts to use the proper alias for Next.js navigation imports
importpathfrom'node:path';import{defineConfig}from'vite';importreactfrom'@vitejs/plugin-react';exportdefaultdefineConfig({plugins: [react()],resolve: {alias: [// Mock Next.js navigation imports that nextstepjs might try to access{find: 'next/navigation',replacement: path.join(process.cwd(),'src/mocks/next-navigation.ts'),},],},});
Custom Navigation Adapter

You can create your own navigation adapter for any routing solution by implementing the NavigationAdapter interface:

import{NextStepReact}from'nextstepjs';importtype{NavigationAdapter}from'nextstepjs';constuseCustomAdapter=(): NavigationAdapter=>{return{push: (path: string)=>{// Your navigation logic here// Example: history.push(path)},getCurrentPath: ()=>{// Your path retrieval logic here// Example: window.location.pathnamereturnwindow.location.pathname;},};};constApp=()=>{return(<NextStepReactnavigationAdapter={useCustomAdapter}steps={steps}>{children}</NextStepReact>);};

Troubleshooting

If you encounter an error related to module exports when using the Pages Router, it is likely due to a mismatch between ES modules (which use export statements) and CommonJS modules (which use module.exports). The nextstepjs package uses ES module syntax, but your Next.js project might be set up to use CommonJS.

To resolve this issue, ensure that your Next.js project is configured to support ES modules. You can do this by updating your next.config.js file to include the following configuration:

/** @type {import('next').NextConfig} */constnextConfig={reactStrictMode: true,experimental: {esmExternals: true,},transpilePackages: ['nextstepjs'],};exportdefaultnextConfig;

Custom Card

You can create a custom card component for greater control over the design:

PropTypeDescription
stepObjectThe current Step object from your steps array, including content, title, etc.
currentStepnumberThe index of the current step in the steps array.
totalStepsnumberThe total number of steps in the onboarding process.
nextStepA function to advance to the next step in the onboarding process.
prevStepA function to go back to the previous step in the onboarding process.
arrowReturns an SVG object, the orientation is controlled by the steps side prop
skipTourA function to skip the tour
'use client';importtype{CardComponentProps}from'nextstepjs';exportconstCustomCard=({
step,
currentStep,
totalSteps,
nextStep,
prevStep,
skipTour,
arrow,}: CardComponentProps)=>{return(<div><h1>{step.icon}{step.title}</h1><h2>{currentStep} of {totalSteps}</h2><p>{step.content}</p><buttononClick={prevStep}>Previous</button><buttononClick={nextStep}>Next</button><buttononClick={skipTour}>Skip</button>{arrow}</div>);};

Custom Arrow

By default NextStep renders a small SVG caret pointing from the card to the highlighted element. You can recolor/resize it with arrowStyle, or replace it entirely with arrowComponent. Both are provider-level props and fully optional (omit them for the default arrow).

import{NextStep}from'nextstepjs';importtype{ArrowComponentProps}from'nextstepjs';// Tweak the built-in caret:<NextStepsteps={steps}arrowStyle={{color: '#6d28d9'}}>{children}</NextStep>;// Or fully replace it. Spread the provided `style` so it stays anchored to the card,// and use `side` (the resolved placement, after any cut-off adjustment) to orient it:constMyArrow=({ side, style }: ArrowComponentProps)=>(<divstyle={{ ...style}}data-side={side}></div>);<NextStepsteps={steps}arrowComponent={MyArrow}>{children}</NextStep>;

Tours Array

NextStep supports multiple "tours", allowing you to create multiple product tours:

import{Tour}from'nextstepjs';conststeps: Tour[]=[{tour: 'firstTour',steps: [// Step objects],},{tour: 'secondTour',steps: [// Step objects],},];

Step Object

PropTypeDescription
iconReact.ReactNode, string, nullOptional. An icon or element to display alongside the step title (used by the default card).
titlestringThe title of your step
contentReact.ReactNodeThe main content or body of the step.
selectorstringOptional. A string used to target an id that this step refers to. If not provided, card will be displayed in the center top of the document body.
side"top", "bottom", "left", "right" (+ corner variants, e.g. "top-left")Optional. Determines where the tooltip should appear relative to the selector.
showControlsbooleanOptional. Determines whether control buttons (next, prev) should be shown if using the default card. Ignored when a custom cardComponent is provided.
showSkipbooleanOptional. Determines whether skip button should be shown if using the default card. Ignored when a custom cardComponent is provided.
blockKeyboardControlbooleanOptional. Determines whether keyboard control should be blocked
pointerPaddingnumberOptional. The padding around the pointer (keyhole) highlighting the target element.
pointerRadiusnumberOptional. The border-radius of the pointer (keyhole) highlighting the target element.
cardOffsetnumberOptional. Gap in pixels between the card and the spotlight highlight; the caret scales with it too (default: 25).
scrollOffsetnumberOptional. Extra clearance in pixels kept above/below the target when it is scrolled into view — useful when a fixed/sticky header would cover it (default: 0).
selectorRetryAttemptsnumberOptional. Extra attempts to find selector when it is missing on the first lookup (for asynchronously rendered targets). 0 keeps the single-lookup behavior (default: 0).
selectorRetryDelaynumberOptional. Delay in milliseconds between selector retry attempts (default: 200).
disableInteractionbooleanOptional. If true, prevents interaction with the highlighted element (default: false).
nextRoutestringOptional. The route to navigate to when moving to the next step.
prevRoutestringOptional. The route to navigate to when moving to the previous step.
viewportIDstringOptional. The id of the viewport element to use for positioning. If not provided, the document body will be used.

NoteNextStep handles card cutoff from screen sides. When the requested side does not have room, NextStep verifies the destination side actually has space before flipping, and otherwise falls back to the side with the most room — so the card stays on-screen instead of swapping into another cramped edge.

Target Anything

Target anything in your app using the element's id attribute.

<divid="nextstep-step1">Onboard Step</div>

Routing During a Tour

NextStep allows you to navigate between different routes during a tour using the nextRoute and prevRoute properties in the step object. These properties enable seamless transitions between different pages or sections of your application.

  • nextRoute: Specifies the route to navigate to when the "Next" button is clicked.
  • prevRoute: Specifies the route to navigate to when the "Previous" button is clicked.

When nextRoute or prevRoute is provided, NextStep will use Next.js's next/navigation to navigate to the specified route.

Using NextStepViewport and viewportID

When a selector is in a scrollable area, it is best to wrap the content of the scrollable area with NextStepViewport. This component takes children and an id as prop. By providing the viewportID to the step, NextStep will target this element within the viewport. This ensures that the step is anchored to the element even if the container is scrollable.

Here's an example of how to use NextStepViewport:

<divclassName="relative overflow-auto h-64"><NextStepViewportid="scrollable-viewport">{children}</NextStepViewport></div>

Example steps

[{tour: 'firsttour',steps: [{icon: <>👋</>,title: 'Tour 1, Step 1',content: <>First tour, first step</>,selector: '#tour1-step1',side: 'top',showControls: true,showSkip: true,pointerPadding: 10,pointerRadius: 10,nextRoute: '/foo',prevRoute: '/bar',},{icon: <>🎉</>,title: 'Tour 1, Step 2',content: <>First tour, second step</>,selector: '#tour1-step2',side: 'top',showControls: true,showSkip: true,pointerPadding: 10,pointerRadius: 10,viewportID: 'scrollable-viewport',},],},{tour: 'secondtour',steps: [{icon: <>🚀</>,title: 'Second tour, Step 1',content: <>Second tour, first step!</>,selector: '#nextstep-step1',side: 'top',showControls: true,showSkip: true,pointerPadding: 10,pointerRadius: 10,nextRoute: '/foo',prevRoute: '/bar',},],},];

NextStep & NextStepReact Props

PropertyTypeDescription
childrenReact.ReactNodeYour website or application content
stepsArray[]Array of Tour objects defining each step of the onboarding
navigationAdapterNavigationAdapterOptional. Router adapter for navigation (defaults to Next.js on NextStep and window adapter on NextStepReact)
showNextStepbooleanControls visibility of the onboarding overlay
shadowRgbstringRGB values for the shadow color surrounding the target area
shadowOpacitystringOpacity value for the shadow surrounding the target area
cardComponentReact.ComponentTypeCustom card component to replace the default one
cardTransitionTransitionMotion transition object for step transitions
onStart(tourName: string | null) => voidCallback function triggered when the tour starts
onStepChange(step: number, tourName: string | null) => voidCallback function triggered when the step changes
onComplete(tourName: string | null) => voidCallback function triggered when the tour completes
onSkip(step: number, tourName: string | null) => voidCallback function triggered when the user skips the tour
clickThroughOverlaybooleanOptional. If true, overlay background is clickable, default is false
disableConsoleLogsbooleanOptional. If true, console logs are disabled, default is false
scrollToTopbooleanOptional. If true, the page will scroll to the top when the tour ends, default is true
noInViewScrollbooleanOptional. If true, the page will not scroll to the target element when it is in view, default is false
overlayZIndexnumberOptional. Base z-index for overlay elements, useful for compatibility with UI libraries like MUI (default: 999)
arrowComponentReact.ComponentType<ArrowComponentProps>Optional. Render a fully custom arrow/caret. Receives the resolved side and the computed positioning style. When omitted, the built-in SVG arrow is used.
arrowStyleReact.CSSPropertiesOptional. Styles merged into the built-in arrow SVG (ignored when arrowComponent is set). Handy for tweaking the caret color or size.

Note When a custom cardComponent is provided, the per-step showControls / showSkip options only affect the built-in card, so they are ignored (and TypeScript omits them from the step type). Your custom card renders its own controls.

<NextStepsteps={steps}showNextStep={true}shadowRgb="55,48,163"shadowOpacity="0.8"cardComponent={CustomCard}cardTransition={{duration: 0.5,type: 'spring'}}onStepChange={(step,tourName)=>console.log(`Step changed to ${step} in ${tourName}`)}onComplete={(tourName)=>console.log(`Tour completed: ${tourName}`)}onSkip={(step,tourName)=>console.log(`Tour skipped: ${step} in ${tourName}`)}clickThroughOverlay={false}overlayZIndex={1400}// Set higher for MUI compatibility (MUI dialogs use 1300)>{children}</NextStep>

useNextStep Hook

useNextStep hook allows you to control the tour from anywhere in your app.

import{useNextStep}from'nextstepjs';
....const{ startNextStep, closeNextStep }=useNextStep();constonClickHandler=(tourName: string)=>{startNextStep(tourName);};

Keyboard Navigation

NextStep supports keyboard navigation:

  • Right Arrow: Next step
  • Left Arrow: Previous step
  • Escape: Skip tour

Localization

NextStep is a lightweight library and does not come with localization support. However, you can easily switch between languages by supplying the steps array based on locale.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

This project is licensed under the MIT License.

Credits

  • Onborda for the inspiration and some code snippets.

About

Lightweight onboarding library for Next.js

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

NextStep

NextStep

NextStep is a lightweight onboarding library for Next.js / React applications. It utilizes motion for smooth animations and supports multiple React frameworks including Next.js, React Router, and Remix.

Some of the use cases:

  • Easier Onboarding: Guide new users with step-by-step tours
  • Engagement Boost: Make help docs interactive, so users learn by doing.
  • Better Error Handling: Skip generic toasters—show users exactly what to fix with tailored tours.
  • Event-Based Tours: Trigger custom tours after key actions to keep users coming back.

The library allows users to use custom cards (tooltips) for easier integration.

If you like the project, please leave a star! ⭐️⭐️⭐️⭐️⭐️

Getting Started

# npm
npm i nextstepjs motion
# pnpm
pnpm add nextstepjs motion
# yarn
yarn add nextstepjs motion
# bun
bun add nextstepjs motion

Navigation Adapters (v2.0+)

NextStep 2.0 introduces a framework-agnostic routing system through navigation adapters. Each adapter is packaged separately to minimize bundle size - only the adapter you import will be included in your bundle.

Important: Make sure to import the adapter you need in your app in order to access full functionality. Without an adapter, navigation features like nextRoute and prevRoute may not work properly.

Built-in Adapters

Next.js

NextStep uses Next.js adapter as default, therefore you don't need to import it.

// app/layout.tsx or pages/_app.tsximport{NextStep,NextStepProvider}from'nextstepjs';exportdefaultfunctionLayout({ children }){return(<NextStepProvider><NextStepsteps={steps}>{children}</NextStep></NextStepProvider>);}
React Router as a Framework
//app/root.tsximport{NextStepProvider,NextStepReact,typeTour}from'nextstepjs';import{useReactRouterAdapter}from'nextstepjs/adapters/react-router';exportdefaultfunctionApp(){return(<NextStepProvider><NextStepReactnavigationAdapter={useReactRouterAdapter}steps={steps}><Outlet/></NextStepReact></NextStepProvider>);}
Remix
// root.tsximport{NextStepProvider,NextStepReact}from'nextstepjs';import{useRemixAdapter}from'nextstepjs/adapters/remix';exportdefaultfunctionApp(){return(<NextStepProvider><NextStepReactnavigationAdapter={useRemixAdapter}steps={steps}><Outlet/></NextStepReact></NextStepProvider>);}
Important Configuration for Vite (React Router or Remix)

If you're using Vite with React Router or Remix, add the following configuration to your vite.config.ts:

exportdefaultdefineConfig({ssr: {noExternal: ['nextstepjs','motion'],},});

Vite also requires next/navigation to be mocked in order to work properly.

  1. Create a mock file for Next, such as next-navigation.ts, and place it in /src/mocks
// Mock for Next.js navigation to prevent build errors with nextstepjs// This file is used to mock Next.js imports when using nextstepjs in a Vite appexportconstuseRouter=()=>{return{push: ()=>{},replace: ()=>{},prefetch: ()=>{},back: ()=>{},forward: ()=>{},refresh: ()=>{},};};exportconstusePathname=()=>{return'';};exportconstuseSearchParams=()=>{returnnewURLSearchParams();};exportconstuseParams=()=>{return{};};
  1. Update vite.config.mts to use the proper alias for Next.js navigation imports
importpathfrom'node:path';import{defineConfig}from'vite';importreactfrom'@vitejs/plugin-react';exportdefaultdefineConfig({plugins: [react()],resolve: {alias: [// Mock Next.js navigation imports that nextstepjs might try to access{find: 'next/navigation',replacement: path.join(process.cwd(),'src/mocks/next-navigation.ts'),},],},});
Custom Navigation Adapter

You can create your own navigation adapter for any routing solution by implementing the NavigationAdapter interface:

import{NextStepReact}from'nextstepjs';importtype{NavigationAdapter}from'nextstepjs';constuseCustomAdapter=(): NavigationAdapter=>{return{push: (path: string)=>{// Your navigation logic here// Example: history.push(path)},getCurrentPath: ()=>{// Your path retrieval logic here// Example: window.location.pathnamereturnwindow.location.pathname;},};};constApp=()=>{return(<NextStepReactnavigationAdapter={useCustomAdapter}steps={steps}>{children}</NextStepReact>);};

Troubleshooting

If you encounter an error related to module exports when using the Pages Router, it is likely due to a mismatch between ES modules (which use export statements) and CommonJS modules (which use module.exports). The nextstepjs package uses ES module syntax, but your Next.js project might be set up to use CommonJS.

To resolve this issue, ensure that your Next.js project is configured to support ES modules. You can do this by updating your next.config.js file to include the following configuration:

/** @type {import('next').NextConfig} */constnextConfig={reactStrictMode: true,experimental: {esmExternals: true,},transpilePackages: ['nextstepjs'],};exportdefaultnextConfig;

Custom Card

You can create a custom card component for greater control over the design:

PropTypeDescription
stepObjectThe current Step object from your steps array, including content, title, etc.
currentStepnumberThe index of the current step in the steps array.
totalStepsnumberThe total number of steps in the onboarding process.
nextStepA function to advance to the next step in the onboarding process.
prevStepA function to go back to the previous step in the onboarding process.
arrowReturns an SVG object, the orientation is controlled by the steps side prop
skipTourA function to skip the tour
'use client';importtype{CardComponentProps}from'nextstepjs';exportconstCustomCard=({
step,
currentStep,
totalSteps,
nextStep,
prevStep,
skipTour,
arrow,}: CardComponentProps)=>{return(<div><h1>{step.icon}{step.title}</h1><h2>{currentStep} of {totalSteps}</h2><p>{step.content}</p><buttononClick={prevStep}>Previous</button><buttononClick={nextStep}>Next</button><buttononClick={skipTour}>Skip</button>{arrow}</div>);};

Custom Arrow

By default NextStep renders a small SVG caret pointing from the card to the highlighted element. You can recolor/resize it with arrowStyle, or replace it entirely with arrowComponent. Both are provider-level props and fully optional (omit them for the default arrow).

import{NextStep}from'nextstepjs';importtype{ArrowComponentProps}from'nextstepjs';// Tweak the built-in caret:<NextStepsteps={steps}arrowStyle={{color: '#6d28d9'}}>{children}</NextStep>;// Or fully replace it. Spread the provided `style` so it stays anchored to the card,// and use `side` (the resolved placement, after any cut-off adjustment) to orient it:constMyArrow=({ side, style }: ArrowComponentProps)=>(<divstyle={{ ...style}}data-side={side}></div>);<NextStepsteps={steps}arrowComponent={MyArrow}>{children}</NextStep>;

Tours Array

NextStep supports multiple "tours", allowing you to create multiple product tours:

import{Tour}from'nextstepjs';conststeps: Tour[]=[{tour: 'firstTour',steps: [// Step objects],},{tour: 'secondTour',steps: [// Step objects],},];

Step Object

PropTypeDescription
iconReact.ReactNode, string, nullOptional. An icon or element to display alongside the step title (used by the default card).
titlestringThe title of your step
contentReact.ReactNodeThe main content or body of the step.
selectorstringOptional. A string used to target an id that this step refers to. If not provided, card will be displayed in the center top of the document body.
side"top", "bottom", "left", "right" (+ corner variants, e.g. "top-left")Optional. Determines where the tooltip should appear relative to the selector.
showControlsbooleanOptional. Determines whether control buttons (next, prev) should be shown if using the default card. Ignored when a custom cardComponent is provided.
showSkipbooleanOptional. Determines whether skip button should be shown if using the default card. Ignored when a custom cardComponent is provided.
blockKeyboardControlbooleanOptional. Determines whether keyboard control should be blocked
pointerPaddingnumberOptional. The padding around the pointer (keyhole) highlighting the target element.
pointerRadiusnumberOptional. The border-radius of the pointer (keyhole) highlighting the target element.
cardOffsetnumberOptional. Gap in pixels between the card and the spotlight highlight; the caret scales with it too (default: 25).
scrollOffsetnumberOptional. Extra clearance in pixels kept above/below the target when it is scrolled into view — useful when a fixed/sticky header would cover it (default: 0).
selectorRetryAttemptsnumberOptional. Extra attempts to find selector when it is missing on the first lookup (for asynchronously rendered targets). 0 keeps the single-lookup behavior (default: 0).
selectorRetryDelaynumberOptional. Delay in milliseconds between selector retry attempts (default: 200).
disableInteractionbooleanOptional. If true, prevents interaction with the highlighted element (default: false).
nextRoutestringOptional. The route to navigate to when moving to the next step.
prevRoutestringOptional. The route to navigate to when moving to the previous step.
viewportIDstringOptional. The id of the viewport element to use for positioning. If not provided, the document body will be used.

NoteNextStep handles card cutoff from screen sides. When the requested side does not have room, NextStep verifies the destination side actually has space before flipping, and otherwise falls back to the side with the most room — so the card stays on-screen instead of swapping into another cramped edge.

Target Anything

Target anything in your app using the element's id attribute.

<divid="nextstep-step1">Onboard Step</div>

Routing During a Tour

NextStep allows you to navigate between different routes during a tour using the nextRoute and prevRoute properties in the step object. These properties enable seamless transitions between different pages or sections of your application.

  • nextRoute: Specifies the route to navigate to when the "Next" button is clicked.
  • prevRoute: Specifies the route to navigate to when the "Previous" button is clicked.

When nextRoute or prevRoute is provided, NextStep will use Next.js's next/navigation to navigate to the specified route.

Using NextStepViewport and viewportID

When a selector is in a scrollable area, it is best to wrap the content of the scrollable area with NextStepViewport. This component takes children and an id as prop. By providing the viewportID to the step, NextStep will target this element within the viewport. This ensures that the step is anchored to the element even if the container is scrollable.

Here's an example of how to use NextStepViewport:

<divclassName="relative overflow-auto h-64"><NextStepViewportid="scrollable-viewport">{children}</NextStepViewport></div>

Example steps

[{tour: 'firsttour',steps: [{icon: <>👋</>,title: 'Tour 1, Step 1',content: <>First tour, first step</>,selector: '#tour1-step1',side: 'top',showControls: true,showSkip: true,pointerPadding: 10,pointerRadius: 10,nextRoute: '/foo',prevRoute: '/bar',},{icon: <>🎉</>,title: 'Tour 1, Step 2',content: <>First tour, second step</>,selector: '#tour1-step2',side: 'top',showControls: true,showSkip: true,pointerPadding: 10,pointerRadius: 10,viewportID: 'scrollable-viewport',},],},{tour: 'secondtour',steps: [{icon: <>🚀</>,title: 'Second tour, Step 1',content: <>Second tour, first step!</>,selector: '#nextstep-step1',side: 'top',showControls: true,showSkip: true,pointerPadding: 10,pointerRadius: 10,nextRoute: '/foo',prevRoute: '/bar',},],},];

NextStep & NextStepReact Props

PropertyTypeDescription
childrenReact.ReactNodeYour website or application content
stepsArray[]Array of Tour objects defining each step of the onboarding
navigationAdapterNavigationAdapterOptional. Router adapter for navigation (defaults to Next.js on NextStep and window adapter on NextStepReact)
showNextStepbooleanControls visibility of the onboarding overlay
shadowRgbstringRGB values for the shadow color surrounding the target area
shadowOpacitystringOpacity value for the shadow surrounding the target area
cardComponentReact.ComponentTypeCustom card component to replace the default one
cardTransitionTransitionMotion transition object for step transitions
onStart(tourName: string | null) => voidCallback function triggered when the tour starts
onStepChange(step: number, tourName: string | null) => voidCallback function triggered when the step changes
onComplete(tourName: string | null) => voidCallback function triggered when the tour completes
onSkip(step: number, tourName: string | null) => voidCallback function triggered when the user skips the tour
clickThroughOverlaybooleanOptional. If true, overlay background is clickable, default is false
disableConsoleLogsbooleanOptional. If true, console logs are disabled, default is false
scrollToTopbooleanOptional. If true, the page will scroll to the top when the tour ends, default is true
noInViewScrollbooleanOptional. If true, the page will not scroll to the target element when it is in view, default is false
overlayZIndexnumberOptional. Base z-index for overlay elements, useful for compatibility with UI libraries like MUI (default: 999)
arrowComponentReact.ComponentType<ArrowComponentProps>Optional. Render a fully custom arrow/caret. Receives the resolved side and the computed positioning style. When omitted, the built-in SVG arrow is used.
arrowStyleReact.CSSPropertiesOptional. Styles merged into the built-in arrow SVG (ignored when arrowComponent is set). Handy for tweaking the caret color or size.

Note When a custom cardComponent is provided, the per-step showControls / showSkip options only affect the built-in card, so they are ignored (and TypeScript omits them from the step type). Your custom card renders its own controls.

<NextStepsteps={steps}showNextStep={true}shadowRgb="55,48,163"shadowOpacity="0.8"cardComponent={CustomCard}cardTransition={{duration: 0.5,type: 'spring'}}onStepChange={(step,tourName)=>console.log(`Step changed to ${step} in ${tourName}`)}onComplete={(tourName)=>console.log(`Tour completed: ${tourName}`)}onSkip={(step,tourName)=>console.log(`Tour skipped: ${step} in ${tourName}`)}clickThroughOverlay={false}overlayZIndex={1400}// Set higher for MUI compatibility (MUI dialogs use 1300)>{children}</NextStep>

useNextStep Hook

useNextStep hook allows you to control the tour from anywhere in your app.

import{useNextStep}from'nextstepjs';
....const{ startNextStep, closeNextStep }=useNextStep();constonClickHandler=(tourName: string)=>{startNextStep(tourName);};

Keyboard Navigation

NextStep supports keyboard navigation:

  • Right Arrow: Next step
  • Left Arrow: Previous step
  • Escape: Skip tour

Localization

NextStep is a lightweight library and does not come with localization support. However, you can easily switch between languages by supplying the steps array based on locale.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

This project is licensed under the MIT License.

Credits

  • Onborda for the inspiration and some code snippets.

About

Lightweight onboarding library for Next.js

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

NextStep

NextStep

NextStep is a lightweight onboarding library for Next.js / React applications. It utilizes motion for smooth animations and supports multiple React frameworks including Next.js, React Router, and Remix.

Some of the use cases:

  • Easier Onboarding: Guide new users with step-by-step tours
  • Engagement Boost: Make help docs interactive, so users learn by doing.
  • Better Error Handling: Skip generic toasters—show users exactly what to fix with tailored tours.
  • Event-Based Tours: Trigger custom tours after key actions to keep users coming back.

The library allows users to use custom cards (tooltips) for easier integration.

If you like the project, please leave a star! ⭐️⭐️⭐️⭐️⭐️

Getting Started

# npm
npm i nextstepjs motion
# pnpm
pnpm add nextstepjs motion
# yarn
yarn add nextstepjs motion
# bun
bun add nextstepjs motion

Navigation Adapters (v2.0+)

NextStep 2.0 introduces a framework-agnostic routing system through navigation adapters. Each adapter is packaged separately to minimize bundle size - only the adapter you import will be included in your bundle.

Important: Make sure to import the adapter you need in your app in order to access full functionality. Without an adapter, navigation features like nextRoute and prevRoute may not work properly.

Built-in Adapters

Next.js

NextStep uses Next.js adapter as default, therefore you don't need to import it.

// app/layout.tsx or pages/_app.tsximport{NextStep,NextStepProvider}from'nextstepjs';exportdefaultfunctionLayout({ children }){return(<NextStepProvider><NextStepsteps={steps}>{children}</NextStep></NextStepProvider>);}
React Router as a Framework
//app/root.tsximport{NextStepProvider,NextStepReact,typeTour}from'nextstepjs';import{useReactRouterAdapter}from'nextstepjs/adapters/react-router';exportdefaultfunctionApp(){return(<NextStepProvider><NextStepReactnavigationAdapter={useReactRouterAdapter}steps={steps}><Outlet/></NextStepReact></NextStepProvider>);}
Remix
// root.tsximport{NextStepProvider,NextStepReact}from'nextstepjs';import{useRemixAdapter}from'nextstepjs/adapters/remix';exportdefaultfunctionApp(){return(<NextStepProvider><NextStepReactnavigationAdapter={useRemixAdapter}steps={steps}><Outlet/></NextStepReact></NextStepProvider>);}
Important Configuration for Vite (React Router or Remix)

If you're using Vite with React Router or Remix, add the following configuration to your vite.config.ts:

exportdefaultdefineConfig({ssr: {noExternal: ['nextstepjs','motion'],},});

Vite also requires next/navigation to be mocked in order to work properly.

  1. Create a mock file for Next, such as next-navigation.ts, and place it in /src/mocks
// Mock for Next.js navigation to prevent build errors with nextstepjs// This file is used to mock Next.js imports when using nextstepjs in a Vite appexportconstuseRouter=()=>{return{push: ()=>{},replace: ()=>{},prefetch: ()=>{},back: ()=>{},forward: ()=>{},refresh: ()=>{},};};exportconstusePathname=()=>{return'';};exportconstuseSearchParams=()=>{returnnewURLSearchParams();};exportconstuseParams=()=>{return{};};
  1. Update vite.config.mts to use the proper alias for Next.js navigation imports
importpathfrom'node:path';import{defineConfig}from'vite';importreactfrom'@vitejs/plugin-react';exportdefaultdefineConfig({plugins: [react()],resolve: {alias: [// Mock Next.js navigation imports that nextstepjs might try to access{find: 'next/navigation',replacement: path.join(process.cwd(),'src/mocks/next-navigation.ts'),},],},});
Custom Navigation Adapter

You can create your own navigation adapter for any routing solution by implementing the NavigationAdapter interface:

import{NextStepReact}from'nextstepjs';importtype{NavigationAdapter}from'nextstepjs';constuseCustomAdapter=(): NavigationAdapter=>{return{push: (path: string)=>{// Your navigation logic here// Example: history.push(path)},getCurrentPath: ()=>{// Your path retrieval logic here// Example: window.location.pathnamereturnwindow.location.pathname;},};};constApp=()=>{return(<NextStepReactnavigationAdapter={useCustomAdapter}steps={steps}>{children}</NextStepReact>);};

Troubleshooting

If you encounter an error related to module exports when using the Pages Router, it is likely due to a mismatch between ES modules (which use export statements) and CommonJS modules (which use module.exports). The nextstepjs package uses ES module syntax, but your Next.js project might be set up to use CommonJS.

To resolve this issue, ensure that your Next.js project is configured to support ES modules. You can do this by updating your next.config.js file to include the following configuration:

/** @type {import('next').NextConfig} */constnextConfig={reactStrictMode: true,experimental: {esmExternals: true,},transpilePackages: ['nextstepjs'],};exportdefaultnextConfig;

Custom Card

You can create a custom card component for greater control over the design:

PropTypeDescription
stepObjectThe current Step object from your steps array, including content, title, etc.
currentStepnumberThe index of the current step in the steps array.
totalStepsnumberThe total number of steps in the onboarding process.
nextStepA function to advance to the next step in the onboarding process.
prevStepA function to go back to the previous step in the onboarding process.
arrowReturns an SVG object, the orientation is controlled by the steps side prop
skipTourA function to skip the tour
'use client';importtype{CardComponentProps}from'nextstepjs';exportconstCustomCard=({
step,
currentStep,
totalSteps,
nextStep,
prevStep,
skipTour,
arrow,}: CardComponentProps)=>{return(<div><h1>{step.icon}{step.title}</h1><h2>{currentStep} of {totalSteps}</h2><p>{step.content}</p><buttononClick={prevStep}>Previous</button><buttononClick={nextStep}>Next</button><buttononClick={skipTour}>Skip</button>{arrow}</div>);};

Custom Arrow

By default NextStep renders a small SVG caret pointing from the card to the highlighted element. You can recolor/resize it with arrowStyle, or replace it entirely with arrowComponent. Both are provider-level props and fully optional (omit them for the default arrow).

import{NextStep}from'nextstepjs';importtype{ArrowComponentProps}from'nextstepjs';// Tweak the built-in caret:<NextStepsteps={steps}arrowStyle={{color: '#6d28d9'}}>{children}</NextStep>;// Or fully replace it. Spread the provided `style` so it stays anchored to the card,// and use `side` (the resolved placement, after any cut-off adjustment) to orient it:constMyArrow=({ side, style }: ArrowComponentProps)=>(<divstyle={{ ...style}}data-side={side}></div>);<NextStepsteps={steps}arrowComponent={MyArrow}>{children}</NextStep>;

Tours Array

NextStep supports multiple "tours", allowing you to create multiple product tours:

import{Tour}from'nextstepjs';conststeps: Tour[]=[{tour: 'firstTour',steps: [// Step objects],},{tour: 'secondTour',steps: [// Step objects],},];

Step Object

PropTypeDescription
iconReact.ReactNode, string, nullOptional. An icon or element to display alongside the step title (used by the default card).
titlestringThe title of your step
contentReact.ReactNodeThe main content or body of the step.
selectorstringOptional. A string used to target an id that this step refers to. If not provided, card will be displayed in the center top of the document body.
side"top", "bottom", "left", "right" (+ corner variants, e.g. "top-left")Optional. Determines where the tooltip should appear relative to the selector.
showControlsbooleanOptional. Determines whether control buttons (next, prev) should be shown if using the default card. Ignored when a custom cardComponent is provided.
showSkipbooleanOptional. Determines whether skip button should be shown if using the default card. Ignored when a custom cardComponent is provided.
blockKeyboardControlbooleanOptional. Determines whether keyboard control should be blocked
pointerPaddingnumberOptional. The padding around the pointer (keyhole) highlighting the target element.
pointerRadiusnumberOptional. The border-radius of the pointer (keyhole) highlighting the target element.
cardOffsetnumberOptional. Gap in pixels between the card and the spotlight highlight; the caret scales with it too (default: 25).
scrollOffsetnumberOptional. Extra clearance in pixels kept above/below the target when it is scrolled into view — useful when a fixed/sticky header would cover it (default: 0).
selectorRetryAttemptsnumberOptional. Extra attempts to find selector when it is missing on the first lookup (for asynchronously rendered targets). 0 keeps the single-lookup behavior (default: 0).
selectorRetryDelaynumberOptional. Delay in milliseconds between selector retry attempts (default: 200).
disableInteractionbooleanOptional. If true, prevents interaction with the highlighted element (default: false).
nextRoutestringOptional. The route to navigate to when moving to the next step.
prevRoutestringOptional. The route to navigate to when moving to the previous step.
viewportIDstringOptional. The id of the viewport element to use for positioning. If not provided, the document body will be used.

NoteNextStep handles card cutoff from screen sides. When the requested side does not have room, NextStep verifies the destination side actually has space before flipping, and otherwise falls back to the side with the most room — so the card stays on-screen instead of swapping into another cramped edge.

Target Anything

Target anything in your app using the element's id attribute.

<divid="nextstep-step1">Onboard Step</div>

Routing During a Tour

NextStep allows you to navigate between different routes during a tour using the nextRoute and prevRoute properties in the step object. These properties enable seamless transitions between different pages or sections of your application.

  • nextRoute: Specifies the route to navigate to when the "Next" button is clicked.
  • prevRoute: Specifies the route to navigate to when the "Previous" button is clicked.

When nextRoute or prevRoute is provided, NextStep will use Next.js's next/navigation to navigate to the specified route.

Using NextStepViewport and viewportID

When a selector is in a scrollable area, it is best to wrap the content of the scrollable area with NextStepViewport. This component takes children and an id as prop. By providing the viewportID to the step, NextStep will target this element within the viewport. This ensures that the step is anchored to the element even if the container is scrollable.

Here's an example of how to use NextStepViewport:

<divclassName="relative overflow-auto h-64"><NextStepViewportid="scrollable-viewport">{children}</NextStepViewport></div>

Example steps

[{tour: 'firsttour',steps: [{icon: <>👋</>,title: 'Tour 1, Step 1',content: <>First tour, first step</>,selector: '#tour1-step1',side: 'top',showControls: true,showSkip: true,pointerPadding: 10,pointerRadius: 10,nextRoute: '/foo',prevRoute: '/bar',},{icon: <>🎉</>,title: 'Tour 1, Step 2',content: <>First tour, second step</>,selector: '#tour1-step2',side: 'top',showControls: true,showSkip: true,pointerPadding: 10,pointerRadius: 10,viewportID: 'scrollable-viewport',},],},{tour: 'secondtour',steps: [{icon: <>🚀</>,title: 'Second tour, Step 1',content: <>Second tour, first step!</>,selector: '#nextstep-step1',side: 'top',showControls: true,showSkip: true,pointerPadding: 10,pointerRadius: 10,nextRoute: '/foo',prevRoute: '/bar',},],},];

NextStep & NextStepReact Props

PropertyTypeDescription
childrenReact.ReactNodeYour website or application content
stepsArray[]Array of Tour objects defining each step of the onboarding
navigationAdapterNavigationAdapterOptional. Router adapter for navigation (defaults to Next.js on NextStep and window adapter on NextStepReact)
showNextStepbooleanControls visibility of the onboarding overlay
shadowRgbstringRGB values for the shadow color surrounding the target area
shadowOpacitystringOpacity value for the shadow surrounding the target area
cardComponentReact.ComponentTypeCustom card component to replace the default one
cardTransitionTransitionMotion transition object for step transitions
onStart(tourName: string | null) => voidCallback function triggered when the tour starts
onStepChange(step: number, tourName: string | null) => voidCallback function triggered when the step changes
onComplete(tourName: string | null) => voidCallback function triggered when the tour completes
onSkip(step: number, tourName: string | null) => voidCallback function triggered when the user skips the tour
clickThroughOverlaybooleanOptional. If true, overlay background is clickable, default is false
disableConsoleLogsbooleanOptional. If true, console logs are disabled, default is false
scrollToTopbooleanOptional. If true, the page will scroll to the top when the tour ends, default is true
noInViewScrollbooleanOptional. If true, the page will not scroll to the target element when it is in view, default is false
overlayZIndexnumberOptional. Base z-index for overlay elements, useful for compatibility with UI libraries like MUI (default: 999)
arrowComponentReact.ComponentType<ArrowComponentProps>Optional. Render a fully custom arrow/caret. Receives the resolved side and the computed positioning style. When omitted, the built-in SVG arrow is used.
arrowStyleReact.CSSPropertiesOptional. Styles merged into the built-in arrow SVG (ignored when arrowComponent is set). Handy for tweaking the caret color or size.

Note When a custom cardComponent is provided, the per-step showControls / showSkip options only affect the built-in card, so they are ignored (and TypeScript omits them from the step type). Your custom card renders its own controls.

<NextStepsteps={steps}showNextStep={true}shadowRgb="55,48,163"shadowOpacity="0.8"cardComponent={CustomCard}cardTransition={{duration: 0.5,type: 'spring'}}onStepChange={(step,tourName)=>console.log(`Step changed to ${step} in ${tourName}`)}onComplete={(tourName)=>console.log(`Tour completed: ${tourName}`)}onSkip={(step,tourName)=>console.log(`Tour skipped: ${step} in ${tourName}`)}clickThroughOverlay={false}overlayZIndex={1400}// Set higher for MUI compatibility (MUI dialogs use 1300)>{children}</NextStep>

useNextStep Hook

useNextStep hook allows you to control the tour from anywhere in your app.

import{useNextStep}from'nextstepjs';
....const{ startNextStep, closeNextStep }=useNextStep();constonClickHandler=(tourName: string)=>{startNextStep(tourName);};

Keyboard Navigation

NextStep supports keyboard navigation:

  • Right Arrow: Next step
  • Left Arrow: Previous step
  • Escape: Skip tour

Localization

NextStep is a lightweight library and does not come with localization support. However, you can easily switch between languages by supplying the steps array based on locale.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

This project is licensed under the MIT License.

Credits

  • Onborda for the inspiration and some code snippets.

About

Lightweight onboarding library for Next.js

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages