A zero-intrusion guided tour engine for the web.
Build onboarding flows, error recovery wizards, interactive documentation, and contextual help — all without touching your application data or modifying your app code to accommodate the tour.
routePilot runs as a pure overlay. It observes your UI through CSS selectors, manages its own state machine, and tears down cleanly when the tour ends. Your app doesn't know a tour is running. The core engine (@routepilot/engine) is fully framework-agnostic — framework packages provide thin UI bindings on top.
@routepilot/engine— framework-agnostic core: state machine, DAG navigation, preparations, shared types, bridges, and CSS.@routepilot/react— React bindings: provider, overlay, hooks, and React-first integration helpers.@routepilot/angular— Angular bindings: service, overlay component, router adapter, directives, and Angular-first integration helpers.@routepilot/assistant— Ask the Tour retrieval core: BM25 index + tokenizer that maps free-text questions to the step that answers them. Framework-agnostic.@routepilot/assistant-react— React UI for the assistant: robot button and prompt bar that render in the tour tooltip footer.@routepilot/assistant-angular— Angular UI for the assistant: same button + prompt bar, wired through an injection token.
The canonical documentation lives here:
- Docs: routepilot.dev/docs
- Angular example: examples/angular
- Website/docs source: apps/site
- Live demo source: apps/demo
| Framework | Package | Status |
|---|---|---|
| React | @routepilot/react |
Stable |
| Angular | @routepilot/angular |
Stable |
| Vue | — | Planned |
| Svelte | — | Planned |
Most tour libraries force you to wrap components, inject props, or restructure your code around the tour. routePilot takes a different approach:
- Zero app code changes — Target elements with
data-tourattributes or any CSS selector. No wrapper components, no prop drilling. - Never touches real data — Scoped preparations seed demo state and revert it automatically. Your production data stays untouched.
- DAG-based navigation — Steps form a directed acyclic graph with branching, conditional transitions, and hub navigation. Not just linear sequences.
- Scoped lifecycle management — Preparations can be scoped to a single step, a group of steps, or the entire tour. Cleanup is automatic.
- DAG-based step navigation — branching, conditional transitions, hub navigation, step picker
- Scoped preparations — step, group, or tour-scoped setup/teardown with automatic cleanup
- Click gating — block progression until users interact with specific elements
- Text input validation — require typed input matching a string or regex before advancing
- Auto-advance — timer-based, condition-polling, or trigger-on-interaction
- Route-aware steps — auto-navigate, guard against drift, or pause on route mismatch
- Interactable system — open, close, and lock modals/drawers during specific steps
- Spotlight & highlight — configurable backdrop, multi-element highlighting, custom outlines
- Inline markup —
==shimmer==,==|pill|==, and==|~shimmer pill~|==in step text - Confetti — celebration effect on tour completion
- Keyboard navigation — Arrow keys, Escape to close
- Conditional steps —
whenguards to skip steps based on runtime state - Lifecycle hooks —
onEnter,onExit,onAdvance,onRetreatper step;onStart,onFinishper tour - Shared state — cross-step
Mapfor passing data between steps - Event system — emit/intercept custom events for cross-step communication
- Dynamic content — content factories that generate title/body/media from runtime context
- Ask the Tour — in-browser BM25 retrieval (no LLM, no backend) that jumps the user straight to the step that answers their question
- TypeScript-first — full type safety with IntelliSense
- Onboarding — Guide users through complex first-time setup flows with click gating and auto-advance.
- Contextual Help — Trigger helpful tips based on where the user is in their journey, with conditional step logic.
- Error Recovery — Step-by-step resolution flows that accumulate fixes across steps using tour-scoped preparations.
- Interactive Documentation — Turn settings pages and complex UIs into self-documenting tours that demonstrate features live.
npm install @routepilot/engine @routepilot/reactimport { GuidedTourProvider, GuidedTourOverlay } from '@routepilot/react';
import '@routepilot/engine/tour.css';
function App() {
return (
<GuidedTourProvider>
<GuidedTourOverlay />
<YourApp />
</GuidedTourProvider>
);
}npm install @routepilot/engine @routepilot/angular// app.config.ts
import { GUIDED_TOUR_CONFIG } from '@routepilot/angular';
export const appConfig: ApplicationConfig = {
providers: [
{ provide: GUIDED_TOUR_CONFIG, useValue: { tours: [myTour] } },
],
};// app.component.ts
import { GuidedTourOverlayComponent, TourRouterAdapterService } from '@routepilot/angular';
@Component({
imports: [GuidedTourOverlayComponent],
template: `<router-outlet /><rp-guided-tour-overlay />`,
})
export class AppComponent {
constructor(private _router: TourRouterAdapterService) {}
}No matter which framework you use, the integration sequence is the same:
- Install the framework package and import
@routepilot/engine/tour.css. - Mount the provider/service and the overlay once at the application root.
- Register your tours in the root config or a registry.
- Add stable selectors such as
data-tour="..."to the real UI you want to target. - Start tours from your own buttons, menus, docs pages, or help center entry points.
- Optionally inject custom bridges if demo state or tour events should use your own store/event bus instead of the default in-memory bridges.
- Provider / GuidedTourService — owns the state machine, runtime lifecycle, shared state, and services for the whole app.
- Overlay — renders the tooltip, backdrop, spotlight, navigation controls, and keyboard handling on top of your existing UI.
- Tours / Registry — lets you organize definitions centrally and start them by ID from anywhere in the app.
- Router adapter — enables route-aware steps, automatic navigation, and route guards.
- Stable selectors — give the engine durable DOM anchors such as
data-tour="...". - Launch trigger — connects your tours to real product entry points like onboarding, contextual help, or docs.
- demoBridge — only needed when preparations should read/write through your own fixture or demo-state layer.
- eventBridge — only needed when tour events should flow through your own event bus.
routePilot works best when the elements you target expose stable selectors:
<button data-tour="create-project-btn">Create project</button>
<aside data-tour="billing-sidebar">…</aside>
<input data-tour="invite-email-input" />By default, routePilot uses built-in in-memory bridges for demo state and tour events.
When you need to connect the engine to your own demo-store layer or event bus, pass
custom demoBridge / eventBridge implementations.
If you do not already know why you need a custom bridge, you probably do not need one. The defaults are the right choice for most tours.
- Use a custom demoBridge when preparations must read/write an existing fixture store, app state layer, shared mock backend, or persisted demo state.
- Use a custom eventBridge when tour events should integrate with an existing event bus instead of staying inside routePilot.
import type { DemoDataBridge, EventBridge } from '@routepilot/engine';
export const demoBridge: DemoDataBridge = {
set(scope, key, value) {
myDemoStore.set(scope.tourId, key, value);
},
remove(scope, key) {
myDemoStore.remove(scope.tourId, key);
},
clear(scope, namespace) {
myDemoStore.clear(scope.tourId, namespace);
},
read(scope, key) {
return myDemoStore.read(scope.tourId, key);
},
};
export const eventBridge: EventBridge = {
emit(scope, event, payload) {
myEventBus.emit(event, payload);
},
intercept(scope, event, handler, options) {
return myEventBus.on(event, handler, options);
},
clear(scope, event) {
myEventBus.clear(event);
},
enable() {},
disable() {},
};import { createStep } from '@routepilot/react'; // or '@routepilot/engine' — both work
const welcomeStep = createStep(
'welcome',
'/dashboard',
'[data-tour="main-cta"]',
'Welcome to the Platform',
'Click here to create your ==first project==.',
'bottom',
{
click: { all: ['[data-tour="main-cta"]'] },
autoAdvance: true,
},
);Tour definitions and step definitions are identical across frameworks. Only the setup and the way you access actions differ.
For full setup guides, API reference, bridge wiring, and framework-specific examples see the Documentation.
- Angular example app:
examples/angular - Angular tours folder:
examples/angular/src/app/tours - Website and docs source:
apps/site - Live demo app source:
apps/demo
routepilot/
packages/
engine/ # Framework-agnostic core engine — types, state machine, DAG, services, CSS (@routepilot/engine)
react/ # React bindings — Provider, Overlay, hooks (@routepilot/react) — re-exports everything from engine
angular/ # Angular bindings — components, services, router adapter (@routepilot/angular)
assistant/ # Ask the Tour core — BM25 index + tokenizer + types + shared CSS (@routepilot/assistant)
assistant-react/ # React UI for the assistant — button + prompt bar (@routepilot/assistant-react)
assistant-angular/ # Angular UI for the assistant — button + prompt bar (@routepilot/assistant-angular)
apps/
site/ # Landing page and documentation
demo/ # Live demo app (embedded in the site via iframe)
The core engine in @routepilot/engine (state machine, DAG builder, services, navigation adapter) is fully framework-agnostic. Each framework package provides the UI layer (overlay component, hooks/services, router integration) on top of the shared engine. @routepilot/react re-exports everything from @routepilot/engine, so React users only need a single import.
Setup and teardown logic with automatic lifecycle management:
preparations: [{
id: 'seed-data',
scope: 'step', // cleanup when leaving this step
factory: () => {
seedDemoData();
return () => clearDemoData();
},
}]Scopes: 'step' (cleanup on exit), 'group' (cleanup when all sharing steps exit), 'tour' (cleanup when tour ends).
Steps can branch conditionally:
transitions: [
{ target: 'advanced-flow', condition: (ctx) => isAdvancedUser(), label: 'Advanced' },
{ target: 'beginner-flow', label: 'Beginner' },
]For non-linear tours like FAQs, define a hub step that users return to between branches:
navigation: {
hubNodeId: 'faq-picker',
hubReturnLabel: 'Back to topics',
hubAction: 'goToHub',
}Block navigation until the user actually interacts:
config: {
click: { all: ['[data-tour="submit-btn"]'] },
textInput: { selector: 'input.name', match: /^.{2,}$/ },
autoAdvance: true,
}Highlight key terms in step body text:
==text==— shimmer highlight==|text|==— pill badge==|~text~|==— shimmer pill
Full API reference, framework-specific guides, and interactive examples:
# Install dependencies
npm install
# Start the dev server (site + demo)
npm run dev
# Run tests
npm test
# Build everything
npm run buildMIT