# V accordion 14

> Onboarding step tracker with completion state

A shadcn/ui block from the [@cnippet](https://ui.cnippet.dev/) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/cnippet/v-accordion-14).

- Page, with a live preview: https://shadcn.exchange/r/cnippet/v-accordion-14
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/cnippet/v-accordion-14-light-07c4602cca8a28ed.png
- Registry item: https://ui.cnippet.dev/r/v-accordion-14.json
- Categories: accordion
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://ui.cnippet.dev/r/v-accordion-14.json
```

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

## Dependencies

- Registry items: `@cnippet/accordion`

## Files

### `registry/default/variants/v-accordion-14.tsx`

```tsx
"use client";

import { CheckIcon } from "lucide-react";
import { useState } from "react";
import {
  Accordion,
  AccordionContent,
  AccordionItem,
  AccordionTrigger,
} from "@/registry/default/ui/accordion";
import { Button } from "@/registry/default/ui/button";

const steps = [
  {
    description:
      "Give your workspace a name and upload a logo. This is how your team will identify the space.",
    title: "Create your workspace",
    value: "step-1",
  },
  {
    description:
      "Add teammates by email. They'll receive an invite link and can join immediately.",
    title: "Invite your team",
    value: "step-2",
  },
  {
    description:
      "Integrate with Slack, GitHub, Jira, and 50+ other tools. Connections can be updated anytime.",
    title: "Connect your tools",
    value: "step-3",
  },
  {
    description:
      "Add a payment method to keep access after your 14-day trial ends. Cancel anytime.",
    title: "Set up billing",
    value: "step-4",
  },
];

export function Pattern() {
  const [completed, setCompleted] = useState<string[]>(["step-1"]);
  const [opened, setOpened] = useState<string[]>(["step-2"]);

  const markDone = (value: string, index: number) => {
    setCompleted((prev) => [...prev, value]);
    const next = steps[index + 1];
    if (next) setOpened([next.value]);
  };

  return (
    <div className="mx-auto w-full max-w-md">
      <div className="mb-4">
        <h2 className="font-semibold text-base">Getting started</h2>
        <p className="text-muted-foreground text-sm">
          {completed.length} of {steps.length} steps completed
        </p>
      </div>
      <Accordion
        className="w-full space-y-2 border-none"
        multiple
        onValueChange={setOpened}
        value={opened}
      >
        {steps.map((step, i) => {
          const done = completed.includes(step.value);
          return (
            <AccordionItem
              className={`rounded-lg border px-4 last:border-b ${
                done
                  ? "border-emerald-200 bg-emerald-50/50 dark:border-emerald-900 dark:bg-emerald-950/30"
                  : ""
              }`}
              key={step.value}
              value={step.value}
            >
              <AccordionTrigger className="hover:no-underline">
                <div className="flex items-center gap-3">
                  <div
                    className={`flex size-6 shrink-0 items-center justify-center rounded-full font-semibold text-xs ${
                      done
                        ? "bg-emerald-500 text-white"
                        : "border-2 bg-background text-muted-foreground"
                    }`}
                  >
                    {done ? <CheckIcon className="size-3.5" /> : i + 1}
                  </div>
                  <span className="font-medium text-sm">{step.title}</span>
                </div>
              </AccordionTrigger>
              <AccordionContent className="pb-4 pl-9">
                <p className="mb-3 text-muted-foreground text-sm">
                  {step.description}
                </p>
                {!done && (
                  <Button onClick={() => markDone(step.value, i)} size="sm">
                    Mark as complete
                  </Button>
                )}
              </AccordionContent>
            </AccordionItem>
          );
        })}
      </Accordion>
    </div>
  );
}
```
