AI agent skill · by @lightdash
Add Onboarding Tour
Add a first-run guided tour, product walkthrough, coachmarks, or empty-state mock/example data to a Lightdash frontend feature. Use when the user wants to onboard users to a page, add a "Take the tour" flow, explain an unfamiliar UI, or show sample data on an empty page.
Archived 2026-10-23 · Source updated 2026-10-11. Check the original repository for the latest version.
SKILL.md instructions
# Add an Onboarding Tour
A zero-dependency, centralised kit for first-run onboarding in `packages/frontend`. Three reusable pieces do the heavy lifting; each feature only supplies its own steps, copy, anchors, and (optionally) example data.
## Building blocks
| Piece | Path | Job |
|---|---|---|
| `useGuidedTour` | `src/hooks/useGuidedTour.ts` | localStorage seen-flag, first-visit auto-open, replay. Returns `{ isOpen, startTour, closeTour }`. |
| `GuidedTour` | `src/components/common/GuidedTour` | Spotlight rendering. Dims the page, highlights a `data-tour` target, anchors a Next/Back/Skip card. `target: null` → centered card. |
| `useOnboardingMock` | `src/hooks/useOnboardingMock.ts` | A react-query `select` that swaps real data for deterministic mock rows while a flag is on. |
**Reference implementation:** the Reviews page — `src/ee/features/aiCopilot/components/Admin/settings/AiReviewsSettingsPage.tsx` (wiring), `AiAgentAdminReviewItemsTable.tsx` (mock rows), and `Admin/onboarding/` (the content). Read these first — copying them is the fastest path.
## Where content lives
**Kit = global, content = per-feature.** The three building blocks above are shared. Everything specific to one feature — its steps, copy, sample rows, and any onboarding-only visuals — goes in a co-located `onboarding/` folder next to the feature, with the same fixed layout every time:
```
<feature-dir>/onboarding/
index.ts public surface (re-exports)
steps.tsx TOUR_STEPS: GuidedTourStep[] (all the step copy)
exampleData.ts EXAMPLE_*, isExample*() (only if the feature shows mock rows)
<Visual>.tsx onboarding-only visuals, e.g. a diagram (+ .module.css)
```
The feature imports from `./onboarding`. Do **not** put feature content in a global folder, and do **not** inline steps or mock data in the page/table — keep components about rendering. Only re-export from `index.ts` what's consumed outside the folder (`ts-unused-exports` is enforced).
## Recipe
1. **Wire the tour state** in the feature page:
```tsx
const { isOpen, startTour, closeTour } = useGuidedTour({
storageKey: 'ld.<feature>.tour.v1',
});
```
2. **Define steps** in `onboarding/steps.tsx` as a module constant (they're static — no `useMemo` needed). Each `target` is a CSS selector resolved when the step is reached, or `null` for a centered explainer:
```tsx
export const TOUR_STEPS: GuidedTourStep[] = [
{ target: '[data-tour="<feature>-intro"]', title: '…', body: '…' },
{ target: '[data-tour="<feature>-row"]', title: '…', body: '…' },
{ target: null, title: '…', body: <SomeDiagram /> }, // centered
];
```
The page imports `{ TOUR_STEPS }` from `./onboarding` and passes it to `<GuidedTour>`.
3. **Add `data-tour` anchors** to the elements each step points at. For a **table row**, add it in the row props so the whole row is spotlit:
```tsx
mantineTableBodyRowProps: ({ row }) =>
row.index === 0 ? { 'data-tour': '<feature>-row' } : {},
```
4. **Render** the tour and a replay button:
```tsx
<Button variant="subtle" leftSection={<MantineIcon icon={IconRoute} />} onClick={startTour}>
Take the tour
</Button>
<GuidedTour steps={steps} opened={isOpen} onClose={closeTour} />
```
5. **(Optional) Deterministic example data** so a tour on an empty (or any) page always highlights the same rows. Put stable, clearly-labelled mock rows and the `isExample` helper in `onboarding/exampleData.ts`, and inject them via `select` while the tour is open:
```tsx
const select = useOnboardingMock(EXAMPLE_ROWS, isOpen);
const { data } = useThings(args, { select }); // hook must forward `select` to useQuery
```
Render example rows muted and inert (disabled actions, no navigation); mark them with an "Example" badge. Gate interactivity off a sentinel id (e.g. `id.startsWith('example:')`).
## Conventions
- **Zero dependencies.** No joyride/driver/intro.js. The spotlight is a `box-shadow: 0 0 0 9999px` dim — already handled by `GuidedTour`.
- **storageKey:** `ld.<feature>.tour.v<n>`. Bump the version to re-show the tour after a redesign.
- **Copy:** warm, natural, straight to the point. **No em dashes, no arrows.** Short titles.
- **Styling:** follow `frontend-style-guide` — no `style` prop (pass runtime geometry via `__vars`), CSS modules, theme tokens / `ldGray`/`ldDark`.
- **Mock rows must never look or act real:** muted, "Example" badge, disabled actions.
## Gotchas
- **Targets that render late** (data still loading): handled — `GuidedTour` polls for each step's element and shows a centered card until it appears. Do **not** filter steps at open time; that drops steps whose targets haven't rendered yet.
- **Determinism:** tie mock data to `isOpen` (tour running), not to emptiness, if you want the tour to highlight the same rows every run. Closing the tour flips back to real data.
- **`select` passthrough:** the data hook must accept and forward a `select` option to `useQuery` (see `useAiAgentAdminReviewItems`). Add it if missing.