Retour au catalogue
Onboarding Checklist
Checklist d'onboarding avec barre de progression, items cochables et animation de completion.
onboardingmedium Both Responsive a11y
minimalplayfulsaasuniversalstacked
Theme
"use client";
import { useState } from "react";
import { Check, Circle, PartyPopper } from "lucide-react";
interface ChecklistItem {
id: string;
label: string;
description: string;
completed: boolean;
}
const initialItems: ChecklistItem[] = [
{ id: "profile", label: "Complete your profile", description: "Add your name, avatar, and bio", completed: true },
{ id: "team", label: "Invite your team", description: "Bring your collaborators on board", completed: true },
{ id: "project", label: "Create first project", description: "Set up your workspace and start building", completed: false },
{ id: "integrate", label: "Connect integrations", description: "Link your tools for a seamless workflow", completed: false },
{ id: "deploy", label: "Deploy to production", description: "Ship your first release to the world", completed: false },
];
export default function OnboardingChecklist() {
const [items, setItems] = useState(initialItems);
const completed = items.filter((i) => i.completed).length;
const total = items.length;
const progress = (completed / total) * 100;
const allDone = completed === total;
const toggle = (id: string) => {
setItems((prev) => prev.map((i) => (i.id === id ? { ...i, completed: !i.completed } : i)));
};
return (
<div className="py-10 px-6" style={{ background: "var(--color-background)" }}>
<div className="mx-auto max-w-md">
<div
className="rounded-2xl p-6"
style={{ background: "var(--color-background-card)", border: "1px solid var(--color-border)" }}
>
<div className="flex items-center justify-between">
<h3 className="text-lg font-semibold" style={{ color: "var(--color-foreground)" }}>
Getting Started
</h3>
{allDone && <PartyPopper size={20} style={{ color: "var(--color-accent)" }} />}
</div>
<p className="mt-1 text-sm" style={{ color: "var(--color-foreground-muted)" }}>
Complete these steps to set up your workspace
</p>
{/* Progress bar */}
<div className="mt-4 flex items-center gap-3">
<div
className="flex-1 h-2 rounded-full overflow-hidden"
style={{ background: "var(--color-background-alt)" }}
>
<div
className="h-full rounded-full transition-all duration-500 ease-out"
style={{
background: allDone
? "var(--color-accent)"
: `linear-gradient(90deg, var(--color-accent), color-mix(in srgb, var(--color-accent) 70%, var(--color-foreground)))`,
width: `${progress}%`,
}}
/>
</div>
<span className="text-xs font-medium tabular-nums" style={{ color: "var(--color-foreground-muted)" }}>
{completed}/{total}
</span>
</div>
{/* Items */}
<div className="mt-5 flex flex-col gap-1">
{items.map((item) => (
<button
key={item.id}
onClick={() => toggle(item.id)}
className="flex items-start gap-3 w-full p-3 rounded-xl text-left transition-all duration-300"
style={{
background: item.completed ? "var(--color-background-alt)" : "transparent",
}}
>
<div className="mt-0.5 flex-shrink-0">
{item.completed ? (
<div
className="w-5 h-5 rounded-full flex items-center justify-center transition-transform duration-300"
style={{ background: "var(--color-accent)", transform: "scale(1)" }}
>
<Check size={12} style={{ color: "var(--color-background)" }} />
</div>
) : (
<Circle size={20} style={{ color: "var(--color-border)" }} />
)}
</div>
<div>
<p
className="text-sm font-medium transition-all duration-300"
style={{
color: "var(--color-foreground)",
textDecoration: item.completed ? "line-through" : "none",
opacity: item.completed ? 0.6 : 1,
}}
>
{item.label}
</p>
<p className="text-xs mt-0.5" style={{ color: "var(--color-foreground-muted)" }}>
{item.description}
</p>
</div>
</button>
))}
</div>
{allDone && (
<div
className="mt-4 p-3 rounded-xl text-center text-sm font-medium"
style={{
background: "color-mix(in srgb, var(--color-accent) 10%, transparent)",
color: "var(--color-accent)",
}}
>
All done! Your workspace is ready.
</div>
)}
</div>
</div>
</div>
);
}