---
title: Onboarding
description: Onboarding flows for a clear first run.
group: Blocks
order: 34
---

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.

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

```bash
npx @arctis-sh/@arctis-sh/ui@latest add onboarding-01
```

### Usage

```tsx
import { Onboarding01 } from "@/components/blocks/onboarding/onboarding-01"
```

```tsx
<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

```bash
npx @arctis-sh/@arctis-sh/ui@latest add onboarding-02
```

### Usage

```tsx
import { Onboarding02 } from "@/components/blocks/onboarding/onboarding-02"
```

```tsx
<Onboarding02 />
```

## Goals

A split layout for choosing initial build goals with checkboxes.

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

```bash
npx @arctis-sh/@arctis-sh/ui@latest add onboarding-03
```

### Usage

```tsx
import { Onboarding03 } from "@/components/blocks/onboarding/onboarding-03"
```

```tsx
<Onboarding03 />
```
