# Key

A button made as a keycap: a cap with a lit top edge on a visible side, which travels down by the side's depth when pressed by pointer, Space or Enter. While its action runs it latches down with a small lamp lit, instead of a spinner, and pops back up when done.

- Category: Essentials
- Has parts that work alone: Icon key
- Docs: https://bevel-ui.vercel.app/components/key
- Import from: `@/components/ui/key`

## Install

```bash
npx shadcn@latest add @bevel/key
```

npm dependencies (installed by the CLI): `@base-ui/react`, `cn`.

## Anatomy

```tsx
<Key variant="primary" pending={saving} onClick={save}>
  <KeyLamp />
  Save changes
</Key>
```

## Parts

| Part | Renders | Description |
| --- | --- | --- |
| `Key` | Button | variant primary (a lit amber key), secondary, ghost (no body until pressed) or danger; size sm, md or lg (32, 36 and 40px tall, side included, like shadcn's buttons). pending latches the key down, sets aria-busy and keeps focus; disabled sinks it into its housing. Render a link with render={<a href="…" />} and nativeButton={false}. Icons from lucide size themselves. |
| `KeyLamp` | span (decorative) | A small lamp on the cap: dark at rest, lit while the key is pending. Put it first or last. |

## Example: Saving a form

Cancel and Save at the foot of a settings form. Save latches down with its lamp lit while the request runs, then pops back up.

```tsx
"use client"

import * as React from "react"
import { Key, KeyLamp } from "@/components/ui/key"

/** A settings form. Save latches down with its lamp lit while the request runs, then pops back up. */
export function KeyForm() {
  const [name, setName] = React.useState("Acme Robotics")
  const [saved, setSaved] = React.useState("Acme Robotics")
  const [saving, setSaving] = React.useState(false)
  const dirty = name.trim() !== saved

  async function save(event: React.FormEvent) {
    event.preventDefault()
    setSaving(true)
    await new Promise((resolve) => setTimeout(resolve, 1400))
    setSaved(name.trim())
    setSaving(false)
  }

  return (
    <form onSubmit={save} className="flex w-full max-w-md flex-col gap-4 rounded-xl border bg-card p-5">
      <div className="flex flex-col gap-1.5">
        <label htmlFor="workspace-name" className="text-sm font-medium">
          Workspace name
        </label>
        <input
          id="workspace-name"
          value={name}
          onChange={(event) => setName(event.target.value)}
          disabled={saving}
          className="h-9 rounded-md border bg-background px-3 text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring"
        />
        <p className="text-xs text-muted-foreground">Shown on invoices and in invitation emails.</p>
      </div>
      <div className="flex items-center justify-end gap-2 border-t pt-4">
        <p role="status" className="mr-auto text-xs text-muted-foreground">
          {saving ? "Saving…" : dirty ? "Unsaved changes" : "All changes saved"}
        </p>
        <Key type="button" variant="ghost" disabled={!dirty || saving} onClick={() => setName(saved)}>
          Cancel
        </Key>
        <Key type="submit" pending={saving} disabled={!dirty}>
          <KeyLamp />
          Save
        </Key>
      </div>
    </form>
  )
}
```

## Example: Variants and sizes

Primary, secondary, ghost and danger at sm, md and lg, with icons, and disabled.

```tsx
import { ArrowRight, Download, Trash2 } from "lucide-react"
import { Key } from "@/components/ui/key"

/** Every variant at every size. Heights match shadcn's buttons, so keys drop into existing layouts. */
export function KeyVariants() {
  return (
    <div className="flex w-full flex-col items-center gap-4">
      {(["sm", "md", "lg"] as const).map((size) => (
        <div key={size} className="flex flex-wrap items-center justify-center gap-3">
          <Key size={size}>
            Continue
            <ArrowRight />
          </Key>
          <Key size={size} variant="secondary">
            <Download />
            Export
          </Key>
          <Key size={size} variant="ghost">
            Skip
          </Key>
          <Key size={size} variant="danger">
            <Trash2 />
            Delete
          </Key>
        </div>
      ))}
      <div className="flex flex-wrap items-center justify-center gap-3">
        <Key disabled>Disabled</Key>
        <Key variant="secondary" disabled>
          Disabled
        </Key>
      </div>
    </div>
  )
}
```
