Skip to content

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.

npm install @pantarey.io/ngx-guided-tour-lite

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.