npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

wts-tour

v1.0.0

Published

Framework-agnostic, dependency-free product tours and user onboarding for JavaScript and TypeScript, with accessible UI, Web Components, themes, async targets, and persistence.

Readme

wts-tour — Framework-Agnostic Product Tours

npm version npm downloads license

Build accessible product tours, guided walkthroughs, feature introductions, and user-onboarding flows with a dependency-free JavaScript and TypeScript library. wts-tour targets regular DOM elements, so the same API works with plain HTML, Web Components, Angular, React, Vue, Svelte, or any other frontend framework.

Why wts-tour?

  • Framework agnostic: one DOM-based controller for every frontend stack.
  • Dependency free: no framework runtime, positioning library, or CSS framework required.
  • Accessible by default: modal isolation, focus trapping, keyboard navigation, ARIA announcements, and reduced-motion support.
  • Modern onboarding UI: spotlight overlay, target arrow, progress, themes, responsive navigation, and configurable transitions.
  • Built for dynamic applications: lazy selectors, async target waiting, hidden-target recovery, and versioned progress persistence.
  • Developer friendly: TypeScript declarations, ESM and CommonJS builds, standalone CSS, Web Component ::part() hooks, and lifecycle events.

Install

npm install wts-tour

Controller API

import { WtsTour } from 'wts-tour';

const tour = new WtsTour(
  [
    {
      target: '#search',
      badge: 'Getting started',
      title: 'Search',
      description: 'Find anything from here.',
    },
    {
      target: document.querySelector('#profile')!,
      content: 'Manage your profile and preferences.',
      placement: 'bottom',
    },
  ],
  {
    positionPreference: 'right',
    theme: 'auto',
    showArrow: true,
    transition: 'slide',
    escapeToClose: true,
    persistence: {
      key: 'main-onboarding',
      version: 1,
      resume: true,
    },
    onFinish: ({ total }) => {
      console.log(`Completed ${total} steps`);
    },
  },
);

tour.on('change', ({ index }) => {
  console.log('Current step', index);
});

await tour.startAsync();

// Clean up when your page/component unmounts.
tour.destroy();

Targets may be CSS selectors, DOM Elements, or lazy functions returning an Element. Lazy targets are useful when a framework renders a target later. Missing, detached, and visually hidden targets are skipped during navigation. Counters, bullets, and the Finish button reflect only the currently available steps.

Prefer badge, title, and description for structured content. Their values are rendered as text and are safe for user-controlled strings. The optional content field accepts trusted HTML, a DOM Node, or a lazy content function.

Targets rendered later

The synchronous methods remain useful when every target is already mounted. Their async equivalents can wait for targets created by route changes, lazy components, or animations:

const tour = new WtsTour(
  [
    {
      target: '#lazy-panel',
      title: 'Your report is ready',
      waitForTarget: 5_000,
    },
  ],
  { waitForTarget: 2_000 },
);

await tour.startAsync();
await tour.nextAsync();
await tour.previousAsync();
await tour.goToAsync(2);

waitForTarget is measured in milliseconds. A step-level value overrides the tour default. Waiting is cancelled when the tour is closed, destroyed, or reconfigured.

Resume and completion

Persistence is opt-in and stores only the current index, completion state, version, and update time:

const tour = new WtsTour(steps, {
  persistence: {
    key: 'account-onboarding',
    version: 2,
    storage: 'local', // "local", "session", or a Storage-compatible object
    resume: true,
  },
});

await tour.startAsync(); // resumes automatically when resume is true
tour.completed;          // true after Finish
await tour.resumeAsync();
tour.resetProgress();

Changing version ignores progress written by an older tour definition.

Web Component

Importing the element entry registers <wts-tour>.

<script type="module">
  import 'wts-tour/element';
</script>

<button id="search">Search</button>
<button id="profile">Profile</button>

<wts-tour
  id="onboarding"
  position="bottom"
  theme="auto"
  transition="slide"
  wait-for-target="2000"
  persistence-key="main-onboarding"
  persistence-version="1"
  resume
  show-counter
  show-arrow
>
  <wts-tour-step
    target="#search"
    badge="Getting started"
    title="Search"
    description="Find anything from here."
  ></wts-tour-step>
  <wts-tour-step target="#profile">
    Manage your account here.
  </wts-tour-step>
</wts-tour>

<script type="module">
  await document.querySelector('#onboarding').startAsync();
</script>

You can also assign steps from JavaScript:

const element = document.querySelector('wts-tour');
element.steps = [
  { target: '#search', content: 'Search the site.' },
];
element.start();

The element emits composed DOM events named wts-tour-start, wts-tour-change, wts-tour-next, wts-tour-previous, wts-tour-skip, and wts-tour-finish.

Framework examples

Angular:

import {
  afterNextRender,
  Component,
  DestroyRef,
  inject,
} from '@angular/core';
import { WtsTour } from 'wts-tour';

@Component({ selector: 'app-page', templateUrl: './page.html' })
export class Page {
  private readonly tour = new WtsTour([
    { target: '#search', content: 'Search the site.' },
  ]);

