Skip to content

Onboarding Playbook

Onboarding is the critical window between signup and success. Poor onboarding causes 40-60% user drop-off; 25% of users abandon an app after just one session. But well-designed onboarding increases retention by up to 50% and can boost revenue 34% through improved activation.

This playbook provides both human-readable guidance and machine-parseable specifications for AI agents implementing onboarding flows.


Quick Reference: Onboarding Specifications

Section titled “Quick Reference: Onboarding Specifications”
MetricDefinitionTarget
Activation Rate% of signups completing key action40%+ (top quartile: 2.3× median)
Time to First Value (TTFV)Time from signup to first successUnder 5 minutes ideal
Onboarding Completion% completing onboarding checklist19-27% average, 40%+ with good UX
Day 1 Retention% returning within 24 hours40%+
Week 1 Retention% returning within 7 days25%+
ElementSpecificationRationale
Number of steps3-5 initial (max 7)Cognitive load limits
Visual styleEmbedded in product (90% best practice)Feels native, not intrusive
Progress indicatorAlways visibleReduces abandonment
First taskCompletable in under 60 secondsQuick win builds momentum
RewardVisual celebration on completionDopamine anchors positive emotion
PhaseMaximum DurationUser Expectation
Signup to interface30 secondsImmediate access
Interface to first action60 secondsClear next step
First action to value3-5 minutes”Aha moment”
Complete basic setup10 minutesCore functionality working

rules:
# Value Delivery
- id: onboarding-quick-win
severity: error
check: "User can achieve meaningful success within first 5 clicks/actions"
context: "New user flow"
best_practice: "Products with quick win retain 80% more users"
- id: onboarding-ttfv
severity: warning
check: "Time to first value under 5 minutes for core use case"
context: "New user flow"
metric: "Every extra minute lowers conversion 3%"
- id: onboarding-no-dead-ends
severity: error
check: "Every screen has clear next action (no blank states without guidance)"
context: "Empty states, new user screens"
wcag: "3.2.1 AA (Predictable)"
# Progressive Disclosure
- id: onboarding-progressive
severity: warning
check: "Features revealed contextually as user needs them, not all at once"
context: "Feature introduction"
best_practice: "Progressive onboarding reduces overwhelm"
- id: onboarding-no-dump
severity: warning
check: "No more than 3-5 concepts introduced per screen"
context: "Tutorial, product tour"
cognitive: "Working memory limits (Miller's Law)"
# Checklist Design
- id: onboarding-checklist-length
severity: warning
check: "Onboarding checklist has 3-7 items maximum"
context: "Onboarding checklist component"
research: "3-5 steps is the sweet spot for activation"
- id: onboarding-progress-visible
severity: error
check: "Progress indicator visible throughout multi-step onboarding"
context: "Multi-step flows"
research: "Lack of progress indicator is top cause of abandonment"
- id: onboarding-skippable
severity: warning
check: "Non-essential onboarding steps can be skipped or deferred"
context: "Onboarding flows"
best_practice: "72% abandon if too many required steps"
# Empty States
- id: empty-state-guidance
severity: error
check: "Empty states include actionable guidance (not just 'No data')"
selector: ".empty-state, [data-empty], :empty-content"
research: "28% less confusion with guided empty states"
- id: empty-state-cta
severity: warning
check: "Empty states include clear call-to-action button"
selector: ".empty-state"
best_practice: "Direct path to creating first item"
# Accessibility
- id: onboarding-keyboard
severity: error
check: "All onboarding flows completable via keyboard"
wcag: "2.1.1 AA"
- id: onboarding-focus-management
severity: error
check: "Focus moves logically through onboarding steps"
wcag: "2.4.3 AA"
- id: tooltip-accessible
severity: error
check: "Tooltips and hotspots are keyboard accessible and screen reader announced"
wcag: "4.1.2 AA"

The activation event is the moment a user gets real value from your product—their “aha moment.”

