mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-17 16:46:31 +03:00
The public repo keeps the OSS promise surface: skill, CLI, extension, tests, and the provider build. The site, labs, concept/composition catalogs, image pipeline, Cloudflare functions, and authoring guide move to pbakaus/impeccable-site. concept-seed tests run against a synthetic fixture catalog; the plugin icon and skill categories moved in-repo; build validation narrows to README prose and non-site counts; release notes read from a sibling impeccable-site checkout. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
600 lines
29 KiB
HTML
600 lines
29 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>/impeccable onboard | Impeccable</title>
|
||
<meta name="description" content="Design first-run experiences, empty states, and paths to value.">
|
||
<meta name="theme-color" content="#fafafa">
|
||
<link rel="canonical" href="https://impeccable.style/docs/onboard">
|
||
<link rel="icon" type="image/svg+xml" href="../favicon.svg">
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400;0,600;1,400&family=Instrument+Sans:wght@400;500;600;700&family=Space+Grotesk:wght@400;500;600&display=swap" rel="stylesheet">
|
||
<link rel="stylesheet" href="../css/sub-pages.css">
|
||
|
||
</head>
|
||
<body class="sub-page skills-layout-page">
|
||
<a href="#main" class="skip-link">Skip to content</a>
|
||
<!-- site-header v1 -->
|
||
<header class="site-header" data-site-header>
|
||
<a href="/" class="site-header-brand" aria-label="Impeccable home">
|
||
<svg class="site-header-brand-logo" viewBox="0 0 32 32" aria-hidden="true"><rect width="32" height="32" rx="6" fill="#1a1a1a"/><text x="16" y="24" font-family="system-ui, -apple-system, sans-serif" font-size="22" font-weight="500" fill="#f5f3ef" text-anchor="middle">/</text></svg>
|
||
<span class="site-header-brand-name">Impeccable</span>
|
||
</a>
|
||
<div class="site-header-right">
|
||
<nav class="site-header-nav" aria-label="Primary">
|
||
<a href="/" data-nav="home">Home</a>
|
||
<a href="/designing" data-nav="designing">Designing</a>
|
||
<a href="/docs" data-nav="docs" aria-current="page">Docs</a>
|
||
<a href="/slop" data-nav="slop">Slop</a>
|
||
<a href="/live-mode" data-nav="live">Live</a>
|
||
</nav>
|
||
<a href="https://github.com/pbakaus/impeccable" class="site-header-github" target="_blank" rel="noopener" aria-label="Impeccable on GitHub, 21k stars">
|
||
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"/></svg>
|
||
<span class="site-header-github-label">21k</span>
|
||
<svg class="site-header-github-star" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2l2.76 6.36L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l7.24-.91L12 2z"/></svg>
|
||
</a>
|
||
</div>
|
||
</header>
|
||
<main id="main">
|
||
|
||
<div class="skills-layout">
|
||
|
||
<aside class="skills-sidebar" aria-label="Documentation">
|
||
<button class="skills-sidebar-toggle" type="button" aria-expanded="false" aria-controls="skills-sidebar-inner">
|
||
<span class="skills-sidebar-toggle-label">/onboard</span>
|
||
<svg class="skills-sidebar-toggle-chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true"><path d="M6 9l6 6 6-6"/></svg>
|
||
</button>
|
||
<div class="skills-sidebar-inner" id="skills-sidebar-inner">
|
||
<p class="skills-sidebar-label">Docs</p>
|
||
|
||
<div class="skills-sidebar-group" data-category="tutorials">
|
||
<p class="skills-sidebar-group-title">Tutorials</p>
|
||
<ul class="skills-sidebar-list">
|
||
<li><a href="/tutorials/getting-started">Getting started</a></li>
|
||
<li><a href="/tutorials/iterate-live">Iterate on UI with Live Mode</a></li>
|
||
<li><a href="/tutorials/brand-vs-product">Brand vs product, pick a register</a></li>
|
||
<li><a href="/tutorials/critique-with-overlay">Critique with the visual overlay</a></li>
|
||
</ul>
|
||
</div>
|
||
<hr class="skills-sidebar-divider">
|
||
|
||
<div class="skills-sidebar-group" data-category="create">
|
||
<p class="skills-sidebar-group-title">Create</p>
|
||
<ul class="skills-sidebar-list">
|
||
<li><a href="/docs/impeccable">/impeccable</a></li>
|
||
<li><a href="/docs/craft">craft</a></li>
|
||
<li><a href="/docs/shape">shape</a></li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="skills-sidebar-group" data-category="evaluate">
|
||
<p class="skills-sidebar-group-title">Evaluate</p>
|
||
<ul class="skills-sidebar-list">
|
||
<li><a href="/docs/audit">audit</a></li>
|
||
<li><a href="/docs/critique">critique</a></li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="skills-sidebar-group" data-category="refine">
|
||
<p class="skills-sidebar-group-title">Refine</p>
|
||
<ul class="skills-sidebar-list">
|
||
<li><a href="/docs/animate">animate</a></li>
|
||
<li><a href="/docs/bolder">bolder</a></li>
|
||
<li><a href="/docs/colorize">colorize</a></li>
|
||
<li><a href="/docs/delight">delight</a></li>
|
||
<li><a href="/docs/layout">layout</a></li>
|
||
<li><a href="/docs/overdrive">overdrive</a></li>
|
||
<li><a href="/docs/quieter">quieter</a></li>
|
||
<li><a href="/docs/typeset">typeset</a></li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="skills-sidebar-group" data-category="simplify">
|
||
<p class="skills-sidebar-group-title">Simplify</p>
|
||
<ul class="skills-sidebar-list">
|
||
<li><a href="/docs/adapt">adapt</a></li>
|
||
<li><a href="/docs/clarify">clarify</a></li>
|
||
<li><a href="/docs/distill">distill</a></li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="skills-sidebar-group" data-category="harden">
|
||
<p class="skills-sidebar-group-title">Harden</p>
|
||
<ul class="skills-sidebar-list">
|
||
<li><a href="/docs/harden">harden</a></li>
|
||
<li><a href="/docs/onboard" aria-current="page">onboard</a></li>
|
||
<li><a href="/docs/optimize">optimize</a></li>
|
||
<li><a href="/docs/polish">polish</a></li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="skills-sidebar-group" data-category="system">
|
||
<p class="skills-sidebar-group-title">System</p>
|
||
<ul class="skills-sidebar-list">
|
||
<li><a href="/docs/document">document</a></li>
|
||
<li><a href="/docs/extract">extract</a></li>
|
||
<li><a href="/docs/live">live</a></li>
|
||
<li><a href="/docs/teach">teach</a></li>
|
||
</ul>
|
||
</div>
|
||
|
||
</div>
|
||
</aside>
|
||
<div class="skills-main">
|
||
|
||
<article class="skill-detail">
|
||
<div class="skill-detail-hero skill-detail-hero--has-demo">
|
||
<header class="skill-detail-header">
|
||
<p class="skill-detail-eyebrow"><a href="/docs">Docs</a> / Harden</p>
|
||
<h1 class="skill-detail-title"><span class="skill-detail-title-namespace"><span class="skill-detail-title-slash">/</span>impeccable</span>onboard</h1>
|
||
<p class="skill-detail-tagline">Design first-run experiences, empty states, and paths to value.</p>
|
||
|
||
<div class="skill-meta-strip">
|
||
<span class="skill-meta-chip skill-meta-category" data-category="harden">Harden</span>
|
||
<span class="skill-meta-chip">User-invocable</span>
|
||
<span class="skill-meta-chip skill-meta-args">[target]</span>
|
||
</div>
|
||
</header>
|
||
|
||
<section class="skill-demo" aria-label="Before and after demo">
|
||
<div class="split-comparison" data-demo="skill-onboard">
|
||
<p class="skill-demo-eyebrow">Drag or hover to compare</p>
|
||
<div class="split-container">
|
||
<div class="split-before">
|
||
<div class="split-content">
|
||
<div style="width: 100%; max-width: 260px; padding: 24px; background: #f5f5f5; border: 1px solid #ddd; border-radius: 6px; text-align: center;">
|
||
<div style="font-size: 32px; opacity: 0.3; margin-bottom: 8px;">📁</div>
|
||
<div style="font-size: 14px; color: #999;">No items found</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="split-after">
|
||
<div class="split-content">
|
||
<div style="width: 100%; max-width: 280px; padding: 24px; background: var(--color-paper); border: 1px solid var(--color-mist); border-radius: 12px; text-align: center;">
|
||
<div style="width: 64px; height: 64px; margin: 0 auto 16px; background: color-mix(in oklch, var(--color-accent) 15%, var(--color-paper)); border-radius: 16px; display: flex; align-items: center; justify-content: center;">
|
||
<span style="font-size: 28px;">✨</span>
|
||
</div>
|
||
<div style="font-family: var(--font-display); font-size: 1.25rem; font-weight: 400; color: var(--color-ink); margin-bottom: 8px;">Create your first project</div>
|
||
<div style="font-size: 0.8125rem; color: var(--color-ash); line-height: 1.5; margin-bottom: 20px;">Projects help you organize your work. Start with a template or blank canvas.</div>
|
||
<div style="display: flex; flex-direction: column; gap: 8px;">
|
||
<button style="padding: 12px; background: var(--color-ink); color: var(--color-paper); border: none; border-radius: 8px; font-size: 0.875rem; font-weight: 500; cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 8px;">
|
||
<span>+</span> New Project
|
||
</button>
|
||
<button style="padding: 10px; background: transparent; color: var(--color-charcoal); border: 1px solid var(--color-mist); border-radius: 8px; font-size: 0.8125rem; cursor: pointer;">Browse Templates</button>
|
||
</div>
|
||
<div style="margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--color-mist);">
|
||
<div style="font-size: 0.6875rem; color: var(--color-ash);">Need help? <span style="color: var(--color-accent); cursor: pointer;">Watch a quick tutorial →</span></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="split-divider"></div>
|
||
</div>
|
||
<div class="split-labels">
|
||
<span class="split-label-item" data-point="before">Before</span>
|
||
<p class="skill-demo-caption">Empty state → Guided onboarding experience</p>
|
||
<span class="split-label-item" data-point="after">After</span>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
|
||
<section class="skill-detail-editorial prose">
|
||
<h2 id="when-to-use-it">When to use it</h2>
|
||
<p><code>/impeccable onboard</code> is for the moments that decide whether a new user sticks around: the first screen, the empty state, the setup flow, the product tour, the "what do I do now" gap. Reach for it when activation is weak, when new users drop off before reaching value, or when your product has empty states that say "no items yet" and stop there.</p>
|
||
<h2 id="how-it-works">How it works</h2>
|
||
<p>The command starts from one question: what is the aha moment, and how fast can a new user get there. Every design decision points at that moment.</p>
|
||
<p>It works across the surfaces that shape first impressions:</p>
|
||
<ol>
|
||
<li><strong>First-run experience</strong>: the moments immediately after sign-up. Should the user see a tour, a blank canvas, a filled example, or nothing at all. Pick the approach that matches the product.</li>
|
||
<li><strong>Empty states</strong>: every zero-data screen gets oriented. Where am I, why is this empty, what do I do next, what will it look like once it is full.</li>
|
||
<li><strong>Setup and installation</strong>: required configuration is minimized, defaults are smart, each step explains why it matters.</li>
|
||
<li><strong>Progressive disclosure</strong>: advanced features stay out of the way until they are earned.</li>
|
||
<li><strong>Activation events</strong>: the moment a user first experiences the core value is instrumented and celebrated, quietly.</li>
|
||
</ol>
|
||
<p>The command resists two common failure modes: over-tutorialized onboarding where users click through a carousel before they can touch anything, and zero-onboarding where users are dropped into an empty app and expected to figure it out.</p>
|
||
<h2 id="try-it">Try it</h2>
|
||
<div class="code-block-wrap"><pre class="code-block"><code>/impeccable onboard the editor</code></pre><button class="code-block-copy" type="button" data-copy="/impeccable onboard the editor" aria-label="Copy to clipboard"></button></div>
|
||
<p>Typical output:</p>
|
||
<ul>
|
||
<li>First-run: replaces empty editor with a filled example document the user can modify. Cancel button discards the example, edit replaces the content with the user's work.</li>
|
||
<li>Empty state on document list: "No documents yet. Create your first, or import from Notion, Google Docs, or Markdown."</li>
|
||
<li>Setup: reduced from 6 required fields to 1 (workspace name). Everything else has a smart default and can be edited later in settings.</li>
|
||
<li>Activation: the first time a user saves a document, a quiet toast says "Saved. Your work is in the cloud now." One-time, not repeated.</li>
|
||
</ul>
|
||
<h2 id="pitfalls">Pitfalls</h2>
|
||
<ul>
|
||
<li><strong>Adding a product tour as the default answer.</strong> Most products do not need a tour. They need a better first screen. Tours are a crutch.</li>
|
||
<li><strong>Designing onboarding without defining the aha moment.</strong> If you cannot say in one sentence what the user should feel in the first 60 seconds, go back to <code>/impeccable shape</code> first.</li>
|
||
<li><strong>Running onboard on a broken flow.</strong> Fix the flow first. Onboarding cannot rescue a product where the core action is broken.</li>
|
||
</ul>
|
||
|
||
</section>
|
||
|
||
|
||
|
||
<section class="skill-source-card">
|
||
<header class="skill-source-card-header">
|
||
<span class="skill-source-card-label">reference/onboard.md</span>
|
||
<span class="skill-source-card-subtitle">Loaded when the impeccable skill routes to this command.</span>
|
||
</header>
|
||
<div class="skill-source-card-body prose">
|
||
<blockquote>
|
||
<p><strong>Additional context needed</strong>: the "aha moment" you want users to reach, and users' experience level.</p>
|
||
</blockquote>
|
||
<p>Create or improve onboarding experiences that help users understand, adopt, and succeed with the product quickly.</p>
|
||
<h2 id="assess-onboarding-needs">Assess Onboarding Needs</h2>
|
||
<p>Understand what users need to learn and why:</p>
|
||
<ol>
|
||
<li><p><strong>Identify the challenge</strong>:</p>
|
||
<ul>
|
||
<li>What are users trying to accomplish?</li>
|
||
<li>What's confusing or unclear about current experience?</li>
|
||
<li>Where do users get stuck or drop off?</li>
|
||
<li>What's the "aha moment" we want users to reach?</li>
|
||
</ul>
|
||
</li>
|
||
<li><p><strong>Understand the users</strong>:</p>
|
||
<ul>
|
||
<li>What's their experience level? (Beginners, power users, mixed?)</li>
|
||
<li>What's their motivation? (Excited and exploring? Required by work?)</li>
|
||
<li>What's their time commitment? (5 minutes? 30 minutes?)</li>
|
||
<li>What alternatives do they know? (Coming from competitor? New to category?)</li>
|
||
</ul>
|
||
</li>
|
||
<li><p><strong>Define success</strong>:</p>
|
||
<ul>
|
||
<li>What's the minimum users need to learn to be successful?</li>
|
||
<li>What's the key action we want them to take? (First project? First invite?)</li>
|
||
<li>How do we know onboarding worked? (Completion rate? Time to value?)</li>
|
||
</ul>
|
||
</li>
|
||
</ol>
|
||
<p><strong>CRITICAL</strong>: Onboarding should get users to value as quickly as possible, not teach everything possible.</p>
|
||
<h2 id="onboarding-principles">Onboarding Principles</h2>
|
||
<p>Follow these core principles:</p>
|
||
<h3 id="show-dont-tell">Show, Don't Tell</h3>
|
||
<ul>
|
||
<li>Demonstrate with working examples, not just descriptions</li>
|
||
<li>Provide real functionality in onboarding, not separate tutorial mode</li>
|
||
<li>Use progressive disclosure, teach one thing at a time</li>
|
||
</ul>
|
||
<h3 id="make-it-optional-when-possible">Make It Optional (When Possible)</h3>
|
||
<ul>
|
||
<li>Let experienced users skip onboarding</li>
|
||
<li>Don't block access to product</li>
|
||
<li>Provide "Skip" or "I'll explore on my own" options</li>
|
||
</ul>
|
||
<h3 id="time-to-value">Time to Value</h3>
|
||
<ul>
|
||
<li>Get users to their "aha moment" ASAP</li>
|
||
<li>Front-load most important concepts</li>
|
||
<li>Teach 20% that delivers 80% of value</li>
|
||
<li>Save advanced features for contextual discovery</li>
|
||
</ul>
|
||
<h3 id="context-over-ceremony">Context Over Ceremony</h3>
|
||
<ul>
|
||
<li>Teach features when users need them, not upfront</li>
|
||
<li>Empty states are onboarding opportunities</li>
|
||
<li>Tooltips and hints at point of use</li>
|
||
</ul>
|
||
<h3 id="respect-user-intelligence">Respect User Intelligence</h3>
|
||
<ul>
|
||
<li>Don't patronize or over-explain</li>
|
||
<li>Be concise and clear</li>
|
||
<li>Assume users can figure out standard patterns</li>
|
||
</ul>
|
||
<h2 id="design-onboarding-experiences">Design Onboarding Experiences</h2>
|
||
<p>Create appropriate onboarding for the context:</p>
|
||
<h3 id="initial-product-onboarding">Initial Product Onboarding</h3>
|
||
<p><strong>Welcome Screen</strong>:</p>
|
||
<ul>
|
||
<li>Clear value proposition (what is this product?)</li>
|
||
<li>What users will learn/accomplish</li>
|
||
<li>Time estimate (honest about commitment)</li>
|
||
<li>Option to skip (for experienced users)</li>
|
||
</ul>
|
||
<p><strong>Account Setup</strong>:</p>
|
||
<ul>
|
||
<li>Minimal required information (collect more later)</li>
|
||
<li>Explain why you're asking for each piece of information</li>
|
||
<li>Smart defaults where possible</li>
|
||
<li>Social login when appropriate</li>
|
||
</ul>
|
||
<p><strong>Core Concept Introduction</strong>:</p>
|
||
<ul>
|
||
<li>Introduce 1-3 core concepts (not everything)</li>
|
||
<li>Use simple language and examples</li>
|
||
<li>Interactive when possible (do, don't just read)</li>
|
||
<li>Progress indication (step 1 of 3)</li>
|
||
</ul>
|
||
<p><strong>First Success</strong>:</p>
|
||
<ul>
|
||
<li>Guide users to accomplish something real</li>
|
||
<li>Pre-populated examples or templates</li>
|
||
<li>Celebrate completion (but don't overdo it)</li>
|
||
<li>Clear next steps</li>
|
||
</ul>
|
||
<h3 id="feature-discovery-adoption">Feature Discovery & Adoption</h3>
|
||
<p><strong>Empty States</strong>:
|
||
Instead of blank space, show:</p>
|
||
<ul>
|
||
<li>What will appear here (description + screenshot/illustration)</li>
|
||
<li>Why it's valuable</li>
|
||
<li>Clear CTA to create first item</li>
|
||
<li>Example or template option</li>
|
||
</ul>
|
||
<p>Example:</p>
|
||
<div class="code-block-wrap"><pre class="code-block"><code>No projects yet
|
||
Projects help you organize your work and collaborate with your team.
|
||
[Create your first project] or [Start from template]</code></pre><button class="code-block-copy" type="button" data-copy="No projects yet
|
||
Projects help you organize your work and collaborate with your team.
|
||
[Create your first project] or [Start from template]" aria-label="Copy to clipboard"></button></div>
|
||
<p><strong>Contextual Tooltips</strong>:</p>
|
||
<ul>
|
||
<li>Appear at relevant moment (first time user sees feature)</li>
|
||
<li>Point directly at relevant UI element</li>
|
||
<li>Brief explanation + benefit</li>
|
||
<li>Dismissable (with "Don't show again" option)</li>
|
||
<li>Optional "Learn more" link</li>
|
||
</ul>
|
||
<p><strong>Feature Announcements</strong>:</p>
|
||
<ul>
|
||
<li>Highlight new features when they're released</li>
|
||
<li>Show what's new and why it matters</li>
|
||
<li>Let users try immediately</li>
|
||
<li>Dismissable</li>
|
||
</ul>
|
||
<p><strong>Progressive Onboarding</strong>:</p>
|
||
<ul>
|
||
<li>Teach features when users encounter them</li>
|
||
<li>Badges or indicators on new/unused features</li>
|
||
<li>Unlock complexity gradually (don't show all options immediately)</li>
|
||
</ul>
|
||
<h3 id="guided-tours-walkthroughs">Guided Tours & Walkthroughs</h3>
|
||
<p><strong>When to use</strong>:</p>
|
||
<ul>
|
||
<li>Complex interfaces with many features</li>
|
||
<li>Significant changes to existing product</li>
|
||
<li>Industry-specific tools needing domain knowledge</li>
|
||
</ul>
|
||
<p><strong>How to design</strong>:</p>
|
||
<ul>
|
||
<li>Spotlight specific UI elements (dim rest of page)</li>
|
||
<li>Keep steps short (3-7 steps max per tour)</li>
|
||
<li>Allow users to click through tour freely</li>
|
||
<li>Include "Skip tour" option</li>
|
||
<li>Make replayable (help menu)</li>
|
||
</ul>
|
||
<p><strong>Best practices</strong>:</p>
|
||
<ul>
|
||
<li>Interactive over passive (let users click real buttons)</li>
|
||
<li>Focus on workflow, not features ("Create a project" not "This is the project button")</li>
|
||
<li>Provide sample data so actions work</li>
|
||
</ul>
|
||
<h3 id="interactive-tutorials">Interactive Tutorials</h3>
|
||
<p><strong>When to use</strong>:</p>
|
||
<ul>
|
||
<li>Users need hands-on practice</li>
|
||
<li>Concepts are complex or unfamiliar</li>
|
||
<li>High stakes (better to practice in safe environment)</li>
|
||
</ul>
|
||
<p><strong>How to design</strong>:</p>
|
||
<ul>
|
||
<li>Sandbox environment with sample data</li>
|
||
<li>Clear objectives ("Create a chart showing sales by region")</li>
|
||
<li>Step-by-step guidance</li>
|
||
<li>Validation (confirm they did it right)</li>
|
||
<li>Graduation moment (you're ready!)</li>
|
||
</ul>
|
||
<h3 id="documentation-help">Documentation & Help</h3>
|
||
<p><strong>In-product help</strong>:</p>
|
||
<ul>
|
||
<li>Contextual help links throughout interface</li>
|
||
<li>Keyboard shortcut reference</li>
|
||
<li>Search-able help center</li>
|
||
<li>Video tutorials for complex workflows</li>
|
||
</ul>
|
||
<p><strong>Help patterns</strong>:</p>
|
||
<ul>
|
||
<li><code>?</code> icon near complex features</li>
|
||
<li>"Learn more" links in tooltips</li>
|
||
<li>Keyboard shortcut hints (<code>⌘K</code> shown on search box)</li>
|
||
</ul>
|
||
<h2 id="empty-state-design">Empty State Design</h2>
|
||
<p>Every empty state needs:</p>
|
||
<h3 id="what-will-be-here">What Will Be Here</h3>
|
||
<p>"Your recent projects will appear here"</p>
|
||
<h3 id="why-it-matters">Why It Matters</h3>
|
||
<p>"Projects help you organize your work and collaborate with your team"</p>
|
||
<h3 id="how-to-get-started">How to Get Started</h3>
|
||
<p>[Create project] or [Import from template]</p>
|
||
<h3 id="visual-interest">Visual Interest</h3>
|
||
<p>Illustration or icon (not just text on blank page)</p>
|
||
<h3 id="contextual-help">Contextual Help</h3>
|
||
<p>"Need help getting started? [Watch 2-min tutorial]"</p>
|
||
<p><strong>Empty state types</strong>:</p>
|
||
<ul>
|
||
<li><strong>First use</strong>: Never used this feature (emphasize value, provide template)</li>
|
||
<li><strong>User cleared</strong>: Intentionally deleted everything (light touch, easy to recreate)</li>
|
||
<li><strong>No results</strong>: Search or filter returned nothing (suggest different query, clear filters)</li>
|
||
<li><strong>No permissions</strong>: Can't access (explain why, how to get access)</li>
|
||
<li><strong>Error state</strong>: Failed to load (explain what happened, retry option)</li>
|
||
</ul>
|
||
<h2 id="implementation-patterns">Implementation Patterns</h2>
|
||
<h3 id="technical-approaches">Technical approaches:</h3>
|
||
<p><strong>Tooltip libraries</strong>: Tippy.js, Popper.js
|
||
<strong>Tour libraries</strong>: Intro.js, Shepherd.js, React Joyride
|
||
<strong>Modal patterns</strong>: Focus trap, backdrop, ESC to close
|
||
<strong>Progress tracking</strong>: LocalStorage for "seen" states
|
||
<strong>Analytics</strong>: Track completion, drop-off points</p>
|
||
<p><strong>Storage patterns</strong>:</p>
|
||
<div class="code-block-wrap"><pre class="code-block code-block--javascript"><code>// Track which onboarding steps user has seen
|
||
localStorage.setItem('onboarding-completed', 'true');
|
||
localStorage.setItem('feature-tooltip-seen-reports', 'true');</code></pre><button class="code-block-copy" type="button" data-copy="// Track which onboarding steps user has seen
|
||
localStorage.setItem('onboarding-completed', 'true');
|
||
localStorage.setItem('feature-tooltip-seen-reports', 'true');" aria-label="Copy to clipboard"></button></div>
|
||
<p><strong>IMPORTANT</strong>: Don't show same onboarding twice (annoying). Track completion and respect dismissals.</p>
|
||
<p><strong>NEVER</strong>:</p>
|
||
<ul>
|
||
<li>Force users through long onboarding before they can use product</li>
|
||
<li>Patronize users with obvious explanations</li>
|
||
<li>Show same tooltip repeatedly (respect dismissals)</li>
|
||
<li>Block all UI during tour (let users explore)</li>
|
||
<li>Create separate tutorial mode disconnected from real product</li>
|
||
<li>Overwhelm with information upfront (progressive disclosure!)</li>
|
||
<li>Hide "Skip" or make it hard to find</li>
|
||
<li>Forget about returning users (don't show initial onboarding again)</li>
|
||
</ul>
|
||
<h2 id="verify-onboarding-quality">Verify Onboarding Quality</h2>
|
||
<p>Test with real users:</p>
|
||
<ul>
|
||
<li><strong>Time to completion</strong>: Can users complete onboarding quickly?</li>
|
||
<li><strong>Comprehension</strong>: Do users understand after completing?</li>
|
||
<li><strong>Action</strong>: Do users take desired next step?</li>
|
||
<li><strong>Skip rate</strong>: Are too many users skipping? (Maybe it's too long or not valuable)</li>
|
||
<li><strong>Completion rate</strong>: Are users completing? (If low, simplify)</li>
|
||
<li><strong>Time to value</strong>: How long until users get first value?</li>
|
||
</ul>
|
||
<p>Remember: You're a product educator with excellent teaching instincts. Get users to their "aha moment" as quickly as possible. Teach the essential, make it contextual, respect user time and intelligence.</p>
|
||
|
||
</div>
|
||
</section>
|
||
|
||
|
||
</article>
|
||
|
||
</div>
|
||
</div>
|
||
</main>
|
||
<script>
|
||
// Copy buttons on rendered code blocks
|
||
document.addEventListener('click', (e) => {
|
||
const btn = e.target.closest('[data-copy]');
|
||
if (!btn) return;
|
||
const text = btn.getAttribute('data-copy');
|
||
if (!text) return;
|
||
navigator.clipboard.writeText(text).then(() => {
|
||
btn.classList.add('is-copied');
|
||
setTimeout(() => btn.classList.remove('is-copied'), 1500);
|
||
}).catch(() => {});
|
||
});
|
||
|
||
// Mobile sidebar toggle (shown on narrow viewports, hidden on desktop).
|
||
document.addEventListener('click', (e) => {
|
||
const toggle = e.target.closest('.skills-sidebar-toggle');
|
||
if (!toggle) return;
|
||
const expanded = toggle.getAttribute('aria-expanded') === 'true';
|
||
toggle.setAttribute('aria-expanded', String(!expanded));
|
||
});
|
||
|
||
// Before/after split-compare: drag on touch, hover OR drag on mouse.
|
||
// Pointer events attach to the padded .split-comparison wrapper so
|
||
// there is a ~20px invisible buffer around the visible box. The
|
||
// divider only snaps back when the pointer leaves that outer buffer.
|
||
(function initSplitCompare() {
|
||
const wrappers = document.querySelectorAll('.split-comparison');
|
||
if (wrappers.length === 0) return;
|
||
const hasHover = matchMedia('(hover: hover)').matches;
|
||
const DEFAULT_POSITION = 50;
|
||
|
||
for (const wrapper of wrappers) {
|
||
const container = wrapper.querySelector('.split-container');
|
||
const splitAfter = wrapper.querySelector('.split-after');
|
||
const splitDivider = wrapper.querySelector('.split-divider');
|
||
if (!container || !splitAfter || !splitDivider) continue;
|
||
|
||
const tanAngle = Math.tan(10 * Math.PI / 180);
|
||
let skewOffset = 8;
|
||
const recalcSkew = () => {
|
||
const r = container.getBoundingClientRect();
|
||
if (r.width > 0 && r.height > 0) {
|
||
skewOffset = 50 * r.height * tanAngle / r.width;
|
||
}
|
||
};
|
||
recalcSkew();
|
||
window.addEventListener('resize', recalcSkew, { passive: true });
|
||
|
||
let targetX = DEFAULT_POSITION;
|
||
let currentX = DEFAULT_POSITION;
|
||
let rafId = null;
|
||
|
||
const paint = (pct) => {
|
||
const x = Math.max(-skewOffset, Math.min(100 + skewOffset, pct));
|
||
splitAfter.style.clipPath =
|
||
`polygon(${x + skewOffset}% 0%, 100% 0%, 100% 100%, ${x - skewOffset}% 100%)`;
|
||
splitDivider.style.left = `${x}%`;
|
||
};
|
||
|
||
const step = () => {
|
||
currentX += (targetX - currentX) * 0.2;
|
||
if (Math.abs(targetX - currentX) < 0.1) {
|
||
currentX = targetX;
|
||
rafId = null;
|
||
} else {
|
||
rafId = requestAnimationFrame(step);
|
||
}
|
||
paint(currentX);
|
||
};
|
||
|
||
const setTarget = (pct) => {
|
||
targetX = pct;
|
||
if (rafId === null) rafId = requestAnimationFrame(step);
|
||
};
|
||
|
||
paint(DEFAULT_POSITION);
|
||
|
||
// Percentage is always relative to the VISIBLE .split-container,
|
||
// not the padded .split-comparison wrapper. The pointer event
|
||
// target is the wrapper but the clip-path math uses the inner box.
|
||
const pctFromClientX = (clientX) => {
|
||
const rect = container.getBoundingClientRect();
|
||
return ((clientX - rect.left) / rect.width) * 100;
|
||
};
|
||
|
||
let hovering = false;
|
||
let dragging = false;
|
||
|
||
wrapper.addEventListener('pointerenter', (e) => {
|
||
if (hasHover && e.pointerType === 'mouse') {
|
||
hovering = true;
|
||
}
|
||
});
|
||
|
||
wrapper.addEventListener('pointerdown', (e) => {
|
||
dragging = true;
|
||
wrapper.setPointerCapture(e.pointerId);
|
||
setTarget(pctFromClientX(e.clientX));
|
||
});
|
||
|
||
wrapper.addEventListener('pointermove', (e) => {
|
||
if (dragging || hovering) {
|
||
setTarget(pctFromClientX(e.clientX));
|
||
}
|
||
});
|
||
|
||
const endDrag = (e) => {
|
||
if (dragging) {
|
||
dragging = false;
|
||
try { wrapper.releasePointerCapture(e.pointerId); } catch {}
|
||
}
|
||
};
|
||
|
||
wrapper.addEventListener('pointerup', endDrag);
|
||
wrapper.addEventListener('pointercancel', endDrag);
|
||
|
||
wrapper.addEventListener('pointerleave', (e) => {
|
||
endDrag(e);
|
||
if (hovering) {
|
||
hovering = false;
|
||
setTarget(DEFAULT_POSITION);
|
||
}
|
||
});
|
||
}
|
||
})();
|
||
</script>
|
||
</body>
|
||
</html>
|