Skip to main content

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 localStorage only
  • No tour analytics or funnel tracking in v1
  • No admin panel to manage tour content
  • No tours for landing-page or web-app
  • No tours for the React Native desktop app

4. Library: Driver.js

PropertyValue
LibraryDriver.js
LicenseMIT — free for commercial SaaS, no restrictions
Bundle size~5kb gzipped
DependenciesZero
TypeScriptNative — built in TypeScript from the ground up
FrameworkFramework-agnostic — works with React 19 / Vite 6
MaintenanceActively maintained (2026)
npmhttps://www.npmjs.com/package/driver.js
Docshttps://driverjs.com/docs/installation
GitHubhttps://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 AuthContext to select the correct tour
  • Handles i18n by reading from i18next before 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.

StepTargetTitleDescription
1[data-tour="nav-sales"]VentasDesde aquí puedes iniciar una venta rápida
2[data-tour="nav-inventory"]InventarioAdministra tus productos y existencias
3[data-tour="nav-customers"]ClientesGestiona tu base de clientes
4[data-tour="nav-reports"]ReportesAccede 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.

StepTargetTitleDescription
1[data-tour="product-search"]Buscar productoEscribe el nombre o escanea el código
2[data-tour="cart-area"]CarritoAquí aparecen los productos seleccionados
3[data-tour="payment-button"]CobrarSelecciona el método de pago y confirma
4[data-tour="receipt-action"]ComprobanteImprime 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:

RoleTour shown
owner / adminFull main onboarding (all 5 steps including reports)
cashierShortened tour — sales + products only, no reports
managerFull 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-pwa via 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 TourLauncher component
  • User can skip or close the tour at any step
  • Tour completion state is persisted in localStorage scoped by businessId
  • 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-tour attributes are added to TreeMenu, MainPage, and sales form elements
  • No TypeScript errors introduced
  • Existing tests pass without modification

13. Implementation Order

  1. Install driver.js via PNPM
  2. Add data-tour attributes to TreeMenu, CollapsedTreeMenu, MainPage
  3. Create tours/ folder with types.ts, index.ts, mainTour.ts, salesTour.ts
  4. Create useTour.ts hook
  5. Add i18n keys to es.json and en.json
  6. Create TourLauncher.tsx component
  7. Wire up first-login trigger in AuthenticatedLayout
  8. Style overrides for Driver.js to match FlowPOS design system
  9. Test on mobile (PWA viewport)

14. Future Iterations (v2+)

  • Persist tour state in backend per user (users.tour_state column) 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

ResourceLink
Driver.js Websitehttps://driverjs.com
Driver.js Docshttps://driverjs.com/docs/installation
Driver.js GitHubhttps://github.com/kamranahmedse/driver.js
Driver.js npmhttps://www.npmjs.com/package/driver.js
React 19 Docshttps://react.dev
i18next Docshttps://www.i18next.com
FlowPOS Frontend Promptproject/frontend
FlowPOS Stackproject/Stack