# V sheet 15

> API Key Manager

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

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

## Install

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

## Files

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

```tsx
"use client";

import { KeyRoundIcon, PlusIcon, RotateCcwIcon, TrashIcon } from "lucide-react";
import { useState } from "react";
import { Badge } from "@/registry/default/ui/badge";
import { Button } from "@/registry/default/ui/button";
import {
  Sheet,
  SheetClose,
  SheetDescription,
  SheetFooter,
  SheetHeader,
  SheetPanel,
  SheetPopup,
  SheetTitle,
  SheetTrigger,
} from "@/registry/default/ui/sheet";

type ApiKey = {
  id: number;
  label: string;
  created: string;
  lastUsed: string;
  prefix: string;
};

const initial: ApiKey[] = [
  {
    created: "Jan 3, 2025",
    id: 1,
    label: "Production",
    lastUsed: "2 minutes ago",
    prefix: "sk_live_••••••••••••••••",
  },
  {
    created: "Feb 14, 2025",
    id: 2,
    label: "Development",
    lastUsed: "Yesterday",
    prefix: "sk_test_••••••••••••••••",
  },
];

export default function Particle() {
  const [keys, setKeys] = useState<ApiKey[]>(initial);
  const [rotating, setRotating] = useState<number | null>(null);

  const revoke = (id: number) =>
    setKeys((prev) => prev.filter((k) => k.id !== id));

  const rotate = async (id: number) => {
    setRotating(id);
    await new Promise((r) => setTimeout(r, 800));
    setRotating(null);
  };

  return (
    <Sheet>
      <SheetTrigger render={<Button variant="outline" />}>
        <KeyRoundIcon aria-hidden="true" />
        API Keys
      </SheetTrigger>
      <SheetPopup>
        <SheetHeader>
          <SheetTitle>API Keys</SheetTitle>
          <SheetDescription>
            Manage your secret keys. Never share them publicly.
          </SheetDescription>
        </SheetHeader>
        <SheetPanel className="space-y-3">
          {keys.map((key) => (
            <div className="rounded-lg border p-3" key={key.id}>
              <div className="flex items-center justify-between gap-2">
                <div className="flex items-center gap-2">
                  <p className="font-medium text-sm">{key.label}</p>
                  <Badge className="text-[10px]" variant="outline">
                    {key.id === 1 ? "Live" : "Test"}
                  </Badge>
                </div>
                <div className="flex items-center gap-1">
                  <Button
                    aria-label="Rotate key"
                    className="size-7"
                    disabled={rotating === key.id}
                    onClick={() => rotate(key.id)}
                    size="icon"
                    variant="ghost"
                  >
                    <RotateCcwIcon
                      aria-hidden="true"
                      className={`size-3.5 ${rotating === key.id ? "animate-spin" : ""}`}
                    />
                  </Button>
                  <Button
                    aria-label="Revoke key"
                    className="size-7 text-destructive hover:text-destructive"
                    onClick={() => revoke(key.id)}
                    size="icon"
                    variant="ghost"
                  >
                    <TrashIcon aria-hidden="true" className="size-3.5" />
                  </Button>
                </div>
              </div>
              <p className="mt-1.5 font-mono text-muted-foreground text-xs">
                {key.prefix}
              </p>
              <div className="mt-2 flex items-center gap-3 text-muted-foreground text-xs">
                <span>Created {key.created}</span>
                <span>·</span>
                <span>Last used {key.lastUsed}</span>
              </div>
            </div>
          ))}
        </SheetPanel>
        <SheetFooter>
          <SheetClose render={<Button variant="ghost" />}>Close</SheetClose>
          <Button>
            <PlusIcon aria-hidden="true" />
            Create New Key
          </Button>
        </SheetFooter>
      </SheetPopup>
    </Sheet>
  );
}
```
