Installation
pnpm dlx shadcn@latest add https://dithercn.dev/r/empty-state.jsonUsage
import { EmptyState } from "@/components/dithercn/empty-state";
import { Button } from "@/components/ui/button";
export function NoProjects() {
return (
<EmptyState
illustration="folder"
title="No projects yet"
description="Create a project to start building."
action={<Button>New project</Button>}
/>
);
}Above the text sits a small lit object drawn in dither cells. Each surface takes a tone from a light up and to the left, so faces turned to the light are dense, faces turned away are sparse, and curved surfaces step through every Bayer level. A one-cell seam splits the object's parts, a clear margin outlines it, and a faint halo and floor shadow sit behind it.
box: an open box with its flaps folded back. The default, for lists and orders.search: a magnifying glass with a glint on the glass. For search and filters with no results.folder: a folder with a sheet peeking out. For files, projects and documents.planet: a lit sphere with a tilted ring. For feeds, events and anything still to arrive.
matrix changes the pattern: bayer4 for crisp steps, bayer8 for finer ones, blue-noise for an even grain.
Examples
Illustrations
The four objects in four colours.
Inside a settings panel
A first-run state for API keys, with the green folder and a dither button for the action.
No search results
The magnifier in grey, for a state the user will see often. Keep these quiet and give a way out.
Props
| Prop | Type | Default |
|---|---|---|
| title* The heading. Say what is empty, plainly. | Type: ReactNode | Default – |
| description One or two sentences: why it is empty and what to do next. | Type: ReactNode | Default – |
| action Buttons or links, under the text. | Type: ReactNode | Default – |
| illustration The dithered object above the text. | Type: "box" | "search" | "folder" | "planet" | Default "box" |
| color Colour of the illustration: a palette name, a CSS variable name or any CSS colour. | Type: DitherColor | Default "ink" |
| matrix The threshold pattern. | Type: "bayer2" | "bayer4" | "bayer8" | "blue-noise" | Default "bayer4" |
| animate Bob the object up and down by one cell. | Type: boolean | Default true |
| titleAs Heading level of the title. | Type: "h2" | "h3" | "h4" | Default "h3" |
Notes
- The illustration is decorative and hidden from assistive technology. The title is a real heading; set
titleAsto fit the page outline. - The bob moves the object one whole cell in steps, so the dither never smears, and it stops under reduced motion. Each illustration is two SVG paths, computed once per illustration and matrix and cached, so it costs no JavaScript at runtime and renders on the server.
- The illustration keeps a 112 by 72 cell aspect ratio up to 336 pixels wide, so cells are 3 pixels at full size, and it shrinks on narrow screens.