Finish these tasks to unlock all features.
4
Complete
2
remaining
Visual progress tracker featuring a circular percentage indicator with completion stats and task list. Perfect for profile setup flows that encourage users to reach 100% completion.
Profile-completion screen built around a hand-drawn SVG progress ring, a completed/remaining stat pair, and a checklist card beneath it. Like the other checklist-style onboarding blocks, completion is entirely data-driven: there is no click handler to check off a task.
Upgrade to Pro
Pro blocks install through the shadcn CLI with your license key and ship their full source. Docs and live previews stay open to everyone, so you can read every block's details first.
Swap YOUR_EMAIL and YOUR_KEY for the email and license key on your account. Find your license key on your account page.
Radix flavor
npx shadcn add "https://ui.beste.co/r/onboarding12?email=YOUR_EMAIL&license_key=YOUR_KEY"Base UI flavor
npx shadcn add "https://ui.beste.co/r-base/onboarding12?email=YOUR_EMAIL&license_key=YOUR_KEY"This installs the block to components/beste/block/onboarding12.tsx and the badge and button shadcn/ui primitives it depends on.
The installed file exports onboarding12Demo alongside the block: the exact props behind the preview above. Spread it to get a working progress ring in one line.
import { Onboarding12, onboarding12Demo } from "@/components/beste/block/onboarding12";
export default function OnboardingPage() {
return <Onboarding12 {...onboarding12Demo} />;
}Then replace the demo with your own props. Written out, a trimmed setup looks like this:
import { Onboarding12 } from "@/components/beste/block/onboarding12";
export default function OnboardingPage() {
return (
<Onboarding12
badge={{ label: "Progress", variant: "secondary" }}
heading="Complete your setup"
description="Finish these tasks to unlock all features."
tasks={[
{ id: "1", title: "Create your account", completed: true },
{ id: "2", title: "Verify email address", completed: true },
{ id: "3", title: "Connect payment method", completed: false },
{ id: "4", title: "Invite team members", completed: false },
]}
labels={{ complete: "Complete", remaining: "remaining" }}
buttons={[{ label: "Continue Setup", href: "/setup" }]}
/>
);
}| Prop | Type | Default | Description |
|---|---|---|---|
badge | { label: string; variant?: "default" | "secondary" | "outline" } | – | Small badge above the heading |
heading | string | – | Main headline |
description | string | – | Supporting copy below the heading |
tasks | Task[] | [] | Checklist items, each with its own completion flag |
labels | { complete?: string; remaining?: string } | {} | Words under the completed/remaining stat numbers |
buttons | ButtonConfig[] | [] | CTA row below the checklist |
className | string | – | Extra classes for the outer <section> |
type Task = {
id: string;
title: string;
completed: boolean;
};
type ButtonConfig = {
label: string;
href: string;
variant?: "default" | "secondary" | "outline" | "ghost";
};tasks[].completed; there is no click handler to check off a task, so animating the ring means changing the tasks prop and letting the ring's own transition-all duration-500 animate the stroke change.CircularProgress component: a muted background circle plus a primary-colored circle whose strokeDashoffset is computed from percentage, with the whole <svg> rotated -90 degrees so the arc starts at 12 o'clock instead of 3 o'clock.h-12 w-px divider; both numbers are derived from the same tasks.filter((t) => t.completed) count used to draw the ring.divide-y on the <ul> for row separators rather than a border on each <li>.tasks is empty the component returns null rather than rendering an empty shell.totalCount > 0 ? ... : 0), though in practice this path is unreachable in the rendered UI since an empty tasks array triggers the early null return first.