
GlowTour.js is a TypeScript product-tour library for onboarding walkthroughs and feature guidance in vanilla JavaScript, React, Vue, Solid, and Angular.
Features
- CSS selectors, DOM elements, and resolver functions for step targets.
- Async workflow actions for application state, DOM availability, and user events.
- Ordered popover and pointer placement with collision fallback.
- Configurable scrolling, page interaction, overlays, and missing-target handling.
- Dialog semantics, keyboard shortcuts, focus trapping, and focus restoration.
- Light and dark themes controlled through CSS custom properties.
- Programmatic navigation, state subscriptions, and lifecycle analytics.
- Optional JSON configuration for CMS or API-driven workflows.
How To Use GlowTour.js
Installation (Vanilla JS)
Install the Vanilla adapter and default theme:
npm i @glowhop/vanilla-tour @glowhop/styles-tour
Import the stylesheet and Vanilla API. The regular entry point requires one call to registerGlowTourElements(). Import from @glowhop/vanilla-tour/auto when automatic Custom Element registration is preferable
import "@glowhop/styles-tour/default.css";
import {
createDefaultTourElement,
createGlowTour,
registerGlowTourElements,
} from "@glowhop/vanilla-tour";
Basic Usage
Create the elements that the tour will highlight:
<label> Workspace name <input id="workspace-name" value="Design Team" /> </label> <button id="save-settings" type="button">Save settings</button> <button id="start-tour" type="button">Start tour</button>
Create one controller, build the workflow, mount the default tour element, and connect the start button:
import "@glowhop/styles-tour/default.css";
import {
createDefaultTourElement,
createGlowTour,
registerGlowTourElements,
} from "@glowhop/vanilla-tour";
registerGlowTourElements();
const tour = createGlowTour();
const workflow = tour
.create("workspace-onboarding")
.step({
id: "workspace-name",
target: "#workspace-name",
title: "Name your workspace",
content: "Choose the name shown across the workspace.",
})
.step({
id: "save-settings",
target: "#save-settings",
title: "Save your settings",
content: "Save the current workspace configuration.",
})
.build();
document.body.append(createDefaultTourElement(tour));
document.querySelector("#start-tour").addEventListener("click", () => {
void tour.run(workflow);
});
Direct Browser Setup With CDN
Load the Vanilla JavaScript and default CSS packages from a CDN:
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@glowhop/styles-tour/default.css"
/>
<script type="module">
import {
createDefaultTourElement,
createGlowTour,
registerGlowTourElements,
} from "https://cdn.jsdelivr.net/npm/@glowhop/vanilla-tour/+esm";
registerGlowTourElements();
const tour = createGlowTour();
const workflow = tour
.create("feature-tour")
.step({
id: "feature",
target: "#feature",
title: "Feature controls",
content: "Use these controls to configure the feature.",
})
.build();
document.body.append(createDefaultTourElement(tour));
document.querySelector("#start-tour").addEventListener("click", () => {
void tour.run(workflow);
});
</script>
Wait For UI State Or User Actions
A workflow can pause until an element appears or respond to an event from the active element.
const workflow = tour
.create("editor-tour")
.step({
id: "open-preview",
target: "#preview-button",
title: "Open the preview",
content: "Select Preview to continue.",
behavior: {
allowInteraction: true,
},
})
.onTargetEvent("click", (_event, context) => {
context.advance();
})
.waitUntilElement("#preview-panel", {
timeout: 5000,
})
.step({
id: "preview-panel",
target: "#preview-panel",
title: "Preview panel",
content: "Review the rendered result here.",
})
.build();
Rich Content In Vanilla Tours
Vanilla title and content values accept a string or DOM Node. Strings render as text. Create a DOM node for images, videos, or custom markup.
Sanitize untrusted CMS or API content before constructing rich DOM content.
const preview = document.createElement("img");
preview.src = "/images/editor-preview.png";
preview.alt = "Editor preview";
preview.width = 280;
const workflow = tour
.create("media-tour")
.step({
id: "preview",
target: "#preview-button",
title: "Preview",
content: preview,
})
.build();
Workflow Builder API
Create a workflow with tour.create(), chain steps and actions, and finish with .build().
tour.create(name, options?): Creates a workflow builder.nameis the workflow identifier andoptionsaccepts the Start Options listed later..step(params): Defines a tour step and returns aWorkflowStepBuilder..do(callback): Runs a synchronous or async action between steps. Returningfalsestops the rest of that step action sequence..wait(ms): Pauses for a fixed number of milliseconds..waitUntil(fn, options?): Polls a condition until it returnstrueor the timeout expires..waitUntilElement(selector, options?): Waits for a matching element to enter the DOM..onTargetEvent(event, callback): Listens for one DOM event, an array of events, or a custom event on the current step element..build(): Returns an immutableWorkflowDefinition.
Step-level transition callbacks belong inside .step():
beforeAdvance(context): Runs before the workflow advances and can return a promise.beforeCancel(context): Runs before cancellation and can return a promise.beforePrevious(context): Runs before navigation to the previous step and can return a promise.
Step Parameters
Every step requires id, target, title, and content.
id(string, required): Stable unique identifier inside the workflow. Duplicate IDs fail validation during.build().target(string | HTMLElement | TargetResolver, required): CSS selector, DOM element, or resolver function.title(string | Nodein Vanilla, required): Popover title.content(string | Nodein Vanilla, required): Popover content.resetPropsOnEnter(boolean, defaulttrue): Resets step properties when the step becomes active.data(Record, optional): Custom data attached to the step.overlay(OverlayOptions, optional): Step-specific overlay settings.popover(PopoverOptions, optional): Step-specific popover settings.indicator(IndicatorOptions, optional): Step-specific pointer settings.behavior(StepBehavior, optional): Interaction, scrolling, and missing-element settings.beforeAdvance(callback, optional): Runs before advancing.beforeCancel(callback, optional): Runs before cancellation.beforePrevious(callback, optional): Runs before going back.
Configuration Reference
Start Options
Pass these options to tour.create(name, options).
cancellable(boolean, defaulttrue): Controls user cancellation.animated(boolean, defaulttrue): Controls animations. Reduced-motion preferences disable them automatically.overlay(OverlayOptions): Workflow-level overlay settings.popover(PopoverOptions): Workflow-level popover settings.indicator(IndicatorOptions): Workflow-level pointer settings.behavior(StepBehavior): Workflow-level behavior settings.allowScroll(boolean, defaulttrue): Keeps page scrolling available during the tour. Setfalseto lock it until finish, cancel, error, or disposal.onStart((context) => void | Promise<void>): Lifecycle hook called when the tour starts.onCancel((context) => void | Promise<void>): Lifecycle hook called during cancellation.onFinish((context) => void | Promise<void>): Lifecycle hook called during completion.onEvent((event: TourEvent) => void): Monitoring callback for this workflow.
Overlay Options
color(string): Overlay color. The theme fill is used when omitted.opacity(number, default0.7): Overlay opacity from0to1.padding(number, default8): Space around the highlighted element in pixels.radius(number, default8): Cutout radius in pixels.animated(boolean, defaulttrue): Controls overlay animation.animation(AnimationOptions): Custom duration and easing.
Popover Options
Valid popover placements are top, bottom, left, and right. GlowTour.js checks placementTryOrder in sequence and centers the popover when none of the listed sides has enough viewport space.
placementTryOrder(Array, default["bottom", "top", "right", "left"]): Ordered popover placement preferences.gap(number, default16): Space between the popover and highlighted element plus the minimum viewport edge gap, in pixels.hideFooter(boolean, defaultfalse): Hides the footer navigation controls.hideAdvanceButton(boolean, defaultfalse): Hides the Next button. Keyboard advance remains available.disableAdvanceButton(boolean, defaultfalse): Blocks button and keyboard advance.hidePreviousButton(boolean, defaultfalse): Hides the Previous button. Keyboard navigation remains available.disablePreviousButton(boolean, defaultfalse): Blocks button and keyboard navigation to the previous step.animated(boolean, defaulttrue): Controls popover animation.animation(AnimationOptions): Custom duration and easing.keyboardShortcuts.advance(string[], default["Enter", "ArrowRight"]): Advance shortcuts.keyboardShortcuts.previous(string[], default["ArrowLeft", "Backspace"]): Previous-step shortcuts.keyboardShortcuts.cancel(string[], default["Escape"]): Cancel shortcuts.arrow(PopoverArrowOptions): Popover arrow settings.
Popover Arrow Options
JavaScript arrow values are written as inline CSS custom properties and take precedence over corresponding --glow-tour-arrow-* values in a stylesheet.
disabled(boolean, defaultfalse): Hides the popover arrow.color(string): Arrow fill. The theme surface color is used when omitted.size(number, default12): Arrow width and height in pixels before rotation.borderWidth(number, default1): Arrow border width in pixels.borderRadius(number, default0): Arrow border radius in pixels.edgePadding(number, default16): Minimum distance from the popover edges in pixels.styleNonce(string): CSP nonce for injected arrow styles.disableAutoStyles(boolean, defaultfalse): Disables built-in arrow style injection for a custom arrow shape.
Indicator Options
disabled(boolean, defaultfalse): Hides the decorative pointer.gap(number, default16): Space between the pointer and highlighted element in pixels.placementTryOrder(Array, default["left", "right", "top", "bottom"]): Ordered pointer placement preferences.animated(boolean, defaulttrue): Controls pointer animation.animation(AnimationOptions): Custom duration and easing.
Behavior Options
allowInteraction(boolean, defaultfalse): Permits interaction with the highlighted element.disableAutoFocus(boolean, defaultfalse): Skips automatic focus handling.disableAutoScroll(boolean, defaultfalse): Stops automatic scrolling to an off-screen element.missingTargetStrategy("error" | "wait" | "skip", default"error"): Selects the response when the element cannot be resolved.overlayClick("none" | "advance" | "cancel", default"none"): Selects the action for a click on the dimmed overlay.targetTimeout(number, default3000): Maximum wait time in milliseconds.scroll(ScrollOptions): Scroll alignment and animation settings.
Scroll Options
behavior("auto" | "smooth", default"smooth"): Browser scroll behavior. Reduced-motion mode uses an instant scroll.block("start" | "center" | "end" | "nearest", default"center"): Vertical alignment.inline("start" | "center" | "end" | "nearest", default"nearest"): Horizontal alignment.
Animation Options
duration(number, default180): Animation time in milliseconds.easing(string, default"ease-out"): CSS easing function.
Animations use a zero duration when animation is disabled or reduced motion is detected.
Wait Options
These options apply to .waitUntil() and .waitUntilElement().
interval(number, default16): Poll interval in milliseconds.timeout(number, default3000): Maximum wait time in milliseconds.
Programmatic Control
createGlowTour() accepts two controller-level options:
onSubscriberError: Receives errors thrown by state or step subscribers.onEvent: Receives monitoring events for every workflow run by this controller.
The returned controller exposes these methods:
tour.create(name, options?); tour.run(workflow, options?); tour.advance(); tour.previous(); tour.goToStep(index); tour.cancel(); tour.dispose();
tour.create(name, options?)returns a workflow builder.tour.run(workflow, { startAt? })runs a built workflow and returns a promise.startAtaccepts a stable step ID.tour.advance()moves forward whencanAdvanceistrue.tour.previous()moves backward whencanPreviousistrue.tour.goToStep(index)jumps to a zero-based step index.tour.cancel()cancels the tour whencanCancelistrue.tour.dispose()cancels pending work, releases the connected root, and permanently disposes that controller instance.
Tour State
Read a snapshot with tour.state.get() or subscribe with tour.state.subscribe(listener). The subscription method returns an unsubscribe function.
{
status: "idle" | "starting" | "transitioning" | "active" |
"finished" | "cancelled" | "error" | "disposed",
name: string,
totalSteps: number,
currentStepIndex: number,
currentStep: TourCurrentStep | null,
direction: "advance" | "previous",
canAdvance: boolean,
canPrevious: boolean,
canCancel: boolean,
isFirstStep: boolean,
isLastStep: boolean,
error: Error | null
}
Monitor Tour Events
Use onEvent to send tour activity to analytics or logging code:
const tour = createGlowTour({
onEvent(event) {
analytics.track(event.type, {
workflow: event.workflowName,
step: event.stepId,
source: event.source,
duration: event.durationMs,
});
},
});
Event Types
tour:start: Emitted afteronStartcompletes and before the first step appears.step:enter: Emitted when a step is on screen and interactive.step:leave: Emitted when the workflow leaves a step.tour:complete: Emitted after the workflow passes its final step.tour:cancel: Emitted when the tour is cancelled.tour:error: Emitted when the tour fails.
Event Payload
Every monitoring event includes:
type: Event type.workflowName: Workflow name passed tocreate().stepId: Related step ID, ornull.stepIndex: Zero-based step index, or-1when no step applies.stepCount: Total number of workflow steps.direction:"advance"or"previous".source: Source of the transition.timestamp:Date.now()value at emission.durationMs: Duration associated with the event.error: Failure object ontour:erroronly.
Resume A Tour
GlowTour.js does not include a persistence layer. Store a stable step ID in sessionStorage, localStorage, or application storage, rebuild the workflow after a reload, and pass the ID through startAt.
const savedStepId = sessionStorage.getItem("onboarding-step");
await tour.run(workflow, {
startAt: savedStepId || undefined,
});
JSON Configuration
Import the config entry point when workflows come from a CMS, API, or stored JSON object.
Use the existing mounted tour controller from the Vanilla setup:
import {
ConfigValidationError,
createWorkflowFromConfig,
} from "@glowhop/vanilla-tour/config";
const config = {
name: "onboarding",
overlay: { opacity: 0.55 },
steps: [
{
id: "invite-button",
target: "#invite-button",
title: "Invite your team",
content: "Open the invite form.",
actions: [
{
type: "waitUntilElement",
selector: "#invite-button",
timeout: 5000,
},
],
eventHandlers: [
{
event: "click",
action: { type: "focusTarget" },
},
],
},
],
};
try {
const workflow = createWorkflowFromConfig(config);
await tour.run(workflow);
} catch (error) {
if (error instanceof ConfigValidationError) {
console.error(error.issues);
}
}
JSON Configuration Rules
nameandstepsare required.- Each step requires
id,target,title, andcontent. - JSON targets use CSS selectors.
- JSON
titleandcontentvalues use strings. - Global and per-step
overlay,popover,indicator, andbehaviorfields use the Builder option names. datastores JSON-compatible step metadata.actionsruns actions associated with a step.eventHandlersattaches a configured action to an event.
Built-in JSON Actions
waitwithmsmaps to a fixed delay.waitUntilElementacceptsselector, plus optionalintervalandtimeout.clickTargetdispatches a click to the active element.focusTargetmoves focus to the active element.
Same-runtime JavaScript configuration objects can also use functions in actions and eventHandlers. advanceAction, previousAction, cancelAction, onStart, onCancel, and onFinish accept functions only and cannot be represented in transported JSON.
Pass validateContent to createWorkflowFromConfig() when an application-created configuration object needs framework content such as a DOM Node, React node, Vue VNode, or Angular TemplateRef. Keep the default string validation for untrusted JSON.
Each adapter exports its own config entry point:
@glowhop/react-tour/config @glowhop/vue-tour/config @glowhop/solid-tour/config @glowhop/angular-tour/config @glowhop/vanilla-tour/config
Styling And Theming
Import @glowhop/styles-tour/default.css before overriding the theme tokens. The default stylesheet declares its tokens at zero specificity, which permits overrides on :root, the tour root, or an ancestor of the tour.
-where([data-glow-tour-root]) {
--glow-tour-color-accent: #7c3aed;
--glow-tour-color-surface: #faf5ff;
--glow-tour-color-text: #24143f;
--glow-tour-radius: 14px;
--glow-tour-popover-width: 320px;
}
The default theme follows prefers-color-scheme. Set data-glow-tour-theme="light" or data-glow-tour-theme="dark" on an ancestor to force a palette.
Color Tokens
--glow-tour-color-accent:#4c35fdlight,#6d5bffdark.--glow-tour-color-accent-hover:#3f2be0light,#5d4bf0dark.--glow-tour-color-accent-active:#3522c7light,#4f3ce0dark.--glow-tour-color-on-accent:#ffffff.--glow-tour-color-surface:#fffffflight,#1c1c21dark.--glow-tour-color-surface-muted:#f6f6f7light,#26262ddark.--glow-tour-color-text:#1f1f23light,#f2f2f4dark.--glow-tour-color-text-muted:#5f5f66light,#a8a8b3dark.--glow-tour-color-border:#dedee3light,#3a3a44dark.--glow-tour-overlay-color:#000000in both palettes.
Spacing And Size Tokens
--glow-tour-spacing:8px.--glow-tour-popover-width:352px.--glow-tour-control-height:32px.--glow-tour-viewport-gap:16px.
Style Tokens
--glow-tour-radius:8px.--glow-tour-shadow:0 4px 12px rgb(0 0 0 / 8%)in light mode and0 8px 24px rgb(0 0 0 / 56%)in dark mode.--glow-tour-transition-duration:120ms.--glow-tour-transition-easing:ease-out.
Arrow Tokens
The core injects the arrow rules. These variables work even when the default theme stylesheet is absent.
--glow-tour-arrow-color: Uses--glow-tour-color-surface, with#ffffffas fallback.--glow-tour-arrow-border-color: Uses--glow-tour-color-border, with#dedee3as fallback.--glow-tour-arrow-border-width:1px.--glow-tour-arrow-border-radius:0px.--glow-tour-arrow-size:12px.
Vanilla Adapter Functions
createGlowTour(options?: GlowTourOptions): VanillaGlowTour: Creates the tour controller.registerGlowTourElements(): void: Registers the GlowTour Custom Elements.createDefaultTourElement(tour, options?): GlowTourRootElement: Creates the default overlay, popover, pointer, and navigation composition.
Vanilla Custom Elements Reference
createDefaultTourElement(tour) creates the normal composition. Compose the elements manually when the popover structure itself needs to change.
glow-tour-root: Root container. Public property:tour: VanillaGlowTour.glow-tour-overlay: Backdrop and highlighted cutout layer.glow-tour-popover: Dialog container for the current step.glow-tour-header: Renders the step title.glow-tour-content: Renders the step content.glow-tour-footer: Contains navigation controls.glow-tour-pointer: Decorative directional pointer. Public property:directionContent: PointerDirectionContent.glow-tour-advance-trigger: Next control. Public property:disabled: boolean.glow-tour-cancel-trigger: Cancel control. Public property:disabled: boolean.glow-tour-back-trigger: Previous control. Public property:disabled: boolean.
GLOW_TOUR_ELEMENT_NAMES contains all ten registered element names.
The pointer’s default directional glyphs are 👆 for top, 👇 for bottom, 👈 for left, and 👉 for right. Assign strings or DOM nodes through directionContent to replace them.
Framework And Browser Compatibility
GlowTour.js currently lists these framework contracts:
- Vanilla/Browser: Custom Elements and Shadow DOM. Chrome 77+, Firefox 63+, Safari 13+, and Edge 79+.
- React: React 18 and 19 through
@glowhop/react-tour. - Vue: Vue 3.3+ through
@glowhop/vue-tour. - Solid: Solid 1.8+ through
@glowhop/solid-tour. - Angular: Angular 18+ through
@glowhop/angular-tour.
Alternatives & Related Resources
- Build Interactive Product tours & User Onboarding with Cue.js
- Lightweight User Tour/Onboarding Library for Web Apps – CodingIntroJS
- Lightweight Vanilla JS Library for Interactive Guided Tours – Boarding.js
- Create Interactive Guided Tours In App – TourGuide.js
- Build Interactive Website Walkthroughs With Journey.js