EXAMPLES of activation events:
Slack: Send first message in a channel
Dropbox: Upload first file
Canva: Create first design
Notion: Create first page with content
Spotify: Play first song
Stripe: Process first payment
ANALYZE user behavior:
1. Identify users who became long-term customers
2. Find actions they took in first session
3. Look for correlation between actions and retention
4. The action with highest correlation = likely activation event
VALIDATE:
IF users who complete [action] retain at 2x+ rate:
THEN [action] is your activation event
FROM signup TO activation event:
1. List every step currently required
2. For each step, ask: "Can this be deferred or removed?"
3. For required steps, ask: "Can this be simplified?"
4. Goal: Minimum steps to meaningful value
EXAMPLE (before):
Signup → Verify email → Complete profile → Connect accounts →
Invite team → Create first project → Add first task
(7 steps, 15+ minutes)
EXAMPLE (after):
Signup → Create first task (on default project)
(2 steps, 2 minutes)
Everything else: progressive profiling later
<!-- Welcome survey to personalize onboarding -->
<div class="welcome-survey" role="form" aria-labelledby="survey-title">
<h2 id="survey-title">What brings you here today?</h2>
<p>We'll customize your experience based on your goals.</p>
<fieldset>
<legend class="sr-only">Select your primary goal</legend>
<label class="goal-option">
<input type="radio" name="goal" value="personal">
<span class="goal-card">
<span class="goal-icon">📝</span>
<span class="goal-title">Personal projects</span>
<span class="goal-desc">Organize my own tasks and notes</span>
</span>
</label>
<label class="goal-option">
<input type="radio" name="goal" value="team">
<span class="goal-card">
<span class="goal-icon">👥</span>
<span class="goal-title">Team collaboration</span>
<span class="goal-desc">Work together with my team</span>
</span>
</label>
<label class="goal-option">
<input type="radio" name="goal" value="client">
<span class="goal-card">
<span class="goal-icon">💼</span>
<span class="goal-title">Client work</span>
<span class="goal-desc">Manage projects for clients</span>
</span>
</label>
</fieldset>
<button type="submit">Continue</button>
<button type="button" class="skip-link">Skip for now</button>
</div>

Ask for information when it’s needed, not all upfront.

InformationWhen to AskWhy
EmailSignup (required)Account creation
NameFirst action or laterPersonalization
CompanyWhen inviting teamContext matters then
Role/TitleWhen showing relevant featuresPersonalize experience
PhoneWhen enabling 2FAClear purpose
PaymentWhen upgradingValue proven first
FOR each piece of information:
IF required for core functionality NOW:
ASK at signup
ELSE IF improves immediate experience significantly:
ASK after first success (with clear benefit stated)
ELSE IF needed for specific feature:
ASK when user accesses that feature
ELSE:
DON'T ASK (or make clearly optional)
RULE: Every field at signup costs 3-5% completion
// Instead of empty state, provide starting point
const defaultProject = {
name: "My First Project",
description: "Get started by adding your first task below",
tasks: [
{
title: "👋 Welcome! Click me to see how tasks work",
completed: false,
isOnboarding: true
},
{
title: "Try creating your own task using the button below",
completed: false,
isOnboarding: true
}
]
};
// Pre-populate based on user's stated goal
function getTemplateForGoal(goal) {
const templates = {
personal: {
name: "Personal Tasks",
tasks: ["Morning routine", "Weekly review", "Read 30 minutes"]
},
team: {
name: "Team Project",
tasks: ["Kickoff meeting", "Define goals", "Assign roles"]
},
client: {
name: "Client: [Name]",
tasks: ["Discovery call", "Send proposal", "Project kickoff"]
}
};
return templates[goal] || templates.personal;
}
<div class="onboarding-options">
<h3>Get started quickly</h3>
<div class="option-cards">
<button class="option-card" onclick="importData()">
<span class="option-icon">📥</span>
<span class="option-title">Import existing data</span>
<span class="option-desc">From CSV, Trello, Asana, or other tools</span>
</button>
<button class="option-card" onclick="useTemplate()">
<span class="option-icon">📋</span>
<span class="option-title">Start with a template</span>
<span class="option-desc">Pre-built projects for common use cases</span>
</button>
<button class="option-card" onclick="startBlank()">
<span class="option-icon"></span>
<span class="option-title">Start from scratch</span>
<span class="option-desc">Create your own structure</span>
</button>
</div>
</div>

