# Onboarding

> Use when designing a first run: activation path, progressive disclosure, empty-state coaching over tours, permission prompts, and the first success moment.

- Skill: `agentsorg/onboarding` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add agentsorg/onboarding`
- Raw SKILL.md: https://api.skillmd.com/api/skills/agentsorg/onboarding/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: agentsorg (https://skillmd.com/u/agentsorg)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/agentsorg/onboarding

---



# Designing a First Run

A first run has exactly one job: produce one real outcome in the user's own data, then get out of the way. Default posture — ship **no tour**, no welcome carousel, no interstitial video. Spend the entire budget removing steps between first paint and the first created object, and let the product's own empty screens do the teaching. Onboarding is a sequence, and it is measured in steps, not minutes. **Boundary with `ui-states`:** `ui-states` owns what an empty state *is* — its anatomy, its skeleton-versus-spinner call, its error and partial-data variants; this skill owns only the order a new account moves through and what that empty state should coach toward. If the question is "what should this empty screen look like", load `ui-states` instead. Whether the first success moment earns a flourish is `delight`'s call, not this skill's.

**Detect the first-run machinery the project already has before adding any.** Look for an existing completion flag (`onboarding_completed_at`, `hasOnboarded`), a feature-flag or entitlement service, a seeded-data path in migrations or fixtures, an existing checklist component, and — critically — an already-installed tour library (`shepherd.js`, `driver.js`, `intro.js`, `react-joyride`, `@reactour/tour`). If one is installed, someone already decided: work inside it or argue explicitly for removing it. Never stand up a second onboarding state machine beside an existing one, and never keep first-run state somewhere the rest of the app cannot read.

## Quick Reference

| Topic | Where |
| --- | --- |
| Once the product archetype is known — solo tool, team workspace, integration/data product, marketplace, consumer mobile — open this for its activation event, first step, seeding rule, and checklist shape | [first-run-patterns.md](references/first-run-patterns.md) |

## Decision Framework: name the activation event first

Before designing any screen, write one sentence: *"A new account is activated when they ______."* It must be a single observable event in the product's own data — a document created, a repo connected, a first message sent — never "finished onboarding" and never "viewed the dashboard". Everything in the first run either shortens the path to that event or is cut. If you cannot name the event, you are not ready to design the sequence; ask, and say why you are asking.

Then count the steps between first paint and that event. Every step is one of three kinds, and only the first is allowed to be mandatory:

1. **Load-bearing** — the product cannot function without it (connect a data source, name the workspace).
2. **Deferrable** — useful, but the product works without it (invite teammates, set a timezone). Move it into the product, triggered at the moment it matters.
3. **Educational** — it only tells the user something. Delete it, and make the interface say it instead.

## Core Principles

1. **Create the user's first object in one step; never ship a tour.** A single step that produces their first real item beats an 8-stop feature tour, because the tour teaches the product while the shortcut delivers value. Default tour length is `0 stops`. *Exception:* one coach mark — exactly one, dismissed permanently on first use — for an affordance with no discoverable trigger, such as a canvas app's right-click radial menu or a gesture-only action.

2. **Request every permission inside the click handler of the feature that needs it.** A cold `Notification.requestPermission()` or geolocation call at first paint spends the single prompt the browser grants you, and a denial is unrecoverable in-app — the user must dig into site settings to reverse it. Gate it behind the button that cannot work without it, and show your own explanatory pre-prompt first so a "no" costs you nothing. *Exception:* a managed deployment where permissions are provisioned by device policy.

3. **Progressive disclosure hides, it does not lock.** Advanced surfaces start collapsed or absent, but nothing a new user reaches is `disabled` with "available after setup". A locked control teaches helplessness; a hidden one teaches nothing and costs nothing. *Exception:* irreversible and destructive actions (delete workspace, rotate keys, transfer ownership) stay visible from minute one — hiding those is how people delete the wrong thing later.

4. **Seed sample data only if it is labeled and removable in one action.** Demo content the user cannot distinguish from their own breaks the only thing a first run must establish — that their work saved. Tag every seeded row (`is_sample: true`), render the label in the UI, and ship a single "Remove sample data" action that takes it all. *Exception:* a read-only sandbox or public demo account where every row is sample and nothing the user types persists.

5. **Recompute progress from real state, never from a counter.** A checklist that reads `3 of 5` must derive each item from the thing it claims — "invited a teammate" queries the members table — so that work done outside the checklist still ticks the box. A stored `step: 3` desynchronizes the moment anyone completes a task by another route, and a checklist that lies about what you already did is worse than no checklist. *Exception:* none; if a step cannot be derived from state, it is educational and should be deleted per the framework above.

6. **The first success moment shows the user's own content, not a confirmation.** `You're all set!` is a receipt for your process. Landing them inside the thing they just made — cursor placed, name editable — is the proof that the product works. *Exception:* flows whose outcome is genuinely invisible (a background import, a DNS change) get a status surface with a real ETA instead.

