Feature: In-App Guided Onboarding with Driver.js
Project: FlowPOS Workspace
App: apps/frontend-pwa
Status: Proposed
Priority: High
Type: Frontend Feature
Affects: frontend-pwa only — no backend changes required (v1)
1. Overview
New merchants who sign up for FlowPOS often struggle to discover core workflows independently. This feature introduces an in-app guided onboarding system using Driver.js — a lightweight, MIT-licensed, TypeScript-native product tour library — to walk new users through the most critical areas of the POS: navigation, making a sale, managing products, and accessing reports.
The tour is triggered automatically on first login and can be re-launched manually from the UI at any time.
2. Goals
- Reduce time-to-first-sale for new merchants
- Reduce support tickets from new users who cannot find core features
- Increase feature discovery for key workflows (sales, inventory, customers, reports)
- Provide a role-aware experience (owner vs. cashier see different tours)
- Support Spanish (default) and English via existing i18n setup
3. Non-Goals (v1)
- No backend persistence of tour state in v1 — use
localStorageonly - No tour analytics or funnel tracking in v1
- No admin panel to manage tour content
- No tours for
landing-pageorweb-app - No tours for the React Native desktop app
4. Library: Driver.js
| Property | Value |
|---|---|
| Library | Driver.js |
| License | MIT — free for commercial SaaS, no restrictions |
| Bundle size | ~5kb gzipped |
| Dependencies | Zero |
| TypeScript | Native — built in TypeScript from the ground up |
| Framework | Framework-agnostic — works with React 19 / Vite 6 |
| Maintenance | Actively maintained (2026) |
| npm | https://www.npmjs.com/package/driver.js |
| Docs | https://driverjs.com/docs/installation |
| GitHub | https://github.com/kamranahmedse/driver.js |
Why Driver.js over Shepherd.js and Intro.js
- Shepherd.js and Intro.js are AGPL licensed — commercial SaaS use requires a paid license
- Driver.js is MIT — no license cost, no restrictions, fully compatible with FlowPOS as a commercial product
- Built in TypeScript natively — aligns with the project's TypeScript-first stack
- Zero dependencies — no version conflicts in the PNPM monorepo
- Simple flat API — easy for Cursor and Claude Code to generate and maintain tour steps
5. Scope
5.1 Files to Create
apps/frontend-pwa/src/
├── hooks/
│ └── useTour.ts # Core tour hook wrapping Driver.js
├── tours/
│ ├── index.ts # Tour registry / exports
│ ├── mainTour.ts # Main onboarding tour steps
│ ├── salesTour.ts # Sales workflow tour steps
│ └── types.ts # Tour-related TypeScript types
└── components/
└── TourLauncher.tsx # "Take the tour" button component
5.2 Files to Modify
apps/frontend-pwa/src/
├── main.tsx or App.tsx # Add TourProvider or trigger logic
├── pages/MainPage.tsx # Add data-tour attributes to key elements
├── components/
│ ├── TreeMenu.tsx # Add data-tour attributes to nav items
│ └── CollapsedTreeMenu.tsx # Add data-tour attributes
├── layouts/
│ └── AuthenticatedLayout.tsx # Add TourLauncher button
└── locales/
├── es.json # Add tour string translations
└── en.json # Add tour string translations
5.3 Installation
# From monorepo root
pnpm --filter frontend-pwa add driver.js
6. Architecture
6.1 Tour Hook — useTour.ts
The central hook that:
- Initializes the Driver.js instance
- Reads tour completion state from
localStorage - Exposes
startTour(tourId),resetTour(tourId),isTourCompleted(tourId) - Detects user role from
AuthContextto select the correct tour - Handles i18n by reading from
i18nextbefore building steps
// Signature
export function useTour(): {
startTour: (tourId: TourId) => void
resetTour: (tourId: TourId) => void
isTourCompleted: (tourId: TourId) => boolean
startOnboardingIfNew: () => void // Called on first login
}
6.2 Tour Registry — tours/index.ts
A typed registry mapping TourId to step arrays. Steps are functions that receive t (i18next translate function) so all content is translatable.
export type TourId = 'main-onboarding' | 'sales-workflow' | 'inventory-intro'
export const tourRegistry: Record<TourId, (t: TFunction) => DriveStep[]> = {
'main-onboarding': getMainOnboardingSteps,
'sales-workflow': getSalesWorkflowSteps,
'inventory-intro': getInventoryIntroSteps,
}
6.3 Data Attributes Strategy
All tour targets use data-tour attributes — never CSS class selectors or IDs. This decouples tours from styling and makes refactoring safe.
// In TreeMenu.tsx
<button data-tour="nav-sales">Ventas</button>
<button data-tour="nav-inventory">Inventario</button>
<button data-tour="nav-customers">Clientes</button>
<button data-tour="nav-reports">Reportes</button>
// In MainPage.tsx
<div data-tour="main-content-area">...</div>
<button data-tour="new-sale-button">Nueva Venta</button>
6.4 Tour Completion State — localStorage
const TOUR_STORAGE_KEY = 'flowpos:tours'
// Stored structure
{
"main-onboarding": { completed: true, completedAt: "2026-03-24T..." },
"sales-workflow": { completed: false }
}
6.5 First-Login Trigger
In AuthenticatedLayout.tsx or equivalent, after auth is confirmed:
const { startOnboardingIfNew } = useTour()
useEffect(() => {
startOnboardingIfNew()
}, [businessId]) // Scoped per business tenant
7. Tour Definitions (v1)
Tour 1: Main Onboarding — mainTour.ts
Triggered automatically on first login.
| Step | Target | Title | Description |
|---|---|---|---|
| 1 | [data-tour="nav-sales"] | Ventas | Desde aquí puedes iniciar una venta rápida |
| 2 | [data-tour="nav-inventory"] | Inventario | Administra tus productos y existencias |
| 3 | [data-tour="nav-customers"] | Clientes | Gestiona tu base de clientes |
| 4 | [data-tour="nav-reports"] | Reportes | Accede a tus reportes del negocio |
| 5 | [data-tour="new-sale-button"] | ¡Comienza! | Haz clic aquí para realizar tu primera venta |
Tour 2: Sales Workflow — salesTour.ts
Triggered manually or after completing Tour 1.
| Step | Target | Title | Description |
|---|---|---|---|
| 1 | [data-tour="product-search"] | Buscar producto | Escribe el nombre o escanea el código |
| 2 | [data-tour="cart-area"] | Carrito | Aquí aparecen los productos seleccionados |
| 3 | [data-tour="payment-button"] | Cobrar | Selecciona el método de pago y confirma |
| 4 | [data-tour="receipt-action"] | Comprobante | Imprime o envía el recibo al cliente |
8. Role-Aware Tours
The useTour hook reads the user role from AuthContext. Different roles see different tours:
| Role | Tour shown |
|---|---|
owner / admin | Full main onboarding (all 5 steps including reports) |
cashier | Shortened tour — sales + products only, no reports |
manager | Full tour |
9. i18n
All tour content is defined as translation keys, not hardcoded strings. Keys are added to both es.json and en.json.
// es.json
{
"tour": {
"main": {
"nav-sales": {
"title": "Ventas",
"description": "Desde aquí puedes iniciar una venta rápida."
},
"nav-inventory": {
"title": "Inventario",
"description": "Administra tus productos y existencias."
}
}
}
}
10. TourLauncher Component
A persistent help button added to AuthenticatedLayout that lets users re-launch any tour at any time.
// components/TourLauncher.tsx
// Renders a "?" button or "Ver tutorial" link
// Opens a small popover listing available tours
// Each tour has a "Reiniciar" (reset) option
11. Driver.js Configuration
import { driver } from 'driver.js'
import 'driver.js/dist/driver.css'
const driverObj = driver({
showProgress: true,
animate: true,
overlayColor: 'rgba(0,0,0,0.65)',
smoothScroll: true,
allowClose: true,
nextBtnText: 'Siguiente →',
prevBtnText: '← Anterior',
doneBtnText: '¡Listo!',
onDestroyStarted: () => {
markTourCompleted(tourId)
driverObj.destroy()
},
steps: tourSteps,
})
Styles are overridden via Tailwind CSS to match the FlowPOS design system. The default driver.css is imported and then selectively overridden using CSS variables.
12. Acceptance Criteria
- Driver.js is installed in
apps/frontend-pwavia PNPM - Main onboarding tour launches automatically on first login per business tenant
- Tour does not re-launch on subsequent logins once completed
- User can manually re-launch any tour from the
TourLaunchercomponent - User can skip or close the tour at any step
- Tour completion state is persisted in
localStoragescoped bybusinessId - All tour text uses i18n keys — no hardcoded strings
- Tour steps render correctly on mobile (PWA responsive behavior)
- Tour steps skip gracefully if a target element is not present in the DOM
- Role-aware logic shows the correct tour per user role
- Spanish (default) and English translations are complete
-
data-tourattributes are added toTreeMenu,MainPage, and sales form elements - No TypeScript errors introduced
- Existing tests pass without modification
13. Implementation Order
- Install
driver.jsvia PNPM - Add
data-tourattributes toTreeMenu,CollapsedTreeMenu,MainPage - Create
tours/folder withtypes.ts,index.ts,mainTour.ts,salesTour.ts - Create
useTour.tshook - Add i18n keys to
es.jsonanden.json - Create
TourLauncher.tsxcomponent - Wire up first-login trigger in
AuthenticatedLayout - Style overrides for Driver.js to match FlowPOS design system
- Test on mobile (PWA viewport)
14. Future Iterations (v2+)
- Persist tour state in backend per user (
users.tour_statecolumn) for cross-device consistency - Add tour completion analytics (step drop-off tracking) via PostHog or custom events
- Add tour for restaurant-specific workflows (tables, orders, kitchen)
- Add changelog announcements using Driver.js highlight mode for new features
- Admin panel to enable/disable tours per business tenant
- Onboarding checklist component linked to tour completion state
15. References
| Resource | Link |
|---|---|
| Driver.js Website | https://driverjs.com |
| Driver.js Docs | https://driverjs.com/docs/installation |
| Driver.js GitHub | https://github.com/kamranahmedse/driver.js |
| Driver.js npm | https://www.npmjs.com/package/driver.js |
| React 19 Docs | https://react.dev |
| i18next Docs | https://www.i18next.com |
| FlowPOS Frontend Prompt | project/frontend |
| FlowPOS Stack | project/Stack |