Olympus Docs
CookbookTools

User onboarding checklist UI

Help new users complete setup

A new user signs up. They have an account, but they haven't:

  • Verified email.
  • Enabled MFA.
  • Added profile photo.
  • Created their first resource.

A checklist UI shepherds them through. Increases retention.

Pattern

export function OnboardingChecklist({ user }) {
  const tasks = [
    { id: "verify_email", label: "Verify your email", done: user.email_verified, href: "/verify-email" },
    { id: "set_name", label: "Add your name", done: user.has_name, href: "/settings" },
    { id: "enable_mfa", label: "Enable 2-factor authentication", done: user.has_mfa, href: "/settings/security" },
    { id: "first_task", label: "Create your first project", done: user.has_project, href: "/projects/new" },
    { id: "invite", label: "Invite a teammate (optional)", done: user.has_invites, href: "/team/invite", optional: true },
  ];
  
  const completed = tasks.filter(t => t.done).length;
  const total = tasks.length;
  
  if (completed === total) return null;  // hide once done
  
  return (
    <Card>
      <CardHeader>
        <h2>Get started ({completed}/{total})</h2>
        <ProgressBar value={completed / total} />
      </CardHeader>
      <ul>
        {tasks.map(t => (
          <li key={t.id}>
            <Checkbox checked={t.done} disabled />
            <a href={t.href}>{t.label}</a>
            {t.optional && <Badge>optional</Badge>}
          </li>
        ))}
      </ul>
    </Card>
  );
}

Display on the main dashboard until complete.

What to include

Default checklist:

  1. Verify email (always).
  2. Add display name / photo (personalizes).
  3. Enable MFA (security).
  4. First domain action (varies by app).
  5. Invite teammates / set up integration (varies).

5-7 items. More is overwhelming.

Dismissal

Allow:

<Button variant="ghost" onClick={dismiss}>I'll do this later</Button>
async function dismiss() {
  await kratos.adminPatch(userId, [
    { op: "add", path: "/metadata_public/onboarding_dismissed", value: new Date().toISOString() }
  ]);
}

Hide for 30 days, then re-show if still incomplete (re-engagement).

Confetti / celebration

When user completes a step:

useEffect(() => {
  if (prevCompleted < completed) {
    confetti();
    track("onboarding_step_completed", { step: lastCompletedStep });
  }
}, [completed]);

Small reward. Motivates next step.

When all complete:

{completed === total && (
  <Banner>
    🎉 You're all set up! Here's a quick tour to help you start.
    <Button>Take the tour</Button>
  </Banner>
)}

Track conversion

Funnel:

  • Signed up.
  • Verified email.
  • Enabled MFA.
  • Created first resource.
  • Active 7 days.

Drop-off at each step tells you where users get stuck. Fix UX there.

Don't bombard

A 10-item checklist with tooltips and notifications is overwhelming. Stick to essentials. Trust users to discover advanced features.

Drip emails

Pair with email reminders. If user hasn't verified email after 3 days:

Subject: Quick reminder to verify your email

Hi,

You signed up 3 days ago but haven't verified your email yet. 
Verifying takes 5 seconds:

  [Resend verification email]

Once verified, you'll be able to:
- Recover your password if lost.
- Receive important notifications.

After 7 days: another reminder. After 30: low-frequency or stop.

Don't spam.

Per-role checklists

If you have user types (free vs paid, admin vs member):

const tasks = baseTasks;
if (user.role === "admin") tasks.push({ id: "configure_team", ... });
if (user.plan === "free") tasks.push({ id: "explore_premium", ... });

Customize per their context.

A/B testing

Different orderings, copy variations:

const variant = hashToBucket(user.id, 2);
const order = variant === 0 ? ["verify_email", "set_name", "enable_mfa", ...]
                            : ["enable_mfa", "verify_email", "set_name", ...];

Test which order converts better.

Mobile

On mobile, the checklist might take whole screen. Use a sticky bottom-bar instead:

<BottomBar>
  Setup progress: {completed}/{total}
  <Button onClick={openChecklist}>View</Button>
</BottomBar>

Tap → modal with full list.

When to hide forever

if (user.created_at < 30 days ago && completed >= total - 1) hide();

User is set up well. Stop nagging.

On this page