Interactive Walkthroughs Over Static Tours

Section titled “Interactive Walkthroughs Over Static Tours”

Static product tours fail because users passively watch without learning. Interactive walkthroughs have users perform real actions.

ApproachRetentionEngagement
Video tutorial10-20%Passive
Static slideshow15-25%Passive
Interactive walkthrough50-70%Active
Guided real task70-85%Active + meaningful
<!-- Step 1: Highlight target element -->
<div class="walkthrough-overlay" aria-live="polite">
<div class="walkthrough-highlight"
style="--target-top: 100px; --target-left: 200px;
--target-width: 150px; --target-height: 40px;">
</div>
<div class="walkthrough-tooltip" role="dialog" aria-labelledby="step-title">
<div class="tooltip-content">
<span class="step-indicator">Step 1 of 4</span>
<h4 id="step-title">Create your first task</h4>
<p>Click the "Add Task" button to create something to track.</p>
</div>
<div class="tooltip-actions">
<button class="btn-secondary" onclick="skipWalkthrough()">
Skip tour
</button>
<button class="btn-primary" onclick="showMe()">
Show me
</button>
</div>
<div class="tooltip-arrow"></div>
</div>
</div>
class InteractiveWalkthrough {
constructor(steps) {
this.steps = steps;
this.currentStep = 0;
}
start() {
this.showStep(0);
}
showStep(index) {
const step = this.steps[index];
// Highlight target element
const target = document.querySelector(step.selector);
this.highlightElement(target);
// Show tooltip
this.showTooltip(step, target);
// Wait for user action (not just "Next" click)
if (step.waitForAction) {
target.addEventListener(step.actionType, () => {
this.completeStep(index);
}, { once: true });
}
}
completeStep(index) {
// Celebrate completion
this.showSuccess(this.steps[index].successMessage);
// Move to next step
if (index < this.steps.length - 1) {
setTimeout(() => this.showStep(index + 1), 1000);
} else {
this.completeWalkthrough();
}
}
}
// Example steps
const onboardingSteps = [
{
selector: '.add-task-button',
title: 'Create your first task',
description: 'Click here to add something to track.',
waitForAction: true,
actionType: 'click',
successMessage: 'Great! You created a task.'
},
{
selector: '.task-checkbox',
title: 'Complete your task',
description: 'Click the checkbox to mark it done.',
waitForAction: true,
actionType: 'change',
successMessage: 'Nice work! That\'s the basics.'
}
];
PRINCIPLE: Let users experiment without fear
IMPLEMENTATION:
- Undo available for all actions (Ctrl+Z, undo button)
- No irreversible steps during onboarding
- Sample/demo data clearly labeled
- "Reset to start" option available
- Confirmation before destructive actions
EXAMPLE dialog for risky action during onboarding:
"You're about to delete this project.
During your trial, you can always undo this.
[Cancel] [Delete (can undo)]"
<!-- Onboarding checklist with progress -->
<aside class="onboarding-checklist" aria-labelledby="checklist-title">
<header class="checklist-header">
<h3 id="checklist-title">Getting Started</h3>
<div class="progress-ring" role="progressbar"
aria-valuenow="2" aria-valuemin="0" aria-valuemax="5"
aria-label="2 of 5 steps complete">
<svg viewBox="0 0 36 36">
<circle class="progress-bg" cx="18" cy="18" r="16"/>
<circle class="progress-fill" cx="18" cy="18" r="16"
style="--progress: 40%"/>
</svg>
<span class="progress-text">2/5</span>
</div>
</header>
<ul class="checklist-items" role="list">
<li class="checklist-item completed">
<span class="item-icon" aria-hidden="true"></span>
<span class="item-text">Create your account</span>
</li>
<li class="checklist-item completed">
<span class="item-icon" aria-hidden="true"></span>
<span class="item-text">Add your first task</span>
</li>
<li class="checklist-item current" aria-current="step">
<span class="item-icon" aria-hidden="true"></span>
<span class="item-text">Invite a teammate</span>
<button class="item-action">Do this →</button>
</li>
<li class="checklist-item">
<span class="item-icon" aria-hidden="true"></span>
<span class="item-text">Connect your calendar</span>
</li>
<li class="checklist-item">
<span class="item-icon" aria-hidden="true"></span>
<span class="item-text">Complete your first project</span>
</li>
</ul>
<footer class="checklist-footer">
<p class="time-estimate">About 3 minutes remaining</p>
<button class="dismiss-checklist">Dismiss</button>
</footer>
</aside>
// Celebration on milestone completion
function celebrateCompletion(milestone) {
// Visual celebration (confetti, animation)
showConfetti();
// Toast notification
showToast({
type: 'success',
title: milestone.title,
message: milestone.message,
duration: 5000
});
// Update progress
updateProgress(milestone.step);
// Optional: Trigger reward
if (milestone.reward) {
showReward(milestone.reward);
}
}
const milestones = {
firstTask: {
title: '🎉 First task created!',
message: 'You\'re on your way. Keep going!',
step: 1
},
firstProject: {
title: '🚀 Project launched!',
message: 'You\'ve set up your first project.',
step: 3
},
onboardingComplete: {
title: '🏆 You\'re all set!',
message: 'You\'ve completed the basics. Time to get productive!',
step: 5,
reward: { type: 'badge', id: 'early-adopter' }
}
};

