# Not found

A 404 page whose code is drawn in sand: gusts wear its edges and grains drift downwind and settle, over a terminal line that echoes the missing path.

> 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/not-found.json](https://asciicn.dev/r/not-found.json), under `files[].content`.

## Installation [#installation]

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

## Usage [#usage]

In Next.js, render it from `app/not-found.tsx`. That file handles unknown URLs and every `notFound()` call, and Next.js answers with a 404 status.

```tsx
import Link from "next/link";

import { NotFound } from "@/components/asciicn/not-found";
import { Button } from "@/components/ui/button";

export default function NotFoundPage() {
  return (
    <main className="flex min-h-svh flex-col items-center justify-center">
      <NotFound
        actions={
          <>
            <Button asChild>
              <Link href="/">Back home</Link>
            </Button>
            <Button asChild variant="outline">
              <Link href="/docs">Browse the docs</Link>
            </Button>
          </>
        }
      />
    </main>
  );
}
```

The code is set in the page's own typeface at a heavy weight, then sampled into glyphs. Each sample knows how far it sits from the edge of a digit. Two gusts per loop sweep across the code and wear its edges down a few samples deep, so it changes shape without ever breaking up. Grains lift off the tops and right-hand faces, arc downwind in `accent` and fade as they settle, and the hole each one leaves fills back in as it lands. Faint wind ripples run through the sand. The loop lasts 16 seconds.

The terminal line types `$ cd <path>`, answers `cd: no such file or directory: <path>` and waits at a blinking cursor. Without `path` it reads `window.location.pathname` after mount, so the server markup and the first client render match. `command` and `message` change the two lines, for a failed request rather than a missing page:

```tsx
<NotFound
  code="500"
  title="Something broke"
  path="/api/orders"
  command="curl -f"
  message="(22) the server returned 500"
/>
```

## Examples [#examples]

### Something broke [#something-broke]

A 500 page with a `curl` line, a red accent and a stronger wind.

```tsx title="examples/not-found-error.tsx"
import { NotFound } from "@/components/asciicn/not-found";
import { Button } from "@/components/ui/button";

export default function NotFoundError() {
  return (
    <NotFound
      code="500"
      titleAs="h3"
      title="Something broke"
      description="The server hit an error it could not recover from. Try again in a moment; we have been told."
      path="/api/orders"
      command="curl -f"
      message="(22) the server returned 500"
      accent="red"
      wind={1.4}
      actions={
        <>
          <Button size="sm">Try again</Button>
          <Button size="sm" variant="outline">
            Status page
          </Button>
        </>
      }
    />
  );
}
```

### Inside a card [#inside-a-card]

`compact` shrinks the art, the terminal line and the type, for a missing record inside an app.

```tsx title="examples/not-found-card.tsx"
import { NotFound } from "@/components/asciicn/not-found";
import { Button } from "@/components/ui/button";

export default function NotFoundCard() {
  return (
    <div className="bg-background w-full max-w-md overflow-hidden rounded-lg border">
      <div className="flex items-center justify-between border-b px-5 py-3">
        <span className="text-sm font-medium">Projects</span>
        <span className="text-muted-foreground font-mono text-xs">atlas-7</span>
      </div>
      <NotFound
        compact
        titleAs="h3"
        title="No such project"
        description="It was renamed or deleted, or you lost access to it."
        path="~/projects/atlas-7"
        actions={<Button size="sm">All projects</Button>}
      />
    </div>
  );
}
```

### Classic ramp [#classic-ramp]

The `standard` charset at a 12px cell. The sand thins from `@` to `.` toward its edges, and the grains in flight are `.` and `:`.

```tsx title="examples/not-found-classic.tsx"
import { NotFound } from "@/components/asciicn/not-found";
import { Button } from "@/components/ui/button";

export default function NotFoundClassic() {
  return (
    <NotFound
      charset="standard"
      cell={12}
      titleAs="h3"
      title="Page not found"
      description="The classic ramp: the sand thins from @ to . at its edges."
      path="/docs/fields/sand"
      actions={
        <Button size="sm" variant="outline">
          Back to docs
        </Button>
      }
    />
  );
}
```

## Props [#props]

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `code` | `string` | `"404"` | The code drawn in sand. |
| `title` | `ReactNode` | `"Page not found"` | The heading under the art. |
| `description` | `ReactNode` | `A line about a moved or mistyped link` | One or two sentences under the heading. |
| `path` | `string` | `window.location.pathname` | The path echoed in the terminal line. Read after mount when omitted. |
| `command` | `string` | `"cd"` | The command typed before the path. |
| `message` | `string` | `"no such file or directory"` | The error the command prints. |
| `actions` | `ReactNode` |  | Buttons or links, laid out in a centred row. |
| `titleAs` | `"h1" \| "h2" \| "h3" \| "p"` | `"h1"` | Heading level of the title, to fit the page outline. |
| `accent` | `AsciiColor` | `"orange"` | Colour of the drifting grains, the prompt and the cursor: a palette name or any CSS colour. |
| `wind` | `number` | `1` | How hard the wind blows, from 0 (still sand, no grains) to 2. |
| `compact` | `boolean` | `false` | A smaller layout for cards and panels. |
| `charset` | `CharsetName` | `"braille"` | Glyphs the sand is drawn with. Braille draws one dot per grain. |
| `colorMode` | `"mono" \| "truecolor" \| "ansi16" \| "duotone" \| "palette"` | `"truecolor"` | `truecolor` draws the sand in ink and the grains in `accent`; `mono` draws both in ink. |
| `dither` | `"none" \| DitherMatrix` | `"blue-noise" for Braille and quadrants, "none" for ramps` | Ordered dithering. Blue noise turns the thinning edges into a stipple of loose grains. |
| `cell` | `number` | `10, or 8 when compact` | Glyph cell height of the art in CSS pixels. |
| `speed` | `number` | `1` | Time multiplier for the wind. |
| `animate` | `boolean` | `true` | `false` holds a still frame, prints the terminal line at once and stops the cursor blinking. |
| `stillTime` | `number` | `5.5` | The moment drawn when motion is off or reduced, in seconds. |
| `chars, phosphor, ramp, ansi, grade, invert, threshold, edgeThreshold, background, fx` | `AsciiLook` |  | The shared look props. See [the engine](/docs/engine/ascii-engine). |
| `handle` | `RefObject<AsciiHandle \| null>` |  | Filled with a handle for export. |

## Notes [#notes]

* The art is a canvas of real glyphs and is labelled with the code (`role="img"`). The terminal line, the title and the description are DOM text, so they are readable, selectable and translatable. Screen readers get the error line once, not letter by letter.
* The motion is a function of time on a 16 second loop, so a still frame needs no history. Under `prefers-reduced-motion` the art holds the `stillTime` frame, the terminal line prints at once and the cursor stays lit.
* The art pauses offscreen and in hidden tabs, and follows the theme: ink glyphs on the page, the accent from the `--ascii-color-*` tokens.
* The code fits its box, so a three-digit code and a word such as `"gone"` both work. Short codes read best.