
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.
- Step entry and exit hooks that can abort navigation before it is committed.
- Ordered popover and pointer placement with collision fallback.
- Configurable scrolling, page interaction, overlays, controls, and missing-target handling.
- Per-step class names for the overlay, popover, pointer, content, footer, and navigation controls.
- Dialog semantics, keyboard shortcuts, focus management, and focus restoration.
- Light and dark themes controlled through CSS custom properties.
- Programmatic navigation, state subscriptions, and lifecycle analytics.
- Optional versioned 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 {
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 {
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",
content: "Save the current workspace configuration.",
})
.build();
const tourElement = document.createElement("glow-tour-default");
tourElement.tour = tour;
document.body.append(tourElement);
document.querySelector("#start-tour").addEventListener("click", () => {
void tour.start(workflow);
});
Direct Browser Setup With CDN
Load the Vanilla JavaScript and default CSS packages from jsDelivr:
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@glowhop/[email protected]/default.css" /> <script type="module"> import { createGlowTour, registerGlowTourElements, } from "https://cdn.jsdelivr.net/npm/@glowhop/[email protected]/+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(); const tourElement = document.createElement("glow-tour-default"); tourElement.tour = tour; document.body.append(tourElement); document.querySelector("#start-tour").addEventListener("click", () => { void tour.start(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. The title is optional. 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",
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 after the current step appears. Returningfalsestops the remaining actions for that step..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..clickTarget(): Callsclick()on the current step target..focusTarget(): Callsfocus()on the current step target..append(workflow): Copies another workflow’s steps, actions, hooks, and target events after the current step..onTargetEvent(event, callback): Listens for one DOM event, an array of events, or a custom event on the current step target..beforeEnter(callback): Runs after the destination target resolves and before the step appears. Callcontext.abort()to stop the navigation..beforeLeave(callback): Runs beforeadvance(),previous(),goTo(), or finishing the tour. It does not run on cancel..build(): Returns an immutableWorkflowDefinition.
Step actions receive a StepContext with target, props, initialProps, direction, signal, and the advance(), previous(), goTo(id), and cancel() navigation methods. Step props persist when a workflow returns to a step during the same run.
The beforeEnter and beforeLeave hooks receive a StepHookContext. It exposes props, initialProps, target, signal, direction, and abort(), but no navigation methods.
Step Parameters
Every step requires id, target, and content. title is optional.
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, optional): Popover title. A step with no title omits the header and uses the content to name the dialog.content(string | Nodein Vanilla, required): Popover content.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, focus, and missing-target settings.controls(TourControls, optional): State and keyboard shortcuts for advance, previous, and cancel.classNames(TourClassNames, optional): Classes applied to rendered tour components for this step.
Configuration Reference
Start Options
Pass these options to tour.create(name, options).
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.controls(TourControls): Default command states and keyboard shortcuts.classNames(TourClassNames): Default component classes for the workflow.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.
The lifecycle hooks receive step and abort(). Calling abort() before a hook completes prevents that start, cancellation, or finish transition.
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.animated(boolean, defaulttrue): Controls popover animation.animation(AnimationOptions): Custom duration and easing.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.
hidden(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.autoStyles(boolean, defaulttrue): Injects the built-in arrow CSS. Setfalsewhen drawing the arrow with custom CSS.
Indicator Options
hidden(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.allowScroll(boolean, defaulttrue): Keeps page scrolling available while the step is shown.autoFocus(boolean, defaulttrue): Moves focus to the tour controls when the step appears. Setfalseto leave focus where it is.autoScroll(boolean, defaulttrue): Scrolls an off-screen target into view.missingTarget.strategy("error" | "wait" | "skip" | "detached", default"error"): Selects the response when the target cannot be resolved."detached"centers the step over a full-screen backdrop.missingTarget.timeout(number, default3000): Maximum target wait time for the"wait"strategy, in milliseconds.overlayClick("none" | "advance" | "cancel", default"none"): Selects the action for a click on the dimmed overlay.scroll(ScrollOptions): Scroll alignment and animation settings.
If an active target disappears from the DOM, GlowTour.js holds the current presentation for a short grace period. A quick reconnection resumes the step. Otherwise the configured missingTarget.strategy takes over.
Controls Options
Controls define whether a user can run each navigation command through the tour UI and which keys trigger it. Programmatic methods such as tour.advance() and tour.cancel() are not blocked by these states.
advance.state("enabled" | "disabled", default"enabled"): Controls the Next button, advance keys, andoverlayClick: "advance".advance.keys(string[], default["Enter", "ArrowRight"]): Keyboard shortcuts for advancing.previous.state("enabled" | "disabled", default"enabled"): Controls the Previous button and previous-step keys.previous.keys(string[], default["ArrowLeft", "Backspace"]): Keyboard shortcuts for going back.cancel.state("enabled" | "disabled", default"enabled"): Controls the Skip button, cancel keys, andoverlayClick: "cancel".cancel.keys(string[], default["Escape"]): Keyboard shortcuts for cancelling.
The default footer is always rendered. Use classNames to hide a button or the footer from the layout.
Class Name Options
Each entry accepts a class name string or an array of class names. A step’s entry replaces the workflow entry for the same component.
overlay: Overlay SVG.popover: Popover.pointer: Pointer indicator.header: Popover header.content: Popover content.footer: Popover footer.previous: Previous button.advance: Advance button.cancel: Cancel button.
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 started by this controller.
The returned controller exposes these methods:
tour.create(name, options?); tour.start(workflow, options?); tour.advance(); tour.previous(); tour.goTo(id); tour.cancel(); tour.dispose();
tour.create(name, options?)returns a workflow builder.tour.start(workflow, { startAt? })starts a built workflow and returns a promise that resolves when the first step is on screen.startAtaccepts a stable step ID.tour.advance()moves forward whencanAdvanceistrue.tour.previous()moves backward whencanPreviousistrue.tour.goTo(id)jumps to the step with the supplied ID. It throws when the workflow has no matching step.tour.cancel()cancels a running tour.tour.dispose()cancels pending work, releases the connected root, and permanently disposes that controller instance.
One controller can connect to one live root at a time.
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 afteronStartand the first step’sbeforeEntercomplete without an abort.step:enter: Emitted when a step is on screen and interactive.step:leave: Emitted when the workflow leaves a displayed step.step:skip: Emitted for a step passed over becausemissingTarget.strategyis"skip"and its target is missing.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.
A navigation aborted by beforeEnter or beforeLeave emits no navigation events.
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:"trigger","keyboard","overlay", or"api".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.start(workflow, {
startAt: savedStepId || undefined,
});
JSON Configuration
Import the config entry point when workflows come from a CMS, API, or stored JSON object. GlowTour.js 1.4 uses config format 1.1, and the version field is required.
Use the existing mounted tour controller from the Vanilla setup:
import {
ConfigValidationError,
createWorkflowFromConfig,
} from "@glowhop/vanilla-tour/config";
const config = {
version: "1.1",
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,
},
],
targetEvents: [
{
event: "click",
action: { type: "focusTarget" },
},
],
},
],
};
try {
const workflow = createWorkflowFromConfig(config);
await tour.start(workflow);
} catch (error) {
if (error instanceof ConfigValidationError) {
console.error(error.issues);
}
}
JSON Configuration Rules
version,name, andstepsare required. The current config format version is"1.1".- Each step requires
id,target, andcontent.titleis optional. - JSON targets use CSS selectors. Function and
HTMLElementtargets are builder-only. - JSON
titleandcontentvalues use strings by default. - Global and per-step
overlay,popover,indicator,behavior, andclassNamesfields use the Builder option names. datastores JSON-compatible primitive step metadata.actionsruns actions associated with a step.targetEventsattaches a configured action to one or more events on the current target.
Built-in JSON Actions
waitwithmsmaps to a fixed delay.waitUntilElementacceptsselector, plus optionalintervalandtimeout.clickTargetdispatches a click to the active target.focusTargetmoves focus to the active target.
Same-runtime JavaScript configuration objects can use functions in actions and targetEvents. Step-level beforeEnter and beforeLeave, plus workflow-level onStart, onCancel, and onFinish, accept functions only and cannot be transported in 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.
Style Individual Steps
Use classNames on a workflow or step when one tour component needs a dedicated class.
const workflow = tour
.create("onboarding", {
classNames: { popover: "onboarding-popover" },
})
.step({
id: "danger-zone",
target: "#delete-account",
content: "This action cannot be undone.",
classNames: {
popover: "tour-danger",
advance: "tour-danger-button",
},
})
.build();
A step’s class replaces the workflow class for the same component. Classes can also change during a step through context.props.update({ classNames }).
Vanilla Adapter Functions
createGlowTour(options?: GlowTourOptions): Tour: Creates the tour controller.registerGlowTourElements(): void: Registers the GlowTour Custom Elements. Import from@glowhop/vanilla-tour/autoto register them as an import side effect.
Vanilla Custom Elements Reference
Use <glow-tour-default> for the standard composition. Build the composition manually when the popover structure itself needs to change.
glow-tour-default: Precomposed tour element. Public properties:tour: Tour | nullandidPrefix: string | undefined. The prefix can also be set with theid-prefixattribute.glow-tour-root: Root container. Public property:tour: Tour.glow-tour-overlay: Backdrop and highlighted cutout layer.glow-tour-popover: Dialog container for the current step.glow-tour-header: Renders the optional 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-previous-trigger: Previous control. Public property:disabled: boolean.
GLOW_TOUR_ELEMENT_NAMES contains the ten composition element names. The default pointer 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: Native Custom Elements with no Shadow DOM through
@glowhop/vanilla-tour. - 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.
GlowTour.js no longer publishes fixed minimum browser versions. It targets current evergreen browsers and requires ES2022 syntax, structuredClone, MutationObserver, requestAnimationFrame, and the inert attribute. The Vanilla adapter also requires Custom Elements.
Current CI runs Chromium, Firefox, and WebKit accessibility-tree tests. Screen-reader tests use VoiceOver with WebKit and Chromium, plus NVDA with Chromium and Firefox.
React, Vue, Solid, and Angular adapters verify SSR and hydration. Their production-app checks use Next.js, Nuxt, SolidStart, and Angular SSR respectively. Vanilla SSR and hydration aren’t applicable; we only verify DOM-free package imports.
Framework adapters and Core are ESM-only, @glowhop/styles-tour is CSS-only, and each package includes TypeScript declarations.
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
Changelog
v1.5.1 (09/29/2026)
- bugfixes
v1.5.0 (09/26/2026)
- feat: report a pending target on the popover, and let the cancel trigger’s text be set
- fix(core): follow a layout viewport grown past the device width
- feat(website): add GitHub stars button next to the install command
- feat(core): add hidePopover() and showPopover() to the tour
- feat(core): step the popover aside while the user scrolls
v1.4.0 (09/19/2026)
- Renamed
tour.run()totour.start()and replacedgoToStep(index)withgoTo(id). - Replaced
beforeAdvance,beforePrevious, andbeforeCancelwithbeforeEnterandbeforeLeave. - Reshaped step behavior and navigation controls, including
autoFocus,autoScroll,missingTarget, root-levelcontrols, and per-stepclassNames. - Added the
"detached"missing-target strategy, optional step titles, navigation aborts, and thestep:skipmonitoring event. - Updated JSON config to required format
version: "1.1"and renamedeventHandlerstotargetEvents. - Replaced the Vanilla
createDefaultTourElement()helper with the<glow-tour-default>Custom Element and renamed the back trigger to<glow-tour-previous-trigger>. - Expanded SSR, hydration, accessibility-tree, VoiceOver, and NVDA verification.







