# Delete workspace

> A danger-zone delete with a ghost: as you type the name, Boo climbs out of the workspace’s tile, and holding Delete waves him off the stage.

Deleting a workspace is usually a red box, a sentence in bold and a field where you type the name. This one gives the moment its weight. The workspace’s own tile sits under a spotlight on the lit stage, and Boo, an illustrated ghost, lives inside it. Every letter of the name you type brings him further up out of the tile while the tile drains of colour; type a letter that doesn’t belong and he shivers and sinks back to where the name still matches. With the whole name right, Delete arms. Hold it and Boo waves goodbye and drifts up out of the stage over the hold; let go early and he drops back. Once it’s deleted the tile is an empty dashed outline, and a moment later Boo peeks up over the bottom edge of the stage. Restore brings him diving back into his tile, shy, as the colour comes back. Under the ghost it is a careful delete: nothing happens until the exact name is typed and the hold completes, the consequences are listed, and onDelete and onRestore are your endpoints.

## Install

```bash
npx shadcn@latest add https://hairlineui.com/r/delete-workspace.json
```

Needs React 19, Tailwind CSS v4 and shadcn/ui theme variables (any style, light and dark). Icons from lucide-react.

- npm: `lucide-react`
- Hairline UI components it needs (the CLI installs them too): `hold-to-confirm`, `text-morph`, `input`, `status-button`
- Docs and live preview: https://hairlineui.com/ui/delete-workspace
- Registry item (source): https://hairlineui.com/r/delete-workspace.json

## Usage

```tsx
import { DeleteWorkspace } from "@/components/delete-workspace";

<DeleteWorkspace
  name={workspace.name}
  onDelete={() => fetch(`/api/workspaces/${workspace.id}`, { method: "DELETE" })}
/>
```

## With a grace period

Soft-delete on the server, restore within the window, and list what goes with it.

```tsx
"use client";
import { DeleteWorkspace } from "@/components/delete-workspace";

export function DangerZone({ workspace }: { workspace: { id: string; name: string; logo?: string; projects: number; members: number } }) {
  return (
    <DeleteWorkspace
      name={workspace.name}
      avatar={workspace.logo}
      consequences={[`${workspace.projects} projects and their history`, `${workspace.members} members lose access`, "Billing stops today"]}
      onDelete={async () => {
        const res = await fetch(`/api/workspaces/${workspace.id}`, { method: "DELETE" });
        if (!res.ok) throw new Error("Couldn’t delete it");
      }}
      onRestore={async () => {
        const res = await fetch(`/api/workspaces/${workspace.id}/restore`, { method: "POST" });
        if (!res.ok) throw new Error("Couldn’t restore it");
      }}
    />
  );
}
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `name` | `string` | – | What has to be typed, exactly, before Delete arms. |
| `onDelete` | `() => Promise<unknown>` | – | Delete it. Throw to keep it and offer another hold. |
| `onRestore` | `() => Promise<unknown>` | – | Shows Restore after the delete; throw to say it didn’t work. |
| `consequences` | `string[]` | – | What goes with it, one short line each. |
| `avatar / color` | `string` | `— / var(--chart-1)` | Its logo, or the colour behind its initials. |
| `kind` | `string` | `"workspace"` | The heading says Delete workspace, project, account… |
| `graceDays` | `number` | `30` | How long Restore is possible, in the line after the delete. |
| `holdMs` | `number` | `1500` | How long Delete has to be held. |

## States

- **safe**: The workspace’s tile under a spotlight on the lit stage; “Northwind is safe and sound”. Boo is out of sight inside it.
- **typing**: Each matching letter springs Boo further up out of the tile (worried, hands clasped, floating), while the tile turns grey and dims; “Northwind is fading”.
- **wrong letter**: He shivers and sinks back to the part of the name that still matches.
- **armed**: The whole name typed: Boo is fully out, the button turns red and reads Hold to delete; “Last chance for Northwind”.
- **holding**: He switches to waving goodbye and drifts up out of the stage over the hold, slowly at first; releasing early springs him back.
- **deleted**: Deleting → Deleted; the tile shrinks away into an empty dashed outline, the form folds into “Northwind was deleted” with Restore, and Boo peeks up over the bottom edge.
- **restored**: Restore: Boo dives back down into his tile, shy and blushing, the colour floods back and the form returns empty.
- **not deleted**: If onDelete throws, the button shows the error and Hold to try again, and Boo drops back into view.

## What it handles

A delete step usually relies on a bold warning nobody reads. This block makes the name you type and the hold you keep the warning itself, and it leaves a way back.

- **The exact name**: Delete stays off until the name is typed exactly; a wrong letter visibly undoes progress.
- **A hold, not a click**: Delete has to be held (1.5s by default), so a stray click or Enter never deletes anything.
- **What goes with it**: consequences lists what’s lost, one short line each.
- **A way back**: With onRestore the deleted state offers Restore and says how long it’s possible.
- **Failures kept in place**: If onDelete throws, Boo drops back into view and the button offers another hold.
- **Its own tile**: The workspace’s logo, or its initials on its colour, is what fades and empties.

## Replaces

a type-the-name confirm, a hold-to-delete button, a consequences list, a deleted state, a restore button

## Where it fits

- **Workspace settings**: The danger zone at the bottom of a team’s settings.
- **Delete account**: Closing a personal account, with a grace period to change your mind.
- **Projects**: Removing a project and everything in it.
- **Repositories and databases**: Anything that needs its exact name typed before it goes.

## Notes

- The name field is a real labelled input; Delete is Hold to confirm, which works with the pointer, Space and Enter, and Escape or letting go cancels it.
- A status region says when the name matches and when the workspace was deleted.
- Boo is decoration (aria-hidden). His four poses are illustrations shipped inside boo-art.ts as WebP data URIs (33KB of images), so the install is complete; his rise, shiver, hold, peek and dive are code.
- One frame loop outside React moves him and sleeps when he’s still; his float is a compositor animation. The scene is drawn at 364 × 184 and scaled to the stage.
- With reduced motion he moves in place without travelling, and the form and tile change without slides.
- Built from Hold to confirm, Input, Status button and Text morph, which the CLI installs with it. No animation library.

License: MIT (https://hairlineui.com/license#components)
