Onboarding

Onboarding flows for a clear first run.

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-01

Usage

import { Onboarding01 } from "@/components/blocks/onboarding/onboarding-01"
<Onboarding01 />

Setup wizard

A multi-step flow covering profile, workspace, and role, with progress kept visible.

Step 1 of 3

Profile

Profile

Who’s setting this up

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-02

Usage

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-03

Usage

import { Onboarding03 } from "@/components/blocks/onboarding/onboarding-03"
<Onboarding03 />