# Onboarding Workspace Setup

> A onboarding workspace setup block.

A shadcn/ui block from the [@blocks-so](https://blocks.so) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/blocks-so/onboarding-02).

- Page, with a live preview: https://shadcn.exchange/r/blocks-so/onboarding-02
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/blocks-so/onboarding-02-light-0eb48a4be266ec9e.png
- Registry item: https://blocks.so/r/onboarding-02.json
- Categories: onboarding
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://blocks.so/r/onboarding-02.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `@tabler/icons-react`
- Registry items: `button`

## Files

### `content/components/onboarding/onboarding-02.tsx`

```tsx
'use client';

import type { Icon } from '@tabler/icons-react';
import {
  IconCircleCheckFilled,
  IconFolder,
  IconPlug,
  IconRocket,
  IconUsers,
} from '@tabler/icons-react';
import { useState } from 'react';
import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';

interface StepData {
  title: string;
  description: string;
  icon: Icon;
  actionLabel: string;
}

const steps: StepData[] = [
  {
    title: 'Create your workspace',
    description:
      'Name your workspace and pick a URL. This is where your team will collaborate on everything.',
    icon: IconRocket,
    actionLabel: 'Get started',
  },
  {
    title: 'Invite your team',
    description:
      'Add teammates by email so everyone can collaborate in real time across projects.',
    icon: IconUsers,
    actionLabel: 'Send invites',
  },
  {
    title: 'Set up your first project',
    description:
      'Create a project to organize tasks, docs, and milestones all in one place.',
    icon: IconFolder,
    actionLabel: 'Create project',
  },
  {
    title: 'Connect your tools',
    description:
      'Link Slack, GitHub, or Figma to keep your workflow in sync automatically.',
    icon: IconPlug,
    actionLabel: 'Browse integrations',
  },
];

function getIconColor(isCompleted: boolean, isActive: boolean) {
  if (isCompleted) {
    return 'text-muted-foreground/30';
  }
  if (isActive) {
    return 'text-primary';
  }
  return 'text-muted-foreground/40';
}

function getTitleClass(isCompleted: boolean, isActive: boolean) {
  if (isCompleted) {
    return 'text-muted-foreground/50 line-through';
  }
  if (isActive) {
    return 'text-foreground';
  }
  return 'text-muted-foreground';
}

function StepIndicator({
  index,
  isCompleted,
  isActive,
}: {
  index: number;
  isCompleted: boolean;
  isActive: boolean;
}) {
  if (isCompleted) {
    return (
      <div className="flex size-7 items-center justify-center">
        <IconCircleCheckFilled
          aria-hidden="true"
          className="size-7 text-emerald-500"
        />
      </div>
    );
  }
  return (
    <div
      className={cn(
        'flex size-7 items-center justify-center rounded-full font-semibold text-xs',
        isActive
          ? 'bg-primary text-primary-foreground'
          : 'bg-muted text-muted-foreground'
      )}
    >
      {index + 1}
    </div>
  );
}

function StepCard({
  step,
  index,
  isCompleted,
  isActive,
  onComplete,
}: {
  step: StepData;
  index: number;
  isCompleted: boolean;
  isActive: boolean;
  onComplete: () => void;
}) {
  const StepIcon = step.icon;

  return (
    <div
      className={cn(
        'rounded-lg border p-4 transition-colors',
        isActive && 'border-primary/30 bg-muted/50',
        !isActive && 'border-border bg-background'
      )}
    >
      <div className="flex gap-3">
        <div className="mt-0.5 shrink-0">
          <StepIndicator
            index={index}
            isActive={isActive}
            isCompleted={isCompleted}
          />
        </div>
        <div className="min-w-0 flex-1">
          <div className="flex items-start gap-3">
            <div className="min-w-0 flex-1">
              <p
                className={cn(
                  'font-medium leading-6',
                  getTitleClass(isCompleted, isActive)
                )}
              >
                {step.title}
              </p>
              <p
                className={cn(
                  'mt-0.5 text-sm leading-5',
                  isActive
                    ? 'text-muted-foreground'
                    : 'text-muted-foreground/60'
                )}
              >
                {step.description}
              </p>
              {isActive && (
                <Button className="mt-3" onClick={onComplete} size="sm">
                  <StepIcon
                    aria-hidden="true"
                    className="-ml-0.5 size-4 shrink-0"
                  />
                  {step.actionLabel}
                </Button>
              )}
            </div>
            <StepIcon
              aria-hidden="true"
              className={cn(
                'mt-0.5 size-5 shrink-0',
                getIconColor(isCompleted, isActive)
              )}
            />
          </div>
        </div>
      </div>
    </div>
  );
}

export function Onboarding02() {
  const [completedSteps, setCompletedSteps] = useState<Set<number>>(
    () => new Set([0])
  );

  const currentStep = steps.findIndex((_, i) => !completedSteps.has(i));
  const completedCount = completedSteps.size;
  const allDone = completedCount === steps.length;

  const handleComplete = (index: number) => {
    setCompletedSteps((prev) => {
      const next = new Set(prev);
      next.add(index);
      return next;
    });
  };

  return (
    <div className="flex min-h-dvh items-center justify-center bg-background p-4">
      <div className="w-full max-w-xl">
        <div className="mb-6">
          <h3 className="font-semibold text-foreground text-lg">
            Set up your workspace
          </h3>
          <p className="mt-1 text-muted-foreground text-sm">
            Complete these steps to get your team up and running
          </p>
          <div className="mt-4">
            <div className="flex items-center justify-between text-sm">
              <span className="text-muted-foreground">
                {allDone ? (
                  <span className="font-medium text-emerald-600">
                    All done 🎉
                  </span>
                ) : (
                  <>
                    <span className="font-medium text-foreground">
                      {completedCount}
                    </span>{' '}
                    of {steps.length} completed
                  </>
                )}
              </span>
            </div>
            <div className="mt-2 h-1.5 w-full overflow-hidden rounded-full bg-muted">
              <div
                className="h-full rounded-full bg-emerald-500 transition-all duration-500"
                style={{
                  width: `${(completedCount / steps.length) * 100}%`,
                }}
              />
            </div>
          </div>
        </div>

        <div className="space-y-3">
          {steps.map((step, index) => (
            <StepCard
              index={index}
              isActive={index === currentStep}
              isCompleted={completedSteps.has(index)}
              key={step.title}
              onComplete={() => handleComplete(index)}
              step={step}
            />
          ))}
        </div>
      </div>
    </div>
  );
}
```
