A region that shows nothing still has something to say. `EmptyState` is that sentence: a title
that names the state in the reader's terms, a description that says what would fill it, and the
one action that begins, set where the content would have been, so the absence explains itself in
place rather than in a corner of the page.

What it is not is decoration for a first render. A state that is part of the page's first markup
was not a change, so it announces nothing; a state that replaced content after a query is a
result, and opts into `status`. `role` is that decision, and it is registered as `none`, a product
whose empty states always answer a query states `status` once in configuration, and a state that
was in the page's first markup is left alone.

```tsx demo="feedback/empty-state-preview"
```

## Usage

```tsx
import { EmptyState } from "@asheeui/web";

<EmptyState
  title="No invoices yet"
  description="Invoices you issue appear here, with their status and their total."
  primaryAction={{ label: "New invoice", href: "/invoices/new" }}
/>
```

`title` is the only required prop. `primaryAction` and `secondaryAction` are configuration rather
than nodes: an action that navigates is written as `{ label, href }`, and it renders through the
same `Button` a hand-written action would, the primary action solid, the secondary bordered, both
honouring the application's cascade.

An action that *runs* something is not a destination, so it goes in `actions` as a node. That is
where a handler belongs:

```tsx
<EmptyState
  title="No invoices yet"
  actions={
    <Button variant="solid" onClick={startInvoice}>
      New invoice
    </Button>
  }
/>
```

`actions` renders inside the same group as the configured pair, after it, so a state offering one
destination and one handler still reads as a single row of choices. `children` is the slot below
the actions, for anything else the state needs, the accepted import format, a sentence of help.

## Why it is empty

`type` is the question the state answers, not a colour scheme. `info` is nothing here yet, which is
expected; `success` is an emptiness the work produced; `warning` is an emptiness that wants
attention; `error` is one something failed to fill. The tone colours the badge behind the icon,
which is why a tone given no `icon` shows nothing at all, so pass an icon whenever the reason
matters.

```tsx demo="feedback/empty-state-tones"
```

## An empty result

A state that replaced content is a change, and a change is announced. Give it `role="status"` and a
reader hears the sentence that explains the absence at the next pause, wherever their attention
happens to be. `role="alert"` interrupts, so it belongs only to an emptiness that is itself a
failure the reader has to hear now. Leave `role` at `none`, the registered value, for a state
that was in the page's first markup: announcing a heading that was there from the start describes
something that never happened.

The instance is not the only place the decision can be made. `components.emptystate.role` states it
once for every empty state that does not say otherwise, which is what a product that renders its
empty states in response to a search wants, and the instance still wins over it for the one state
that is part of a first render.

The same rule is what `DataTable` applies to its own empty presentation, a `status` while it
answers a query, silent on first paint.

```tsx demo="feedback/empty-state-result"
```

## Props

```props EmptyState
```

`type`, `size`, `panel` and `role` are the component's own tier: each resolves instance →
`components.emptystate` → the framework's fallback, registered as `info`, `md`, `false` and `none`.
None of them reads the application's `defaultColor` or `defaultRadius`, because the tone of an
absence is not the product's accent, every empty state in an application says the same kind of
thing in the same tone unless the product states otherwise, once, in configuration.

`panel` is the decision worth making deliberately. `false` lets the state sit directly on the page,
which suits a region that *is* the page; `true` draws a surface, which suits a state standing in for
content inside a dashboard, and is what a table uses for "no rows".

`title`, `titleAs`, `description`, `icon`, `primaryAction`, `secondaryAction`, `actions`, `as` and
`children` are instance-only. What a particular absence means, where it sits in the page's outline,
and which action ends it, are statements about one screen rather than about the design.

## Accessibility

- **It is silent by default.** No live-region role is rendered unless the instance, or
  `components.emptystate.role`, asks for one, so a state in the page's first markup is read as
  ordinary content when the reader arrives at it, and never as the announcement of something that
  just changed.
- `role="status"` is the polite channel: after a filter or a query, the title and description are
  announced together at the next pause. `role="alert"` cuts in on whatever is being read, so it is
  reserved for an emptiness that is a failure in its own right.
- **The title is a heading, and `titleAs` states its level.** It is an `<h2>` by default, so the
  state enters the document outline and a reader can reach it by heading navigation; `titleAs`
  states `h1` when the state *is* the page and `h4` when it sits inside a card's `h3`. Only heading
  elements are accepted, so the title keeps its place in the outline whatever it is set to.
- The configured actions render as a real `<button>` or `<a>`, in the tab order after whatever
  precedes them, and `actions` children keep the semantics they were given. Nothing here moves
  focus: the state reports, and the reader decides whether to act.
- The icon's badge is not hidden from assistive technology, so give a glyph or an image inside it
  `aria-hidden`, the title and description already say what the state is, and an unmarked glyph
  otherwise joins the announced sentence.

The two channels, and which surface announces what, are
[Announcements](/concepts/announcements).

## Related

```related error-state, loading-state, data-table
```
