Skip to content

Migrating to GlowTour.js 1.4

GlowTour.js 1.4 reshapes part of the public API before it is locked in. Old names are removed, not deprecated: TypeScript reports each removed name, and a JSON config using an old key is rejected as an unknown key. This page lists every change, grouped by area.

The fastest path is to upgrade every @glowhop/* package together, run your type checker, validate your JSON configs, and follow the sections that match the reported errors.

  • beforeAdvance(), beforePrevious(), and beforeCancel() are removed from the step builder, along with the BeforeActionStepContext and StepTransitionAction types.
  • beforeLeave() runs before any navigation away from the step: advance(), previous(), goTo(), or finishing the tour. It never runs on cancel.
  • beforeEnter() runs each time the step is entered, after its target is resolved and before the step is shown, so the props it sets are the first ones rendered.
  • Both hooks receive a StepHookContext: props, initialProps, target, signal, direction, and abort(), without navigation methods. The context no longer spreads the step props.
  • Step props are no longer reset when the tour enters a step. A value set with context.props.set() or context.props.update() is kept when the tour comes back to that step, until the workflow runs again. resetPropsOnEnter is removed.
// Before
.beforeAdvance((context) => save(context.data))
.beforeCancel(() => cleanup())
// After
.beforeLeave((context) => {
if (context.direction === "advance") save(context.props.get().data);
})
// ...and in the workflow options
{ onCancel: () => cleanup() }
  • Steps that relied on props being reset on every visit: reset them in beforeEnter:

    .beforeEnter(({ props, initialProps }) => props.set(initialProps))
  • resetPropsOnEnter: false: remove it, not resetting is now the default.

  • Cleanup that ran in beforeCancel: move it to the workflow onCancel option, which receives the current step in context.step, or listen to context.signal in a step action.

  • behavior is part of the dynamic step props. Read it with context.props.get().behavior and change it during the step with context.props.update({ behavior }).
  • WorkflowStepDefinition.behavior is removed: the step behavior, merged over the workflow defaults, is in props.behavior.
  • props.set() replaces behavior along with the other props.
  • allowScroll moves from the workflow options to behavior.allowScroll. It can be set per step and changed during a step, like allowInteraction. The workflow behavior still sets it for every step.
  • allowScroll in the workflow options or a JSON config: move it into behavior:

    // Before
    tour.create("onboarding", { allowScroll: false });
    // After
    tour.create("onboarding", { behavior: { allowScroll: false } });
  • Code reading definition.steps[index].behavior: read definition.steps[index].props.behavior.

  • props.set() calls that build the props from scratch: spread the current props to keep the behavior, or use props.update():

    props.set((current) => ({ ...current, content: "Saved." }));
    props.update({ content: "Saved." });
  • beforeEnter and beforeLeave receive abort(). It stops the navigation: the tour stays on its current step and emits no event.
  • A navigation now emits its events once it cannot be stopped anymore, in this order: step:skip for each step skipped by missingTarget: { strategy: "skip" }, step:leave for the step being left, then step:enter for the step shown.
  • tour:start is emitted once the first step’s beforeEnter has passed, instead of right after onStart.
  • A navigation over several skipped steps emits one step:leave, instead of one per skipped step.
  • Going back over skipped steps past the first step keeps the current step. It used to cancel the tour.
  • Analytics: handle the new step:skip event, or ignore it in a default branch. If you counted step:leave events to detect skips, count step:skip instead.
  • Code relying on tour:start right after onStart: move that code to onStart itself.
  • Code implementing StepHookContext by hand (tests, mocks): add an abort() method.
  • tour.run(workflow, options?) is renamed tour.start(workflow, options?). Its arguments and its promise are unchanged: it resolves once the first step is on screen.
  • The value returned by useGlowTour() (Angular: injectGlowTour()) exposes start instead of run.
1.3 1.4
tour.run(workflow) tour.start(workflow)
tour.run(workflow, { startAt }) tour.start(workflow, { startAt })
// Before
await tour.run(workflow);
// After
await tour.start(workflow);
  • Mocks or wrappers that implement the tour controller (tests, facades): rename their run method to start.
  • tour.goToStep(index) is removed. tour.goTo(id) replaces it and takes the step’s id, like startAt in start(). An index breaks as soon as steps are reordered or inserted.
  • tour.goTo(id) throws when no step has this id. It does nothing while a transition is in progress or when that step is already shown.
  • Step actions and target event handlers can call context.goTo(id). Like context.advance(), it stops the remaining actions of the step.
// Before
await tour.goToStep(2);
// After
await tour.goTo("billing");

Negative booleans become positive ones, and related behavior settings are grouped:

1.3 1.4
behavior.disableAutoFocus: true behavior.autoFocus: false
behavior.disableAutoScroll: true behavior.autoScroll: false
indicator.disabled indicator.hidden
popover.arrow.disabled popover.arrow.hidden
popover.arrow.disableAutoStyles: true popover.arrow.autoStyles: false
popover.keyboardShortcuts controls: { advance: { keys }, previous: { keys }, cancel: { keys } } (see Controls)
behavior.missingTargetStrategy, behavior.targetTimeout behavior.missingTarget: { strategy, timeout }

The same shapes apply to the workflow options, each step, and the JSON config.

autoFocus: false also does more than disableAutoFocus: true did. It used to keep focus only when it was already in the popover, and moved it onto the Advance button otherwise. It now never moves focus during the step: focus stays on the page when the step allows interaction, and stays on the body when a modal step makes the page inert. See Keeping focus where it is.

// Before
.step({
id: "report",
target: "#report",
title: "Report",
content: "Your latest report.",
popover: { arrow: { disabled: true }, keyboardShortcuts: { previous: [] } },
behavior: { disableAutoScroll: true, missingTargetStrategy: "wait", targetTimeout: 5000 },
})
// After
.step({
id: "report",
target: "#report",
title: "Report",
content: "Your latest report.",
popover: { arrow: { hidden: true } },
behavior: { autoScroll: false, missingTarget: { strategy: "wait", timeout: 5000 } },
controls: { previous: { keys: [] } },
})
  • The five popover button flags and popover.keyboardShortcuts are replaced by a controls option, next to popover and behavior. It holds one entry per command, advance, previous and cancel, each with a state, "enabled" (default) or "disabled", and the keys that run it.
  • A step’s controls override the workflow ones field by field: a step that sets only advance.state keeps the workflow’s advance.keys.
  • A disabled control blocks everything the tour UI offers for it: the button, its keys, and overlayClick.
  • Hiding a button is a styling choice: give it a class through classNames and hide that class in your CSS, see Hiding a control’s button. Like hideAdvanceButton, a hidden button keeps its keys; add state: "disabled" to block them too.
  • tour.advance(), tour.previous(), tour.cancel(), tour.goTo() and the step context are not affected by controls.
  • The cancellable start option is removed. To keep the user from ending a tour, disable its cancel control on the workflow: the Skip button is disabled, and Escape and overlayClick: "cancel" do nothing. Unlike cancellable: false, tour.cancel() and context.cancel() still cancel, and the Skip button stays rendered unless you hide it.
  • A step whose target disappears, with no step left to fall back to, now always cancels the tour. With cancellable: false it used to fail with a missing-target error.
  • The footer is always rendered, in the footer component and in the default tour component. To hide it, give it a class through classNames.footer.
1.3 1.4
popover.hideAdvanceButton: true classNames: { advance: "tour-hidden" }
popover.disableAdvanceButton: true controls: { advance: { state: "disabled" } }
popover.hidePreviousButton: true classNames: { previous: "tour-hidden" }
popover.disablePreviousButton: true controls: { previous: { state: "disabled" } }
popover.hideFooter: true classNames: { footer: "tour-hidden" }
popover.keyboardShortcuts: { advance: ["n"] } controls: { advance: { keys: ["n"] } }
cancellable: false controls: { cancel: { state: "disabled" } }, plus classNames: { cancel: "tour-hidden" } to hide the button
// Before
popover: { hideAdvanceButton: true },
// ...
props.update({ popover: { hideAdvanceButton: false } });
// After, with `[data-glow-tour-root] .tour-hidden { display: none; }` in your CSS
classNames: { advance: "tour-hidden" },
// ...
props.update({ classNames: { advance: [] } });
  • A step that hid the Next button to keep the user on it: also set controls: { advance: { state: "disabled" } }, so Enter, ArrowRight and overlayClick: "advance" cannot advance either, and set it back to "enabled" with the class.
  • A tour that relied on cancellable: false to ignore tour.cancel(): check the condition in your own code before calling it.
  • A footer that relied on hideFooter: classNames: { footer: "tour-hidden" } hides it with its three buttons. Their keys stay active unless their controls are disabled.
  • A workflow config carries version: "1.1", the version of the config format. A config without it is rejected.
  • Step keys follow the builder names:
1.3 1.4
eventHandlers targetEvents (entries keep event and action)
advanceAction, previousAction beforeLeave, branching on direction
cancelAction the workflow onCancel
resetPropsOnEnter removed
(none) beforeEnter
  • Types: EventHandlerConfig becomes TargetEventConfig, TransitionActionRef becomes StepHookActionRef, and the core EventHandler type becomes TargetEventHandler.
  • The step option and popover control changes above apply to the config as well, and the config rejects cancellable.
// Before
const config: WorkflowConfig = {
name: "onboarding",
steps: [
{
id: "profile",
target: "#profile",
title: "Profile",
content: "Fill in your profile.",
eventHandlers: [{ event: "click", action: { type: "focusTarget" } }],
advanceAction: () => saveDraft(),
},
],
};
// After
const config: WorkflowConfig = {
version: "1.1",
name: "onboarding",
steps: [
{
id: "profile",
target: "#profile",
title: "Profile",
content: "Fill in your profile.",
targetEvents: [{ event: "click", action: { type: "focusTarget" } }],
beforeLeave: ({ direction }) => {
if (direction === "advance") saveDraft();
},
},
],
};
  • Every adapter uses the same names, with the GlowTour prefix. React and Solid lose their unprefixed components. The GlowTour object keeps its compound syntax (<GlowTour.Root>), now also in Vue, but loses GlowTour.Default and GlowTour.BackTrigger.
  • The back trigger is now the previous trigger, like tour.previous(). Its default label is “Previous step”.
  • Vanilla renders the default tour with a <glow-tour-default> element instead of createDefaultTourElement().
  • React and Solid: GlowTourPopover, GlowTourPointer, and GlowTourFooter no longer accept as. They always render a <section>, a <div>, and a <footer>, the elements the tour binds its positioning, focus, and ARIA behavior to. On the footer, as never had an effect.
1.3 1.4
React / Solid Root, Popover, Header, Content, Footer, Overlay, Pointer GlowTourRoot, GlowTourPopover, GlowTourHeader, GlowTourContent, GlowTourFooter, GlowTourOverlay, GlowTourPointer
React / Solid AdvanceTrigger, CancelTrigger GlowTourAdvanceTrigger, GlowTourCancelTrigger
React / Solid GlowTour.BackTrigger GlowTour.PreviousTrigger
React / Solid DefaultTour, GlowTour.Default, DefaultTourProps GlowTourDefault, GlowTourDefaultProps
BackTrigger (React, Solid), GlowTourBackTrigger (Vue, Angular) GlowTourPreviousTrigger
<glow-tour-back-trigger> (Angular, Vanilla) <glow-tour-previous-trigger>
backLabel prop, Vanilla back-label attribute previousLabel, previous-label
Vanilla createDefaultTourElement(tour, { idPrefix }) <glow-tour-default> with tour and id-prefix
Vanilla VanillaGlowTour Tour
// Before
import { GlowTour } from "@glowhop/react-tour";
<GlowTour.Root tour={tour}>
<GlowTour.Popover>
<GlowTour.BackTrigger backLabel="Back" />
</GlowTour.Popover>
</GlowTour.Root>;
// After, with named components
import { GlowTourPopover, GlowTourPreviousTrigger, GlowTourRoot } from "@glowhop/react-tour";
<GlowTourRoot tour={tour}>
<GlowTourPopover>
<GlowTourPreviousTrigger previousLabel="Back" />
</GlowTourPopover>
</GlowTourRoot>;
// After, with the GlowTour object
import { GlowTour } from "@glowhop/react-tour";
<GlowTour.Root tour={tour}>
<GlowTour.Popover>
<GlowTour.PreviousTrigger previousLabel="Back" />
</GlowTour.Popover>
</GlowTour.Root>;
// Before
document.body.append(createDefaultTourElement(tour, { idPrefix: "help" }));
// After
const element = document.createElement("glow-tour-default");
element.idPrefix = "help";
element.tour = tour;
document.body.append(element);
  • as on the popover, pointer, or footer: remove it. Style the component with className (Solid: class) and style, or put your own element inside it.
  • Tests or styles matching the “Back step” label: match “Previous step”, or pass your own previousLabel.
  • The hook that reads the state inside GlowTourRoot is renamed, so that useGlowTour can become the main way to run a tour from a component.
  • React, Vue, and Solid: useTour() becomes useGlowTourContext(). It still works only inside GlowTourRoot and returns the same value.
  • Angular: injectGlowTour() becomes injectGlowTourContext(). The name injectGlowTour() now belongs to the new API below, which returns an object, not a state signal: code that still calls it inside glow-tour-root compiles but reads the wrong shape.
1.3 1.4
React / Vue / Solid useTour() useGlowTourContext()
Angular injectGlowTour() injectGlowTourContext()
// Before
import { useTour } from "@glowhop/react-tour";
const state = useTour();
// After
import { useGlowTourContext } from "@glowhop/react-tour";
const state = useGlowTourContext();
// Before
protected readonly state = injectGlowTour();
// After
protected readonly state = injectGlowTourContext();
  • State read outside GlowTourRoot with tour.state.subscribe: replace the subscription with useGlowTour(tour) (Angular: injectGlowTour(tour)), which returns each state field as a ref, accessor, or signal.

These additions need no change to existing code:

  • useGlowTour() (Angular: injectGlowTour()) runs a tour from a component. It returns the tour, its methods, and each state field in the framework’s reactive form. Pass an existing tour to share it between components.

  • context.props.update(patch) merges a partial change into the step props.

  • controls.cancel.state hides or disables the cancel button, with its keys and overlayClick: "cancel".

  • A step title is optional: without it, the header is omitted and the content names the dialog.

  • StepContext exposes initialProps and direction.