Lightweight, zero-dependency page tour and user onboarding SDK for any website or web app. Drop in two files, define steps externally, and your interactive walkthrough is live.
- Zero dependencies - no jQuery, no React, no runtime bloat
- Framework agnostic - works with Vanilla JS, React, Vue, Angular, Svelte, or any stack
- CDN or npm - one
<script>tag or a standard ES module import - External step configuration - define tour steps in a separate JSON file or JS array; no SDK rebuild needed
- TypeScript support - ships with full type declarations (
QocialTourStep,QocialTourOptions) - Responsive & mobile-first - anchored card on desktop, bottom sheet on mobile (< 640px)
- Rich HTML descriptions - embed images, YouTube videos, or any HTML inside step tooltips
- Theming via CSS variables - override colors, radius, and card width without touching source
- Full lifecycle callbacks -
onStart,onStep,onComplete,onSkip,onStop - Overlay & spotlight - highlights the target element with a configurable overlay
- Auto placement - tooltip card flips automatically to stay inside the viewport
- Accessible - 44px touch targets, focus management, and keyboard navigation support
- Install
- Step Configuration
- API
- HTML Descriptions
- Responsive & Mobile Support
- Theming
- TypeScript
- Naming Convention
- Works With
- Local Development
- License
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@qocial/tour/dist/qocial-tour.min.css">
<script src="https://cdn.jsdelivr.net/npm/@qocial/tour/dist/qocial-tour.min.js"></script>
<script src="/tours/my-app-tour.js"></script>
<script>
// init() returns a Promise - handle errors with .catch()
QocialTour.init({ steps: window.MyAppTour, autoStart: true }).catch(console.error);
</script>npm install @qocial/tourimport QocialTour from "@qocial/tour";
import "@qocial/tour/css";
import steps from "./my-app-tour.json";
QocialTour.init({ steps, autoStart: true });Each step is an object in an array. Steps can be defined inline, loaded from a local JS file, or fetched from a remote JSON URL.
| Field | Required | Description |
|---|---|---|
step |
No | Step number (defaults to array index + 1) |
target |
Yes | CSS selector, or parent:#selector shorthand |
useParent |
No | Highlight the parent element of target |
title |
Yes | Plain text title shown in the tooltip card |
description |
No | Plain text or HTML (images, YouTube embeds, etc.) |
placement |
No | auto, top, bottom, left, right |
window.MyAppTour = [
{
step: 1,
target: "#profile",
useParent: true,
title: "Your profile",
description: "<p>Manage your account here.</p>"
},
{
step: 2,
target: "#help",
title: "Video walkthrough",
description: `
<p>Watch this quick demo:</p>
<div class="qocial-tour-media qocial-tour-media--video">
<iframe src="https://www.youtube.com/embed/VIDEO_ID" allowfullscreen></iframe>
</div>
`
}
];Load steps from a remote JSON URL (async fetch):
// init() is async when steps is a URL - always await it
await QocialTour.init({ steps: "/tours/my-app-tour.json", autoStart: true });// Always await init() - it may fetch a JSON file
await QocialTour.init({
steps: [...], // array or JSON URL string
autoStart: false,
showProgress: true,
overlay: true,
zIndex: 9999,
labels: { next: "Next", prev: "Back", skip: "Skip", finish: "Done" },
onStart({ total }) {},
onStep({ step, index, total, skipped, reason }) {},
onComplete() {},
onSkip() {},
onStop() {}, // called when stop() is invoked programmatically
});
// Pass the trigger element so focus returns to it when the tour closes
const btn = document.getElementById("start-tour");
btn.addEventListener("click", function () {
QocialTour.start(this);
});
QocialTour.next();
QocialTour.prev();
QocialTour.stop(); // silent stop, fires onStop
QocialTour.skip(); // fires onSkip
QocialTour.destroy(); // remove all DOM + event listeners
QocialTour.getState(); // { active, currentIndex, totalSteps, currentStep }The description field renders as HTML. All tour configs are developer-authored, so content is treated as trusted - the same as writing HTML directly in your page.
Use .qocial-tour-media.qocial-tour-media--video for responsive 16:9 YouTube embeds:
<div class="qocial-tour-media qocial-tour-media--video">
<iframe src="https://www.youtube.com/embed/VIDEO_ID"
title="Demo" frameborder="0" allowfullscreen></iframe>
</div>Note for CMS / user-supplied content: If descriptions come from a database, CMS, or user input, sanitize them with DOMPurify before passing to
init():import DOMPurify from "dompurify"; const steps = rawSteps.map(s => ({ ...s, description: DOMPurify.sanitize(s.description) })); await QocialTour.init({ steps });
- Desktop (≥ 640px): tooltip card anchors near the highlighted element and flips automatically to stay in the viewport.
- Mobile (< 640px): card becomes a bottom sheet with safe-area padding and 44px touch targets.
- Long HTML content scrolls inside
.qocial-tour-description.
Override CSS variables on your site - no SDK changes needed:
:root {
--qocial-tour-overlay: rgba(0, 0, 0, 0.55);
--qocial-tour-card-bg: #fff;
--qocial-tour-accent: #2563eb;
--qocial-tour-radius: 8px;
--qocial-tour-card-max-width: 420px;
}Note: The 640px mobile breakpoint inside
@mediaqueries is hardcoded in both the CSS and the JSMOBILE_BREAKPOINTconstant. Update both together if you need a different breakpoint.
The package ships dist/index.d.ts. Import types directly:
import QocialTour, { QocialTourStep, QocialTourOptions } from "@qocial/tour";All SDK identifiers use the qocial-tour- prefix to avoid conflicts with other packages:
| Surface | Format | Example |
|---|---|---|
| CSS classes | .qocial-tour-* |
.qocial-tour-card |
| CSS variables | --qocial-tour-* |
--qocial-tour-accent |
| Data attributes | data-qocial-tour-* |
data-qocial-tour-step |
| JS global | QocialTour |
window.QocialTour |
| npm package | @qocial/tour |
import QocialTour from "@qocial/tour" |
@qocial/tour is framework agnostic and works anywhere JavaScript runs:
- Vanilla JS - include via CDN, no build tools needed
- React - call
QocialTour.init()insideuseEffect - Vue - call inside
onMounted - Angular - call inside
ngAfterViewInit - Svelte - call inside
onMount - Next.js / Nuxt / Remix - use in client-side lifecycle hooks
- Any CMS or static site - WordPress, Webflow, Shopify, Ghost, etc.
npm install
npm run build
npx serve .
# Open http://localhost:3000/examples/MIT © Qocial