7. **Persist onboarding state server-side, keyed to the account.** `localStorage` alone replays the entire first run on a second device and on every private window, which is how users conclude their account did not save. Store the completion flag and each derived milestone with the account; treat client storage as a cache. *Exception:* local-first products with no account, where the device *is* the account.

8. **Every step is skippable and every skipped step is re-enterable.** A `Skip` control on each step, plus a permanent path back in (Settings → Setup, or the checklist itself) so skipping is not a one-way door. Users who skip everything and succeed anyway are the ones you were designing for. *Exception:* a load-bearing step per the framework — there is no `Skip` for choosing a workspace name.

9. **Never block first paint on onboarding.** Render the app, then layer the first-run affordance on top — `0` full-screen gates before the product is visible. A gate before anything is on screen gives the user nothing to be curious about and no reason to finish. *Exception:* legal gates that must precede any data processing, such as age verification or region consent.

10. **Instrument by step, and delete the biggest drop before adding anything.** Emit `step_viewed` and `step_completed` with the step id, so a drop is attributable to one step rather than to the whole flow; this is the one surface where the fix is almost always subtraction. *Exception:* a step where drop-off is the goal, such as a plan selector that filters out non-buyers early.

## Smell / Fix

| Smell | Fix |
| --- | --- |
| Multi-slide welcome carousel before the app renders | Render the app; put the one useful sentence in the empty state |
| `requestPermission()` on mount | Move into the click handler of the feature that needs it |
| "Available after setup" disabled controls | Hide them, or let them work |
| Sample data indistinguishable from real data | `is_sample` tag, visible label, one-action removal |
| `localStorage.setItem('onboarded', true)` as the only record | Account-scoped flag server-side |
| Checklist driven by an incrementing `step` counter | Derive each item from the state it asserts |
| A tour library in `package.json` and three coach marks | One coach mark maximum, and only for an undiscoverable affordance |
| "You're all set!" success screen | Land the user inside the object they just created |
| Progress bar that reaches 100% before the activation event | Bar ends at the activation event or does not exist |
| No skip link | Skip on every non-load-bearing step, re-enterable from Settings |

## Output format

When speccing a first run, produce one table plus the activation sentence above it:

`Activation event: a new account is activated when they ______.`

| Step | Kind | Blocking? | State it derives from | What happens if skipped |
| --- | --- | --- | --- | --- |

No `Educational` row survives into the final table — strike it through, with the interface change that replaces it.

## Checklist

- [ ] Activation event named as one observable event in the product's data
- [ ] Steps between first paint and activation counted; educational steps deleted
- [ ] Tour count is `0`, or exactly one coach mark with a stated justification
- [ ] Every permission requested at the moment of use, behind an explanatory pre-prompt
- [ ] Sample data labeled and removable in one action, or absent
- [ ] Checklist items derived from real state, not a stored counter
- [ ] First success moment lands in the user's own content
- [ ] Completion state stored with the account, not only in the browser
- [ ] Every non-load-bearing step skippable and re-enterable
- [ ] Empty-state *spec* handed to `ui-states`; flourish decision handed to `delight`

