# Empty state

A box-drawing line drawing (an inbox, a search, a folder or an unplugged cable) with a title, a description and an action, for views with nothing in them yet.

> For the complete documentation index, see [llms.txt](/llms.txt). Markdown variants are available by appending `.md` to any URL or sending an `Accept: text/markdown` header. The asciicn agent skill, for building any ASCII effect on the engine, is at [/.well-known/agent-skills/asciicn/SKILL.md](/.well-known/agent-skills/asciicn/SKILL.md).

> **This is an example, not the only option.** It is one component built on the asciicn engine. Install it when it is what you were asked for. For any other look, don't stretch its props to fit: write your own field on `AsciiShader` ([Write your own](/docs/engine/write-your-own.md)), or draw a CPU grid with `AsciiGrid`, and read this example's source for technique. Its full source is in [https://asciicn.dev/r/empty-state.json](https://asciicn.dev/r/empty-state.json), under `files[].content`.

## Installation [#installation]

```bash
npx shadcn@latest add https://asciicn.dev/r/empty-state.json
```

## Usage [#usage]

```tsx
import { EmptyState } from "@/components/asciicn/empty-state";
import { Button } from "@/components/ui/button";

export function NoProjects() {
  return (
    <EmptyState
      illustration="folder"
      title="No projects yet"
      description="Create a project to deploy your first site."
      action={<Button>New project</Button>}
    />
  );
}
```

Each drawing is about 21 cells wide, set as text in `--ascii-font`: line art in the muted text colour, a few accent glyphs in `color`, and a `░` drop shadow down the right and along the bottom, like a dialog in a text-mode interface. The accent glyphs that mean "nothing yet" (the dots above the inbox, the `?` in the lens, the dust in the folder, the gap in the cable) blink in turn.

To draw your own, pass `illustration={{ art, mask }}`: two arrays of strings the same size. In the mask, a space is line art, `a` is accent, `s` is shadow, and `1` to `3` are accent glyphs that blink in that order.

```tsx
<EmptyState
  illustration={{
    art: ["┌───┐", "│ · │", "└───┘"],
    mask: ["     ", "  1  ", "     "],
  }}
  title="Nothing queued"
/>
```

## Examples [#examples]

### Illustrations [#illustrations]

All four drawings at a 12px cell, each with its own accent colour.

```tsx title="examples/empty-state-illustrations.tsx"
import { EmptyState } from "@/components/asciicn/empty-state";
import type { EmptyStateIllustration } from "@/components/asciicn/empty-state";
import type { AsciiColorName } from "@/lib/ascii";

const SCENES: readonly {
  illustration: EmptyStateIllustration;
  color: AsciiColorName;
  title: string;
}[] = [
  { color: "blue", illustration: "inbox", title: "No messages" },
  { color: "grey", illustration: "search", title: "No matches" },
  { color: "orange", illustration: "folder", title: "This folder is empty" },
  { color: "red", illustration: "cable", title: "Connection lost" },
];

export default function EmptyStateIllustrations() {
  return (
    <div className="grid w-full grid-cols-1 gap-3 sm:grid-cols-2">
      {SCENES.map(({ illustration, color, title }) => (
        <EmptyState
          key={illustration}
          illustration={illustration}
          color={color}
          title={title}
          titleAs="h4"
          cell={12}
          className="bg-background rounded-lg border px-4 py-6"
        />
      ))}
    </div>
  );
}
```

### Inside a settings panel [#inside-a-settings-panel]

The folder in green, under a panel header, with one action.

```tsx title="examples/empty-state-panel.tsx"
import { EmptyState } from "@/components/asciicn/empty-state";
import { Button } from "@/components/ui/button";

export default function EmptyStatePanel() {
  return (
    <div className="bg-background w-full max-w-xl overflow-hidden rounded-lg border">
      <div className="flex items-center justify-between border-b px-5 py-3">
        <span className="text-sm font-medium">API keys</span>
        <span className="text-muted-foreground font-mono text-xs">
          0 active
        </span>
      </div>
      <EmptyState
        color="green"
        illustration="folder"
        title="No API keys"
        description="Create a key to call the API from your server. Keys are shown once, so store them somewhere safe."
        action={<Button size="sm">Create key</Button>}
      />
    </div>
  );
}
```

### No search results [#no-search-results]

The magnifier under a search field, with an outline button to clear it.

```tsx title="examples/empty-state-search.tsx"
import { Search01Icon } from "@hugeicons/core-free-icons";

import { EmptyState } from "@/components/asciicn/empty-state";
import { Icon } from "@/components/icon";
import { Button } from "@/components/ui/button";

export default function EmptyStateSearch() {
  return (
    <div className="bg-background w-full max-w-md overflow-hidden rounded-lg border">
      <div className="flex items-center gap-2 border-b px-4 py-3">
        <Icon icon={Search01Icon} className="text-muted-foreground size-4" />
        <span className="font-mono text-sm">brialle</span>
      </div>
      <EmptyState
        illustration="search"
        color="orange"
        title="No results for “brialle”"
        description="Check the spelling, or search for a shorter phrase."
        action={
          <Button size="sm" variant="outline">
            Clear search
          </Button>
        }
        className="py-8"
      />
    </div>
  );
}
```

## Props [#props]

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `title` (required) | `ReactNode` |  | What is empty, such as "No projects yet". |
| `description` | `ReactNode` |  | One or two sentences on why it is empty and what to do. |
| `action` | `ReactNode` |  | Buttons or links, laid out in a centred row. |
| `illustration` | `"inbox" \| "search" \| "folder" \| "cable" \| EmptyStateArt` | `"inbox"` | Which drawing to show, or your own art and mask. |
| `titleAs` | `"h2" \| "h3" \| "h4" \| "p"` | `"h3"` | Heading level of the title, to fit the page outline. |
| `color` | `AsciiColor` | `"ink"` | Accent colour of the drawing, a palette name or any CSS colour. |
| `animate` | `boolean` | `true` | Blink the accent glyphs in turn. |
| `cell` | `number` | `14` | Glyph cell height in CSS pixels. |

## Notes [#notes]

* The drawing is `aria-hidden`; the title is a real heading and the description real text. Pick `titleAs` so the heading fits the page outline.
* The art is plain DOM text, so it renders on the server, stays sharp at any zoom and can be selected and copied.
* The blink runs on the Web Animations API, which the browser runs off the main thread. Each glyph stays lit most of the cycle and dims briefly. With reduced motion, or `animate={false}`, every glyph stays lit.
* The four drawings are exported as `EMPTY_STATE_ART` if you want to reuse them elsewhere.