ngx-guided-tour-lite: A Zero-Dependency Angular Guided Tour Library
ngx-guided-tour-lite is an Angular guided tour library built for JSON-driven onboarding flows. Once tours need to be generated by AI, loaded from the backend, and continue across async UI transitions, static in-code tour setups stop being enough.
Why JSON-Driven Angular Guided Tours Need More
Pantarey uses onboarding tours heavily. Tours are part of the product, part of the marketplace application templates, and part of the AI-assisted setup flow. Three requirements pushed us beyond what existing tour libraries offer:
Pure JSON support. Tours need to be generated by AI and stored in a database. That means the entire tour config, including step actions, conditions, and translations, must be expressible as a plain JSON object or string. No callbacks in the config, no imperative setup code. A backend or an LLM produces JSON, the frontend consumes it.
Backend-loadable for application templates. Pantarey has a marketplace where users import pre-built process templates ("applications"). Each application can ship its own guided tour as part of the template package. The tour config is loaded at runtime from the backend, not compiled into the frontend bundle. Existing libraries assume tours are defined statically in component code.
Advanced step logic. Real onboarding flows need conditional steps (show step A or B depending on DOM state), gate steps that wait for lazy-loaded content to appear, pause and resume across route changes, and typewriter fill that types into form fields while dispatching Angular-compatible input events. These are not edge cases. They are the default for any non-trivial onboarding.
Existing libraries kept requiring workarounds for each of these. Instead of layering more adapter code on top, we decided to build a purpose-built Angular service.
The Solution: ngx-guided-tour-lite
@pantarey.io/ngx-guided-tour-lite is a single Angular service with embedded CSS. No external dependencies beyond @angular/core.
The entire library is one injectable service (GuidedTourService) provided in root. No module imports, no component declarations, no stylesheet imports.
Minimal Example
import { Component, inject } from '@angular/core';
import { GuidedTourService } from '@pantarey.io/ngx-guided-tour-lite';
@Component({ ... })
export class DashboardComponent {
private tour = inject(GuidedTourService);
startOnboarding(): void {
this.tour.startTour({
options: {
overlayOpacity: 0.6,
showProgress: true,
locale: 'en',
},
steps: [
{
selector: '[data-tour="sidebar-menu"]',
side: 'right',
text: {
en: { title: 'Navigation', description: 'Use the sidebar to switch between modules.' },
de: { title: 'Navigation', description: 'Über die Seitenleiste zwischen Modulen wechseln.' },
},
},
{
selector: '[data-tour="create-button"]',
side: 'bottom',
text: {
en: { title: 'Create', description: 'Start a new process from here.' },
},
},
],
onComplete: () => console.log('Tour finished'),
});
}
}
No extra CSS import. The service injects a <style> block into the document head on first use and reuses it for all subsequent tours.
JSON-Driven Configuration
startTour() accepts either a GuidedTourConfig object or a raw JSON string. The string is parsed internally.
// Object — standard usage
this.tour.startTour({ steps: [...] });
// JSON string — loaded from API or database
const tourJson = await this.http.get<string>('/api/tours/onboarding').toPromise();
this.tour.startTour(tourJson);
This is one of the core design decisions behind the library. Tours can be stored in a database, generated by AI, loaded by marketplace applications at runtime, and deployed without a frontend rebuild. The onComplete callback is only available in the object variant since JSON cannot serialize functions.
Step Actions
The default info action shows a popover with Next/Back/Done buttons. Four additional actions handle the scenarios where vanilla tour libraries break down.
| Action | Behavior |
|---|---|
info |
Standard step. Popover with navigation buttons. |
gate |
Waits for the user to click the highlighted element, then polls for a gateSelector to appear before advancing. |
pauseAndResume |
Pauses the tour on click. Polls for a resumeSelector in the background. Resumes remaining steps when the selector appears. |
typewriterFill |
Types text into form fields character by character, dispatching input events. Auto-advances after completion. |
destroyOnClick |
Ends the tour when the user clicks the highlighted element. |
Gate Steps
Gate steps solve the tab-switching and lazy-loading problem. The tour highlights a clickable element (e.g. a tab header), removes the Next button, and waits. When the user clicks, the overlay disappears and the tour polls for the gateSelector, an element that only exists after the target view has loaded.
{
selector: '[data-tour="settings-tab"]',
action: 'gate',
gateSelector: '[data-tour="settings-form"]',
gateTimeout: 8000,
text: {
en: { title: 'Open Settings', description: 'Click the Settings tab to continue.' },
},
}
If the gate target is not found within gateTimeout (default: 8 seconds), the tour is destroyed. No hanging UI, no stale overlays.
Pause & Resume
Some onboarding flows require the user to leave the current page entirely. A pauseAndResume step handles this: the tour cleans up all DOM elements on click, then polls for the resumeSelector in the background. When the element appears, even after a full route change, the tour resumes with the remaining steps.
{
selector: '[data-tour="open-editor"]',
action: 'pauseAndResume',
resumeSelector: '[data-tour="editor-canvas"]',
resumeTimeout: 120000,
text: {
en: { title: 'Open the Editor', description: 'Click to open the process editor. The tour continues there.' },
},
}
The polling interval is 500 ms with a configurable timeout (default: 2 minutes). The service stores the remaining step config internally and calls startTour() again once the resume target is found.
Typewriter Fill
Typewriter steps type text into form fields one character at a time. Each character dispatches an input event with bubbles: true, so Angular reactive forms and template-driven forms register the value change.
{
selector: '[data-tour="name-field"]',
action: 'typewriterFill',
typewriterFill: [
{
selector: '[data-tour="name-field"] input',
text: 'My First Process',
charDelayMs: 35,
startDelayMs: 300,
},
{
selector: '[data-tour="description-field"] textarea',
text: 'Automated onboarding workflow',
charDelayMs: 30,
},
],
text: {
en: { title: 'Fill in Details', description: 'Watch as the fields are filled automatically.' },
},
}
Multiple fields can be filled sequentially within a single step. After the last character is typed, the tour waits 800 ms and then advances to the next step.
Runtime Conditions
Steps can adapt to the current DOM state using the condition property. The condition is evaluated when the step is about to render, not at tour start time.
{
selector: '[data-tour="save-button"]',
condition: {
if: { visible: '[data-tour="unsaved-indicator"]' },
then: {
text: {
en: { title: 'Save Changes', description: 'You have unsaved work. Click Save to continue.' },
},
},
else: {
text: {
en: { title: 'Already Saved', description: 'Everything is saved. Click Next.' },
},
},
},
}
This replaces the pattern of branching tour logic in component code.
Mobile Steps
The config accepts a mobileSteps array. When the viewport width is 600 px or less, the mobile steps are used instead of the default steps. This is not about hiding steps on small screens. It is a separate step sequence optimized for the mobile layout.
{
steps: [
{ selector: '[data-tour="sidebar"]', side: 'right', text: { en: { title: 'Sidebar', description: '...' } } },
],
mobileSteps: [
{ selector: '[data-tour="mobile-menu"]', side: 'bottom', text: { en: { title: 'Menu', description: '...' } } },
],
}
Smart Positioning
The service uses a requestAnimationFrame loop to watch the highlighted element's position. If an accordion opens, a toolbar loads additional items, or a CSS animation shifts layout, the overlay and popover reposition automatically. Before showing a step, the service also waits for the element's bounding rect to stabilize. This prevents the popover from appearing mid-animation.
Auto-flip is built in. If the preferred side does not fit in the viewport, the popover tries the opposite side, then the remaining two sides. Final clamping ensures the popover never overflows the screen.
Theming
All visual properties are exposed as CSS custom properties. Override them on :root or any parent element:
:root {
--guided-tour-primary: #3b82f6;
--guided-tour-bg: #1a1a2e;
--guided-tour-text: #fff;
--guided-tour-text-secondary: rgba(255, 255, 255, 0.78);
--guided-tour-text-muted: rgba(255, 255, 255, 0.35);
--guided-tour-border: rgba(255, 255, 255, 0.1);
--guided-tour-divider: rgba(255, 255, 255, 0.06);
--guided-tour-arrow-bg: #1a1a2e;
--guided-tour-z-index: 10000;
--guided-tour-max-width: 400px;
--guided-tour-radius: 16px;
--guided-tour-font: inherit;
}
The default design uses a dark glass-morphism popover with blur, gradients, and smooth transitions. Button labels are overridable via options.labels for any language.
Multi-Language
Step text is a locale map. The service resolves the current locale from options.locale, falls back to the base language code (de-DE to de), and finally falls back to en.
text: {
en: { title: 'Welcome', description: 'Start here.' },
de: { title: 'Willkommen', description: 'Hier starten.' },
fr: { title: 'Bienvenue', description: 'Commencez ici.' },
}
No external i18n library required. No translation keys. The text is self-contained in the tour config.
What Ships
The entire library is a single @Injectable service with ~380 lines of TypeScript, plus the embedded CSS string. No components, no modules, no external stylesheets.
| Feature | Details |
|---|---|
| Dependencies | @angular/core only |
| Bundle size | ~8 KB minified |
| Gate steps | Wait for click, then poll for lazy-loaded content |
| Pause and resume | Survives route changes, polls in background |
| Typewriter fill | Types into form fields with input event dispatch |
| Runtime conditions | Conditional step logic based on DOM state |
| Mobile steps | Separate step array for viewports under 600 px |
| JSON string input | startTour() accepts raw JSON strings directly |
| Theming | 12 CSS custom properties |
| Multi-language | Built-in locale map with fallback chain |
Conclusion
The library covers the async UI transitions that break generic tour libraries: gate steps for lazy content, pause and resume across routes, and typewriter fill for form demos. Pure JSON config means tours can be generated by AI and loaded from the backend. Zero dependencies keep the library lightweight.
This library is used in production at Pantarey for all onboarding tours across tenants.