# Empty

Fills the space of a list, table or page that has nothing to show yet, and says what to do next.

- **Import:** `import { Empty, EmptyHeader, EmptyTitle, EmptyDescription, EmptyContent, EmptyMedia } from "@booleanpress/ui/empty"`
- **Page:** <https://ui.booleanpress.com/components/empty> · @booleanpress/ui 0.1.0

## Usage

Build the message from its parts: a header with an optional icon, a title and a description, then the content for the actions.

```tsx
import { MailIcon } from "lucide-react"
import { Button } from "@booleanpress/ui/button"
import {
  Empty,
  EmptyContent,
  EmptyDescription,
  EmptyHeader,
  EmptyMedia,
  EmptyTitle,
} from "@booleanpress/ui/empty"

export function NoEmails() {
  return (
    <Empty>
      <EmptyHeader>
        <EmptyMedia variant="icon">
          <MailIcon />
        </EmptyMedia>
        <EmptyTitle>No emails yet</EmptyTitle>
        <EmptyDescription>Emails appear here as soon as your site sends one.</EmptyDescription>
      </EmptyHeader>
      <EmptyContent>
        <Button>Send a test email</Button>
      </EmptyContent>
    </Empty>
  )
}
```

`Empty` has no border or background of its own, only a dashed border style: add `border` for the dashed frame. `EmptyMedia` takes `variant="icon"` for an icon in a rounded tile, or the default for a bare icon or picture.

Say which kind of empty it is. A first run (nothing created yet) offers the action that creates; a search with no result offers to clear the search; an error is not an empty state, and belongs in an `Alert`.

## Examples

### Basic

An icon tile, a title, a description and one action.

```tsx
import { MailIcon } from "lucide-react"
import { Button } from "@booleanpress/ui/button"
import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "@booleanpress/ui/empty"

export default function EmptyBasic() {
  return (
    <Empty>
      <EmptyHeader>
        <EmptyMedia variant="icon">
          <MailIcon />
        </EmptyMedia>
        <EmptyTitle>No emails yet</EmptyTitle>
        <EmptyDescription>Emails appear here as soon as your site sends one.</EmptyDescription>
      </EmptyHeader>
      <EmptyContent>
        <Button>Send a test email</Button>
      </EmptyContent>
    </Empty>
  )
}
```

### Bordered

`className="border"` draws the dashed frame; two actions sit side by side.

```tsx
import { PlugIcon } from "lucide-react"
import { Button } from "@booleanpress/ui/button"
import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "@booleanpress/ui/empty"

export default function EmptyBordered() {
  return (
    <Empty className="w-full max-w-md border">
      <EmptyHeader>
        <EmptyMedia variant="icon">
          <PlugIcon />
        </EmptyMedia>
        <EmptyTitle>No mailers connected</EmptyTitle>
        <EmptyDescription>Connect a mailer to send your site's email through it.</EmptyDescription>
      </EmptyHeader>
      <EmptyContent className="flex-row justify-center gap-2">
        <Button>Add a mailer</Button>
        <Button variant="outline">Read the guide</Button>
      </EmptyContent>
    </Empty>
  )
}
```

### No results

A bare icon and a way back out of a search that found nothing.

```tsx
import { SearchIcon } from "lucide-react"
import { Button } from "@booleanpress/ui/button"
import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "@booleanpress/ui/empty"

export default function EmptyNoResults() {
  return (
    <Empty>
      <EmptyHeader>
        <EmptyMedia>
          <SearchIcon className="size-8 text-muted-foreground" />
        </EmptyMedia>
        <EmptyTitle>No people match "grace"</EmptyTitle>
        <EmptyDescription>Check the spelling, or search by email address.</EmptyDescription>
      </EmptyHeader>
      <EmptyContent>
        <Button variant="outline">Clear the search</Button>
      </EmptyContent>
    </Empty>
  )
}
```

## Accessibility

**Semantics.** Plain `div`s, with no roles. `EmptyTitle` and `EmptyDescription` are not headings or paragraphs, so the page's heading levels are untouched.

**Labels.** The text is the content. Wrap the title in the heading level your page needs if the empty state is the main content of a section.

**Focus.** Not focusable; the buttons inside it are in the tab order. Nothing moves focus when the empty state appears.

**Known limits.**

- An empty state that replaces a list after a search or filter is not announced. Announce the result count in a live region the page owns.
- `EmptyTitle` and `EmptyDescription` are `div`s (stock), so a screen reader's heading navigation skips them.

### Keyboard

| Key | Behaviour |
| --- | --- |

## API

### Empty

Renders a `div` and passes it every other prop.

### EmptyHeader

Renders a `div` and passes it every other prop.

### EmptyTitle

Renders a `div` and passes it every other prop.

### EmptyDescription

Renders a `p` and passes it every other prop.

### EmptyContent

Renders a `div` and passes it every other prop.

### EmptyMedia

Renders a `div` and passes it every other prop.

Every part takes `className`, merged with its defaults by `cn()`, and `ref`, which reaches the element it renders.

**Data attributes:** `data-slot="empty"` (Empty), `data-slot="empty-header"` (EmptyHeader), `data-slot="empty-title"` (EmptyTitle), `data-slot="empty-description"` (EmptyDescription), `data-slot="empty-content"` (EmptyContent), `data-slot="empty-icon"` (EmptyMedia), and `data-variant`.

## Theming

The tile is `--muted`, the description `--muted-foreground`. The dashed border takes its colour from `--border` when you add `border`.

| Token | Used for |
| --- | --- |
| `--foreground` | text |
| `--muted` | background |
| `--muted-foreground` | text |
| `--primary` | text |