Empty states are prime onboarding real estate. Design them to guide, not frustrate.

<div class="empty-state" role="status">
<!-- Visual (illustration or icon) -->
<div class="empty-visual" aria-hidden="true">
<img src="/illustrations/empty-projects.svg" alt="">
</div>
<!-- Headline: What this area is for -->
<h3 class="empty-title">No projects yet</h3>
<!-- Description: Why it's empty + what to do -->
<p class="empty-description">
Projects help you organize related tasks.
Create your first project to get started.
</p>
<!-- Primary action -->
<button class="btn-primary empty-cta">
Create a project
</button>
<!-- Secondary options -->
<div class="empty-alternatives">
<a href="/templates">Browse templates</a>
<span aria-hidden="true">·</span>
<a href="/import">Import from another tool</a>
</div>
</div>
ElementGuidelineExample
VisualFriendly, not sadIllustration of opportunity, not emptiness
HeadlineState the purpose”No messages yet” not “Empty”
DescriptionExplain + guide”Messages from your team appear here. Start a conversation!”
CTAClear next action”Send your first message”
AlternativesOther paths forward”Or invite teammates first”

Rule of thumb: Two parts instruction, one part delight.

const emptyStates = {
// First-time empty (onboarding opportunity)
firstTime: {
visual: 'welcome-illustration',
title: 'Welcome to Projects!',
description: 'This is where you\'ll organize your work. Let\'s create your first project together.',
cta: { text: 'Create first project', action: 'startOnboarding' },
alternatives: ['Use a template', 'Watch a quick tour']
},
// Returning user empty (different context)
cleared: {
visual: 'success-illustration',
title: 'All caught up!',
description: 'You\'ve completed all your tasks. Nice work!',
cta: { text: 'Add new task', action: 'createTask' },
alternatives: ['Review completed', 'Take a break']
},
// Search empty (help them succeed)
noResults: {
visual: 'search-illustration',
title: 'No results found',
description: 'We couldn\'t find anything matching "{{query}}".',
cta: { text: 'Clear search', action: 'clearSearch' },
suggestions: ['Check your spelling', 'Try broader terms', 'Search in All Projects']
},
// Error empty (recovery path)
error: {
visual: 'error-illustration',
title: 'Something went wrong',
description: 'We couldn\'t load your projects. This is usually temporary.',
cta: { text: 'Try again', action: 'retry' },
alternatives: ['Check status page', 'Contact support']
}
};

