# V checkbox 15

> Sprint task checklist with priority badges and completion celebration

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

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

## Install

```bash
npx shadcn@latest add https://ui.cnippet.dev/r/v-checkbox-15.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/checkbox`

## Files

### `registry/default/variants/v-checkbox-15.tsx`

```tsx
"use client";

import { useId, useState } from "react";
import { Badge } from "@/registry/default/ui/badge";
import { Button } from "@/registry/default/ui/button";
import { Checkbox } from "@/registry/default/ui/checkbox";

const TASKS = [
  { id: "t1", label: "Review pull request #142", priority: "high" },
  { id: "t2", label: "Update API documentation", priority: "medium" },
  { id: "t3", label: "Fix login redirect bug", priority: "high" },
  { id: "t4", label: "Write unit tests for AuthService", priority: "medium" },
  { id: "t5", label: "Migrate legacy endpoints to v2", priority: "low" },
  { id: "t6", label: "Deploy staging environment", priority: "low" },
];

const priorityVariant: Record<string, "destructive" | "warning" | "secondary"> =
  {
    high: "destructive",
    low: "secondary",
    medium: "warning",
  };

export function Pattern() {
  const id = useId();
  const [done, setDone] = useState<Set<string>>(new Set());

  const toggle = (tid: string) =>
    setDone((prev) => {
      const next = new Set(prev);
      next.has(tid) ? next.delete(tid) : next.add(tid);
      return next;
    });

  const remaining = TASKS.length - done.size;

  return (
    <div className="w-full max-w-sm space-y-3">
      <div className="flex items-center justify-between">
        <p className="font-semibold text-sm">Sprint Tasks</p>
        <span className="text-muted-foreground text-xs">
          {remaining} remaining
        </span>
      </div>
      <div className="space-y-0.5">
        {TASKS.map((task) => (
          <label
            className={`flex cursor-pointer items-center gap-3 rounded-md px-2 py-2 transition-colors hover:bg-muted/50 ${done.has(task.id) ? "opacity-50" : ""}`}
            htmlFor={`${id}-${task.id}`}
            key={task.id}
          >
            <Checkbox
              checked={done.has(task.id)}
              id={`${id}-${task.id}`}
              onCheckedChange={() => toggle(task.id)}
            />
            <span
              className={`flex-1 text-sm ${done.has(task.id) ? "text-muted-foreground line-through" : ""}`}
            >
              {task.label}
            </span>
            <Badge size="sm" variant={priorityVariant[task.priority]}>
              {task.priority}
            </Badge>
          </label>
        ))}
      </div>
      {remaining === 0 && (
        <div className="rounded-lg bg-emerald-50 px-4 py-2.5 text-center font-medium text-emerald-700 text-sm dark:bg-emerald-950/40 dark:text-emerald-400">
          All tasks complete! 🎉
        </div>
      )}
      <Button
        className="w-full"
        onClick={() => setDone(new Set())}
        size="sm"
        variant="outline"
      >
        Reset
      </Button>
    </div>
  );
}
```