  constructor() {
    afterNextRender(() => this.tour.start());
    inject(DestroyRef).onDestroy(() => this.tour.destroy());
  }
}

React:

import { useEffect } from 'react';
import { WtsTour } from 'wts-tour';

export function Page() {
  useEffect(() => {
    const tour = new WtsTour([
      { target: '#search', content: 'Search the site.' },
    ]);
    tour.start();
    return () => tour.destroy();
  }, []);

  return <button id="search">Search</button>;
}

Options

Common options include:

  • positionPreference: top, bottom, left, or right
  • gap, viewportPadding, and highlightPadding
  • keyboardNavigation and escapeToClose
  • modal, showOverlay, showBullet, and showSlideCounter
  • showProgress and showCloseButton
  • showArrow and arrowSize
  • theme: light, dark, auto, or minimal
  • transition: scale, slide, fade, or none
  • transitionDuration and transitionEasing
  • waitForTarget and per-step target wait overrides
  • persistence for versioned resume and completion state
  • hidePrevious, hideSkip, and hideDone
  • navigationPosition and skipButtonPosition
  • labels, or the legacy individual label fields
  • scrollBehavior, scrollLock, and customClass
  • lifecycle callbacks such as onStart, onChange, and onFinish
  • onError for errors thrown by lazy targets, content, labels, callbacks, or event listeners

Tours are modal by default: focus and pointer interaction stay in the tour while background content is inert. Set modal: false for a non-blocking tour. Arrow-key navigation does not intercept typing or selection inside form fields and editable content.

Options can be changed while a tour is active:

tour.updateOptions({
  portal: document.querySelector('#tour-layer')!,
  injectStyles: false,
  modal: false,
});

Portal changes within the same document, style injection, scroll locking, and modal isolation are applied immediately. Target and popup size changes are observed automatically.

You can force a measurement after an application-owned layout change with tour.refresh().

Motion

Step transitions use the browser's Web Animations API and automatically turn off when the user prefers reduced motion:

const tour = new WtsTour(steps, {
  transition: 'slide',
  transitionDuration: 240,
  transitionEasing: 'cubic-bezier(0.22, 1, 0.36, 1)',
  theme: 'auto',
  showArrow: true,
  arrowSize: 10,
  showProgress: true,
  showCloseButton: true,
});

The popup footer wraps into stable metadata and action groups. On narrow cards, the groups stack without horizontal scrolling; long step content scrolls independently so navigation remains visible.

See the exported WtsTourOptions and TourStep types for the complete API.

Styling and CSP

Default styles are injected into the tour root. Customize them with CSS variables on .wts-tour:

.wts-tour {
  --wts-tour-accent: #7c3aed;
  --wts-tour-popup-background: #111827;
  --wts-tour-popup-color: #f9fafb;
  --wts-tour-popup-border: rgba(255, 255, 255, .12);
  --wts-tour-overlay: rgba(0, 0, 0, .78);
  --wts-tour-highlight-filter: blur(20px);
  --wts-tour-popup-width: 360px;
  --wts-tour-arrow-background: #111827;
  --wts-tour-button-background: transparent;
  --wts-tour-primary-background: #7c3aed;
  --wts-tour-bullet-size: 8px;
  --wts-tour-progress-height: 3px;
  --wts-tour-duration: 240ms;
  --wts-tour-easing: cubic-bezier(0.22, 1, 0.36, 1);
}

--wts-tour-highlight-filter accepts any CSS filter value and defaults to blur(20px) for a soft spotlight. Set it to none for a crisp cutout, reduce the blur for a tighter glow, or combine filters such as blur(12px) brightness(1.08). Larger blur values can require more GPU work on low-powered devices.

The custom element exposes its Shadow DOM through ::part():

wts-tour::part(popup) {
  border-radius: 24px;
}

wts-tour::part(primary-button) {
  font-weight: 700;
}

wts-tour::part(arrow) {
  filter: drop-shadow(0 4px 5px rgb(0 0 0 / .18));
}

Available parts are portal, root, highlight, arrow, popup, content, badge, title, description, navigation, navigation-meta, navigation-actions, counter, button, skip-button, previous-button, primary-button, next-button, finish-button, bullets, bullet, progress, progress-bar, and close-button.

For a strict Content Security Policy, disable inline style injection and import the stylesheet:

import 'wts-tour/styles.css';

const tour = new WtsTour(steps, { injectStyles: false });

Browser and SSR behavior

The package is safe to import during server-side rendering. Create and start a tour only after the browser DOM has mounted. The distributed JavaScript targets modern evergreen browsers and ships ESM, CommonJS, and TypeScript declarations.

Development

npm test
npm run check
npx playwright install
npm run test:browser

The browser suite runs the built package in Chromium, Firefox, and WebKit and checks responsive overflow, arrow rendering, delayed targets, and persisted resume/completion.

See MIGRATION.md when upgrading from the Angular-only 0.x package.