<!-- Permission request with clear explanation -->
<div class="permission-request" role="alertdialog"
aria-labelledby="perm-title" aria-describedby="perm-desc">
<div class="permission-icon" aria-hidden="true">📍</div>
<h3 id="perm-title">Allow location access?</h3>
<p id="perm-desc">
We use your location to show nearby events and calculate travel time.
You can change this anytime in Settings.
</p>
<div class="permission-details">
<details>
<summary>How we use your data</summary>
<ul>
<li>Show events within your selected radius</li>
<li>Estimate travel time to venues</li>
<li>Never shared with third parties</li>
<li>Stored only on your device</li>
</ul>
</details>
</div>
<div class="permission-actions">
<button class="btn-secondary" onclick="denyPermission()">
Not now
</button>
<button class="btn-primary" onclick="requestPermission()">
Allow location
</button>
</div>
</div>
<aside class="trial-status" role="complementary" aria-label="Trial information">
<div class="trial-header">
<span class="trial-badge">Free Trial</span>
<span class="trial-days">12 days left</span>
</div>
<div class="trial-limits">
<div class="limit-item">
<span class="limit-label">Projects</span>
<span class="limit-value">2 of 3 used</span>
<progress value="2" max="3" aria-label="2 of 3 projects used"></progress>
</div>
<div class="limit-item">
<span class="limit-label">Team members</span>
<span class="limit-value">1 of 5 used</span>
<progress value="1" max="5" aria-label="1 of 5 team members used"></progress>
</div>
</div>
<a href="/pricing" class="trial-upgrade">
View plans & pricing →
</a>
</aside>
<section class="help-options" aria-labelledby="help-title">
<h3 id="help-title">Need help?</h3>
<div class="help-cards">
<a href="/docs" class="help-card">
<span class="help-icon">📚</span>
<span class="help-title">Documentation</span>
<span class="help-desc">Guides and tutorials</span>
</a>
<button onclick="openChat()" class="help-card">
<span class="help-icon">💬</span>
<span class="help-title">Chat with us</span>
<span class="help-desc">Usually replies in 5 min</span>
<span class="help-status online">Online now</span>
</button>
<a href="/community" class="help-card">
<span class="help-icon">👥</span>
<span class="help-title">Community</span>
<span class="help-desc">Ask other users</span>
</a>
<a href="mailto:support@example.com" class="help-card">
<span class="help-icon">📧</span>
<span class="help-title">Email support</span>
<span class="help-desc">For complex issues</span>
</a>
</div>
</section>

