# Onboarding Accordion

> A onboarding accordion block.

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

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

## Install

```bash
npx shadcn@latest add https://blocks.so/r/onboarding-04.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: `accordion`, `button`

## Files

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

```tsx
'use client';

import {
  IconAdjustments,
  IconCalculator,
  IconChartBar,
  IconCircleCheckFilled,
  IconDatabase,
} from '@tabler/icons-react';
import { useState } from 'react';
import {
  Accordion,
  AccordionContent,
  AccordionItem,
  AccordionTrigger,
} from '@/components/ui/accordion';
import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';

const steps = [
  {
    title: 'Configure your environment',
    subtitle: 'Environment ready',
    icon: IconAdjustments,
    description:
      'Set up your development environment with API keys and preferences.',
    buttonText: 'Edit settings',
  },
  {
    title: 'Connect your database',
    subtitle: 'Add connection',
    icon: IconDatabase,
    description:
      'Link your PostgreSQL, MySQL, or MongoDB instance to get started.',
    buttonText: 'Add database',
  },
  {
    title: 'Define your schema',
    subtitle: 'Create schema',
    icon: IconCalculator,
    description: 'Model your data with types, relations, and validation rules.',
    buttonText: 'Open editor',
  },
  {
    title: 'Build your first API',
    subtitle: 'Generate endpoints',
    icon: IconChartBar,
    description:
      'Auto-generate REST or GraphQL endpoints from your schema in seconds.',
    buttonText: 'Generate API',
  },
];

export function Onboarding04() {
  const [activeStep, setActiveStep] = useState(1);

  const [openItem, setOpenItem] = useState(
    activeStep < steps.length ? steps[activeStep].title : ''
  );

  const handleComplete = () => {
    const next = activeStep + 1;
    setActiveStep(next);
    setOpenItem(next < steps.length ? steps[next].title : '');
  };

  return (
    <div className="flex min-h-dvh items-center justify-center bg-background p-4">
      <div className="mx-auto w-full min-w-0 max-w-sm sm:min-w-sm">
        <h3 className="font-semibold text-foreground text-lg">Quick start</h3>
        <p className="mt-1 text-muted-foreground text-sm">
          Set up your backend in four simple steps
        </p>
        <Accordion
          className="mt-6 space-y-2"
          onValueChange={(value) => setOpenItem(value[0] ?? '')}
          value={openItem ? [openItem] : []}
        >
          {steps.map((step, index) => {
            let status: 'complete' | 'current' | 'upcoming' = 'upcoming';
            if (index < activeStep) {
              status = 'complete';
            } else if (index === activeStep) {
              status = 'current';
            }

            return (
              <AccordionItem
                className="!border-b rounded-lg border shadow-xs"
                key={step.title}
                value={step.title}
              >
                <AccordionTrigger className="px-4 hover:no-underline">
                  <div className="flex items-center space-x-2">
                    {status === 'complete' ? (
                      <span
                        aria-hidden
                        className="flex size-5 items-center justify-center"
                      >
                        <IconCircleCheckFilled
                          aria-hidden
                          className="size-6 shrink-0 text-foreground"
                        />
                      </span>
                    ) : (
                      <span
                        aria-hidden
                        className="size-5 shrink-0 rounded-full border border-border"
                      />
                    )}
                    <p
                      className={cn(
                        'font-medium text-sm',
                        status === 'upcoming'
                          ? 'text-muted-foreground/60'
                          : 'text-foreground'
                      )}
                    >
                      {step.title}
                    </p>
                  </div>
                </AccordionTrigger>
                <AccordionContent className="px-4">
                  <div className="flex items-center justify-center rounded-md bg-muted px-4 py-5 ring-1 ring-ring ring-inset">
                    <div className="max-w-xs text-center">
                      <step.icon
                        aria-hidden
                        className="mx-auto size-7 shrink-0 text-muted-foreground"
                      />
                      <p className="mt-4 font-semibold text-muted-foreground text-sm">
                        {step.subtitle}
                      </p>
                      <p className="mt-1 text-muted-foreground text-sm">
                        {step.description}
                      </p>
                      {status === 'complete' ? (
                        <Button className="mt-6" size="sm" variant="outline">
                          {step.buttonText}
                        </Button>
                      ) : (
                        <Button
                          className="mt-6"
                          disabled={status === 'upcoming'}
                          onClick={() => {
                            if (status === 'current') {
                              handleComplete();
                            }
                          }}
                          size="sm"
                        >
                          {step.buttonText}
                        </Button>
                      )}
                    </div>
                  </div>
                </AccordionContent>
              </AccordionItem>
            );
          })}
        </Accordion>
      </div>
    </div>
  );
}
```
