@tent-official/react-walkthrough
v1.6.1
Published
Lightweight React walkthrough/tour component with auto-positioning, dependency chains, and smooth animations
Maintainers
Readme
@tent-official/react-walkthrough
Lightweight React walkthrough/tour component with auto-positioning, dependency chains, and smooth animations.
Features
- SVG-based spotlight overlay with smooth animated transitions
- Auto-positioning popover (top/bottom/left/right) with viewport detection
- Inner positions (
inner-top,inner-bottom,inner-left,inner-right) — place the popover inside the highlight area - Dependency chains — start a walkthrough only after another completes
- Conditional start —
startWhenConditionprop to gate walkthrough on custom logic (e.g. data loaded) - Auto-cleanup on navigation — highlight is automatically removed when the component unmounts (e.g. browser back/forward)
- Customizable labels, colors, and layout
- Reset and replay walkthroughs without page refresh
- Portal-based rendering (works inside modals)
- Zero external dependencies (only React as peer dep)
Installation
npm install @tent-official/react-walkthrough
# or
yarn add @tent-official/react-walkthroughPeer Dependencies
Make sure these are installed in your project:
npm install react react-domQuick Start
import { useWalkthrough, WalkthroughOverlay } from "@tent-official/react-walkthrough";
function App() {
useWalkthrough({
name: "intro-tour",
delay: 500, // wait 500ms before highlighting the first step
steps: [
{
el: "welcome-title",
title: "Welcome!",
description: [{ description: "This is the main title of your app." }],
},
{
el: "nav-menu",
title: "Navigation",
description: [{ description: "Use this menu to navigate around." }],
position: "right",
},
],
});
return (
<div>
<h1 id="welcome-title">My App</h1>
<nav id="nav-menu">...</nav>
<WalkthroughOverlay />
</div>
);
}API
useWalkthrough(options)
Hook to register a walkthrough tour.
| Option | Type | Default | Description |
| --- | --- | --- | --- |
| name | string | required | Unique name for this walkthrough |
| steps | IWalkthroughStep[] | required | Array of steps |
| storageSuffix | string | "" | Storage suffix for localStorage |
| dependsOn | string[] | [] | Walkthrough names that must complete first |
| startWhenCondition | boolean \| () => boolean | undefined | Additional condition that must be true for the walkthrough to start. Both dependsOn AND startWhenCondition must be satisfied. Example: startWhenCondition: list.length > 0 |
| onWalkthroughComplete | (name: string) => void | — | Callback when walkthrough completes (both finish and skip) |
| handleWhenLastStep | () => void | — | Callback fired only when the user clicks "Done" on the last step (not on skip). Useful for triggering navigation (e.g. router.push) |
| isShowSkip | boolean | true | Show skip button |
| isShowPrev | boolean | true | Show back button (can be overridden per step) |
| isShowStep | boolean | true | Show step counter e.g. "1/3" (can be overridden per step) |
| nextLabel | string | "Next" | Next button label (can be overridden per step) |
| prevLabel | string | "Back" | Previous button label (can be overridden per step) |
| skipLabel | string | "Skip" | Skip button label (can be overridden per step) |
| delay | number | 0 | Delay in ms before highlighting the first step. During delay, a dark overlay is shown |
| doneLabel | string | "Done" | Done button label (last step) (can be overridden per step) |
| containerElement | string | — | DOM element ID of the scrollable container (e.g. a drawer). When provided, scrolls within this container instead of the window |
| animationSpeed | number | 350 | Base animation speed (ms) used to calculate transition duration from distance |
| displayTotal | number | — | Override the total step count displayed in the step counter (display-only, does not affect logic) |
| displayStepOffset | number | 0 | Offset added to the displayed current step number (display-only, does not affect logic). e.g. displayStepOffset: 3 with 3 real steps shows 4/total → 5/total → 6/total |
| isClearLastPositionOnEnd | boolean | false | When true, clears the saved last highlight position when the tour ends. This prevents the next tour from animating its highlight from this tour's last position |
| onStepNext | Record<number, () => void> | — | Map of original step index → callback. Fires when the user clicks "Next" from that step. Example: { 0: () => console.log("left step 0"), 2: () => doSomething() } |
| onStepPrev | Record<number, () => void> | — | Map of original step index → callback. Fires when the user clicks "Back" from that step. Example: { 1: () => closeDropdown(), 3: () => cleanup() } |
Returns: { start: () => void }
IWalkthroughStep
| Property | Type | Default | Description |
| --- | --- | --- | --- |
| el | string | required | DOM element ID to highlight (with or without # prefix) |
| title | string | — | Popover title |
| description | IStepDescription[] | required | Description blocks |
| position | "top" \| "bottom" \| "left" \| "right" \| "inner-top" \| "inner-bottom" \| "inner-left" \| "inner-right" | auto | Preferred popover position. inner-* positions place the popover inside the highlight area (12 px inset) |
| padding | number | 8 | Padding around highlighted element (px) |
| borderRadius | number | 10 | Border radius of highlight cutout (px) |
| width | number \| string | "auto" | Popover width |
| height | number \| string | — | Popover height |
| triggerElOnNext | string \| boolean | — | When set, clicking "Next" triggers a click on an element. If a string (element ID), clicks that element; if true, clicks the step's own target element |
| isShowPrev | boolean | — | Override tour-level isShowPrev for this step |
| isShowStep | boolean | — | Override tour-level isShowStep for this step |
| nextLabel | string | — | Override tour-level nextLabel for this step |
| prevLabel | string | — | Override tour-level prevLabel for this step |
| skipLabel | string | — | Override tour-level skipLabel for this step |
| doneLabel | string | — | Override tour-level doneLabel for this step |
| displayStep | number | — | Override the displayed step number for this step (display-only) |
| displayTotal | number | — | Override the displayed total for this step (display-only) |
| delayAfterNext | number | 0 | Delay in ms after clicking "Next" (after onStepNext and triggerElOnNext have fired) before advancing to the next step. The overlay stays visible with the popover hidden during the delay |
| nextEl | string | — | DOM element ID to look for after delayAfterNext completes. If found, advances to the step that owns it. If not found, skips to the next valid step. Supports #id or id format |
IStepDescription
| Property | Type | Description |
| --- | --- | --- |
| title | string | Optional label for this block |
| description | ReactNode | Description content |
| direction | "row" \| "column" | Layout direction |
<WalkthroughOverlay />
Place this component once at the root of your app. It renders via React Portal into document.body.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| $popoverPadding | number | 12 | Inner padding of popover (px) |
| $popoverBorderRadius | number | 8 | Border radius of popover (px) |
| $popoverGap | number | 12 | Gap between highlight and popover (px) |
| $popoverMinWidth | number | 275 | Minimum width of the popover (px) |
| $animationSpeed | number | 350 | Base animation speed (ms). Actual duration is calculated from distance — shorter distance = faster, longer distance = slower. Lower value = faster overall, higher = slower overall |
| nextColor | string | "#000" | Custom next button color |
| prevColor | string | — | Custom previous button color |
| skipColor | string | — | Custom skip button color |
resetWalkthrough(options?)
Reset walkthroughs so they can be replayed without page refresh.
import { resetWalkthrough } from "@tent-official/react-walkthrough";
resetWalkthrough({
storageSuffix: "my-app",
walkthroughList: ["intro-tour", "feature-tour"],
});| Option | Type | Default | Description |
| --- | --- | --- | --- |
| storageSuffix | string | "" | Storage suffix |
| walkthroughList | string[] | [] | Names of walkthroughs to reset |
getCompletedWalkthroughs(options?)
Get a list of all completed walkthrough names. Returns an array of walkthrough name strings that have been marked as done.
import { getCompletedWalkthroughs } from "@tent-official/react-walkthrough";
const list = getCompletedWalkthroughs({ storageSuffix: "my-app" });
// => ["intro-tour", "feature-tour"]| Option | Type | Default | Description |
| --- | --- | --- | --- |
| storageSuffix | string | "" | Storage suffix matching the one used in useWalkthrough |
Dependency Chains
You can create sequential walkthroughs using dependsOn:
// This runs first
useWalkthrough({
name: "intro-tour",
storageSuffix: "my-app",
steps: [/* ... */],
});
// This starts automatically after intro-tour completes
useWalkthrough({
name: "advanced-tour",
storageSuffix: "my-app",
dependsOn: ["intro-tour"],
steps: [/* ... */],
});Conditional Start
Use startWhenCondition to gate a walkthrough on custom logic. The walkthrough will only start when both dependsOn (if any) and startWhenCondition are satisfied:
function Dashboard({ items }) {
// Won't start until items are loaded AND intro-tour is done
useWalkthrough({
name: "items-tour",
storageSuffix: "my-app",
dependsOn: ["intro-tour"],
startWhenCondition: items.length > 0,
steps: [/* ... */],
});
// Also accepts a function
useWalkthrough({
name: "profile-tour",
startWhenCondition: () => document.getElementById("profile-btn") !== null,
steps: [/* ... */],
});
return <div>...</div>;
}When
startWhenConditionisundefined(default), onlydependsOnis checked — backward compatible.
Auto-Cleanup on Navigation
The walkthrough highlight is automatically removed when the component that registered it unmounts. This means:
- Pressing browser back/forward properly clears the overlay
- Route changes (e.g. React Router, Next.js) clean up automatically
- No stale highlights left on screen after navigation
No extra configuration needed — this works out of the box.
Keyboard Blocking
While a walkthrough is active, the following keyboard keys are automatically blocked to prevent users from accidentally interacting with highlighted elements behind the overlay:
| Key | Reason |
| --- | --- |
| Enter | Prevents triggering click on focused elements |
| Space | Prevents triggering click on buttons/checkboxes |
| Escape | Prevents closing dialogs/dropdowns being highlighted |
| Backspace | Prevents browser back navigation or input deletion |
| Delete | Prevents deleting content in inputs |
| Tab | Prevents moving focus to elements behind the overlay |
Walkthrough buttons (Next / Back / Skip / Done) remain fully functional — only keys targeting elements outside the walkthrough are blocked.
Auto-Positioning
The popover automatically positions itself to stay within the viewport:
- If
positionis specified, it tries that position first - If it doesn't fit, it falls back to: bottom → top → right → left
- If nothing fits perfectly, it picks the direction with the most available space
The popover also waits for the target element to be scrolled into view before appearing.
Inner Positions
Use inner-top, inner-bottom, inner-left, or inner-right to place the popover inside the highlight area, inset 12 px from the corresponding edge. Inner positions never auto-fallback — they always stay exactly where specified.
This is useful when the highlighted area is large (e.g. a table or a panel) and you want the popover to appear within it rather than outside.
{
el: "large-table",
title: "Table Overview",
description: [{ description: "Here is your data table." }],
position: "inner-bottom", // popover sits inside the highlight, near the bottom-right corner
}Instant Dismiss
When the user clicks Done on the last step (or Skip at any step), the highlight and popover vanish instantly in the same render frame — no lingering overlay or flash.
License
MIT