MetricFormulaBenchmark
Activation RateUsers completing activation event / Total signups25-30% average, 40%+ good
Time to First ValueMedian time from signup to activation eventUnder 5 min ideal
Checklist CompletionUsers completing all checklist items / Users who started19% average, 27%+ good
Day 1 RetentionUsers returning within 24 hours / Total signups40%+
Day 7 RetentionUsers returning within 7 days / Total signups20%+
Drop-off RateUsers abandoning at each step / Users starting stepIdentify problem steps
// Track onboarding funnel
const onboardingEvents = {
signup: 'onboarding_started',
profileComplete: 'onboarding_profile_complete',
firstAction: 'onboarding_first_action',
inviteTeam: 'onboarding_invite_sent',
complete: 'onboarding_complete',
skip: 'onboarding_skipped'
};
function trackOnboardingStep(step, metadata = {}) {
analytics.track(onboardingEvents[step], {
...metadata,
timeFromSignup: getTimeFromSignup(),
sessionNumber: getSessionNumber(),
platform: getPlatform(),
entryPoint: getEntryPoint()
});
}
// Track time to first value
function trackActivation() {
const signupTime = localStorage.getItem('signupTime');
const activationTime = Date.now();
const ttfv = activationTime - parseInt(signupTime);
analytics.track('user_activated', {
timeToFirstValue: ttfv,
timeToFirstValueMinutes: Math.round(ttfv / 60000),
activationAction: 'first_task_created'
});
}
// Customer Effort Score (CES) after onboarding
function showCESSurvey() {
showSurvey({
question: 'How easy was it to get started?',
scale: [1, 2, 3, 4, 5, 6, 7],
labels: {
1: 'Very difficult',
7: 'Very easy'
},
followUp: {
condition: score => score <= 4,
question: 'What made it difficult?'
}
});
}
// Product-Market Fit (PMF) signal
function showPMFSurvey() {
showSurvey({
question: 'How would you feel if you could no longer use this product?',
options: [
'Very disappointed',
'Somewhat disappointed',
'Not disappointed'
],
followUp: {
condition: answer => answer === 'Very disappointed',
question: 'What do you primarily use this product for?'
}
});
}

  • Activation event defined and instrumented
  • Minimum path to value mapped (under 5 steps)
  • Onboarding checklist designed (3-5 items)
  • Empty states designed with guidance
  • Progress indicators implemented
  • Skip/defer options available
  • Help paths accessible from any screen
  • All onboarding flows keyboard navigable
  • Focus management correct through steps
  • Tooltips/hotspots screen reader accessible
  • Progress announced to assistive technology
  • Skip links available for walkthroughs
  • Color not sole indicator of progress
  • Funnel tracking for each onboarding step
  • Time-to-first-value metric calculated
  • Drop-off points identified per step
  • A/B testing framework ready
  • CES survey implemented post-onboarding
  • Weekly review of drop-off metrics
  • Monthly cohort analysis
  • Quarterly user interviews on onboarding
  • A/B tests running on friction points

MistakeImpactFix
Feature tour before valueUsers don’t care about features yetShow value first, teach features contextually
Too many signup fields3-5% drop per fieldProgressive profiling—ask later
No progress indicatorUsers feel trapped, abandonAlways show steps remaining
Overwhelming first screenCognitive overloadProgressive disclosure, focus on one action
Generic empty statesNo guidance, users stuckActionable empty states with clear CTAs
Forced onboardingUsers resent lack of controlMake skippable, let users explore
No quick winUsers don’t feel successDesign for first success in under 60 seconds
Mobile afterthought50%+ users on mobileDesign mobile-first onboarding

OpenView’s 2024 SaaS benchmarks show top-quartile companies achieve 2.3× higher activation than median, and a Fairmarkit study found 25% increase in activation leads to 34% revenue boost.

The SaaS Product Metrics Benchmark Report 2024 found:

  • Average completion rate: 19.2%
  • FinTech highest: 24.5%
  • Companies using best practices: 40%+
  • Users completing checklists are 3× more likely to convert

A 2024 Amplitude study found cutting TTFV by 20% lifted ARR growth 18% for mid-market SaaS. Research shows every extra minute of time-to-value lowers conversion by 3%.

According to 2024 industry research:

  • 40-60% drop-off after signup with poor onboarding
  • 25% of users abandon after one session
  • 75% abandon within first week if struggling
  • 72% abandon if onboarding requires too many steps

UX Design Institute research indicates adaptive, AI-powered onboarding is becoming the norm, with personalized flows showing significantly higher activation rates than generic approaches.


Research & Benchmarks:

Patterns & Best Practices:

Tools & Examples: