First-run layouts for welcoming someone, collecting the basics, and helping them choose a direction.
Welcome
A centered welcome with a short checklist and get-started actions.
Welcome to Arctis
Let’s get your workspace ready
A short setup so your team can start shipping pages without the blank canvas.
- Invite your team in one click
- Start from ready-made blocks
- Ship a first page today
tsx
"use client";import { Button } from "@/components/ui/button";import { cn } from "@/lib/utils";type Onboarding01Props = { className?: string;};const HIGHLIGHTS = [ "Invite your team in one click", "Start from ready-made blocks", "Ship a first page today",] as const;function CheckIcon({ className }: { className?: string }) { return ( <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" className={className} > <path d="M5 12.5 10 17.5 19 7" /> </svg> );}export function Onboarding01({ className }: Onboarding01Props) { return ( <section className={cn("@container w-full bg-background", className)}> <div className="mx-auto flex w-full max-w-lg flex-col items-center px-4 py-10 text-center @[32rem]:px-6 @[32rem]:py-14"> <p className="text-xs tracking-wide text-muted-foreground"> Welcome to Arctis </p> <h2 className="mt-2 text-2xl font-medium tracking-wide text-foreground @[32rem]:text-3xl"> Let’s get your workspace ready </h2> <p className="mt-3 text-sm tracking-wide text-muted-foreground"> A short setup so your team can start shipping pages without the blank canvas. </p> <ul className="mt-8 flex w-full flex-col gap-3 text-left"> {HIGHLIGHTS.map((item) => ( <li key={item} className="flex items-center gap-3 rounded-xl bg-muted px-4 py-3 text-sm tracking-wide text-foreground" > <span className="flex size-5 shrink-0 items-center justify-center text-foreground [&_svg]:size-3.5"> <CheckIcon /> </span> {item} </li> ))} </ul> <div className="mt-8 flex w-full flex-col gap-2 @[32rem]:flex-row @[32rem]:justify-center"> <Button type="button" size="sm" className="w-full @[32rem]:w-auto"> Get started </Button> <Button type="button" size="sm" variant="ghost" className="w-full @[32rem]:w-auto" > Skip for now </Button> </div> </div> </section> );}Installation
npx @arctis-sh/@arctis-sh/ui@latest add onboarding-01Usage
import { Onboarding01 } from "@/components/blocks/onboarding/onboarding-01"<Onboarding01 />Setup wizard
A multi-step flow covering profile, workspace, and role, with progress kept visible.
tsx
"use client";import { useState } from "react";import { Button } from "@/components/ui/button";import { Input } from "@/components/ui/input";import { Label } from "@/components/ui/label";import { Progress } from "@/components/ui/progress";import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";import { cn } from "@/lib/utils";type Onboarding02Props = { className?: string;};const STEPS = [ { id: "profile", title: "Profile", detail: "Who’s setting this up" }, { id: "workspace", title: "Workspace", detail: "Name your space" }, { id: "role", title: "Role", detail: "How you’ll use Arctis" },] as const;const ROLES = [ { value: "design", label: "Design", detail: "Marketing pages and kits" }, { value: "eng", label: "Engineering", detail: "Ship components faster" }, { value: "founder", label: "Founder", detail: "Launch and iterate" },] as const;function CheckIcon({ className }: { className?: string }) { return ( <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" className={className} > <path d="M5 12.5 10 17.5 19 7" /> </svg> );}export function Onboarding02({ className }: Onboarding02Props) { const [step, setStep] = useState(0); const [done, setDone] = useState(false); const [name, setName] = useState(""); const [workspace, setWorkspace] = useState(""); const [role, setRole] = useState("design"); const progress = ((step + 1) / STEPS.length) * 100; const current = STEPS[step]!; const isLast = step === STEPS.length - 1; const roleLabel = ROLES.find((option) => option.value === role)?.label; const displayName = name.trim() || "there"; const displayWorkspace = workspace.trim() || "your workspace"; return ( <section className={cn("@container w-full bg-background", className)}> <div className="mx-auto w-full max-w-md px-4 py-10 @[32rem]:px-6 @[32rem]:py-14"> {done ? ( <div className="flex flex-col items-center text-center"> <span className="flex size-12 items-center justify-center rounded-full bg-muted text-foreground [&_svg]:size-5"> <CheckIcon /> </span> <h2 className="mt-5 text-xl font-medium tracking-wide text-foreground @[32rem]:text-2xl"> You’re all set, {displayName} </h2> <p className="mt-2 text-sm tracking-wide text-muted-foreground"> {displayWorkspace} is ready {roleLabel ? ` for ${roleLabel.toLowerCase()}` : ""}. Jump in and start building. </p> <div className="mt-8 flex w-full flex-col gap-2 @[32rem]:flex-row @[32rem]:justify-center"> <Button type="button" size="sm" className="w-full @[32rem]:w-auto"> Go to dashboard </Button> <Button type="button" size="sm" variant="ghost" className="w-full @[32rem]:w-auto" onClick={() => { setDone(false); setStep(0); }} > Start over </Button> </div> </div> ) : ( <> <div className="flex items-center justify-between gap-3"> <p className="text-xs tracking-wide text-muted-foreground"> Step {step + 1} of {STEPS.length} </p> <p className="text-xs tracking-wide text-muted-foreground"> {current.title} </p> </div> <Progress value={progress} className="mt-3" /> <div className="mt-8"> <h2 className="text-xl font-medium tracking-wide text-foreground @[32rem]:text-2xl"> {current.title} </h2> <p className="mt-2 text-sm tracking-wide text-muted-foreground"> {current.detail} </p> </div> <div className="mt-6 min-h-40"> {step === 0 ? ( <div className="flex flex-col gap-1.5"> <Label htmlFor="onboarding-02-name">Your name</Label> <Input id="onboarding-02-name" value={name} onChange={(event) => setName(event.target.value)} placeholder="Maya Chen" className="border-0 bg-muted" /> </div> ) : null} {step === 1 ? ( <div className="flex flex-col gap-1.5"> <Label htmlFor="onboarding-02-workspace">Workspace name</Label> <Input id="onboarding-02-workspace" value={workspace} onChange={(event) => setWorkspace(event.target.value)} placeholder="Northline" className="border-0 bg-muted" /> </div> ) : null} {step === 2 ? ( <RadioGroup value={role} onValueChange={setRole} className="flex flex-col gap-2" > {ROLES.map((option) => ( <div key={option.value} className="flex items-center gap-3 rounded-xl bg-muted px-3 py-3" > <RadioGroupItem id={`onboarding-02-${option.value}`} value={option.value} /> <label htmlFor={`onboarding-02-${option.value}`} className="min-w-0 flex-1 cursor-pointer" > <span className="block text-sm font-medium tracking-wide text-foreground"> {option.label} </span> <span className="block text-xs tracking-wide text-muted-foreground"> {option.detail} </span> </label> </div> ))} </RadioGroup> ) : null} </div> <div className="mt-8 flex items-center justify-between gap-2"> <Button type="button" size="sm" variant="ghost" disabled={step === 0} onClick={() => setStep((value) => Math.max(0, value - 1))} > Back </Button> <Button type="button" size="sm" onClick={() => { if (isLast) { setDone(true); return; } setStep((value) => Math.min(STEPS.length - 1, value + 1)); }} > {isLast ? "Finish setup" : "Continue"} </Button> </div> </> )} </div> </section> );}Installation
npx @arctis-sh/@arctis-sh/ui@latest add onboarding-02Usage
import { Onboarding02 } from "@/components/blocks/onboarding/onboarding-02"<Onboarding02 />Goals
A split layout for choosing initial build goals with checkboxes.
Almost done
What are you building first?
Pick a few goals so we can suggest the right blocks. You can change this later.
1 selected
tsx
"use client";import { useState } from "react";import { Button } from "@/components/ui/button";import { Checkbox } from "@/components/ui/checkbox";import { cn } from "@/lib/utils";type Onboarding03Props = { className?: string;};const GOALS = [ { id: "landing", label: "Landing pages", detail: "Hero, features, pricing" }, { id: "app", label: "App UI", detail: "Dashboards and settings" }, { id: "docs", label: "Docs site", detail: "Guides and API reference" }, { id: "marketing", label: "Campaigns", detail: "Banners, CTAs, emails" },] as const;export function Onboarding03({ className }: Onboarding03Props) { const [selected, setSelected] = useState<string[]>(["landing"]); function toggle(id: string) { setSelected((current) => current.includes(id) ? current.filter((value) => value !== id) : [...current, id], ); } return ( <section className={cn("@container w-full bg-background", className)}> <div data-slot="block-split" className="mx-auto grid w-full max-w-4xl gap-10 px-4 py-10 @[40rem]:grid-cols-2 @[40rem]:items-start @[40rem]:gap-12 @[40rem]:px-6 @[40rem]:py-14" > <div> <p className="text-xs tracking-wide text-muted-foreground"> Almost done </p> <h2 className="mt-2 text-xl font-medium tracking-wide text-foreground @[32rem]:text-2xl"> What are you building first? </h2> <p className="mt-2 text-sm tracking-wide text-muted-foreground"> Pick a few goals so we can suggest the right blocks. You can change this later. </p> <p className="mt-6 text-sm tracking-wide text-muted-foreground"> {selected.length} selected </p> </div> <div> <ul className="flex flex-col gap-2"> {GOALS.map((goal) => { const checked = selected.includes(goal.id); const inputId = `onboarding-03-${goal.id}`; return ( <li key={goal.id}> <div className="flex items-center gap-3 rounded-xl bg-muted px-3 py-3"> <Checkbox id={inputId} checked={checked} onCheckedChange={() => toggle(goal.id)} /> <label htmlFor={inputId} className="min-w-0 flex-1 cursor-pointer"> <span className="block text-sm font-medium tracking-wide text-foreground"> {goal.label} </span> <span className="block text-xs tracking-wide text-muted-foreground"> {goal.detail} </span> </label> </div> </li> ); })} </ul> <div className="mt-6 flex flex-wrap gap-2"> <Button type="button" size="sm" disabled={selected.length === 0}> Continue </Button> <Button type="button" size="sm" variant="ghost"> Skip </Button> </div> </div> </div> </section> );}Installation
npx @arctis-sh/@arctis-sh/ui@latest add onboarding-03Usage
import { Onboarding03 } from "@/components/blocks/onboarding/onboarding-03"<Onboarding03 />