# Empty state

> A block shown in place of a list, table or result set with no rows, with an icon, a message and actions.

Page: https://pico.rusticstack.com/docs/extras/empty-state

An empty state is a `section[data-empty]` shown in place of a list, table or result set with no rows. It is for "nothing here yet" and "nothing matches". A failed load is an error, not an empty state, so use an [alert](/docs/extras/alert) for that.

Load `css/extras/empty-state.min.css`, or the whole set from `css/pico.extras.min.css`. See the [overview](/docs/extras/overview).

## Default

An optional `figure` with `aria-hidden="true"` holds the icon. After it comes a heading, a `p` and a `footer` with one or two actions. Any of those can be left out.

**Example: Empty state**

```html
<section data-empty>
  <figure aria-hidden="true">
    <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
      stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" focusable="false">
      <path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
    </svg>
  </figure>
  <h3>No projects yet</h3>
  <p>Create a project to start tracking your work here.</p>
  <footer>
    <a href="/projects/new" role="button">Create project</a>
  </footer>
</section>
```

## Two actions

**Example: Empty state with two actions**

```html
<section data-empty>
  <figure aria-hidden="true">
    <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
      stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" focusable="false">
      <path d="M4 4h16v16H4z" />
      <path d="M4 14h4l2 3h4l2-3h4" />
    </svg>
  </figure>
  <h3>Your inbox is empty</h3>
  <p>
    Invite teammates to start a conversation, or import your existing threads from another
    tool.
  </p>
  <footer>
    <button type="button">Invite teammates</button>
    <button type="button" class="secondary outline">Import threads</button>
  </footer>
</section>
```

## Text only

**Example: Text only**

```html
<section data-empty>
  <h3>No activity this week</h3>
  <p>Events from your workspace will show up here.</p>
</section>
```

## Compact

`data-empty="compact"` has less padding and a smaller icon. It suits cards, panels and lists.

**Example: Compact empty state**

```html
<article>
  <header><h3>Notifications</h3></header>
  <section data-empty="compact">
    <p>No notifications.</p>
  </section>
</article>
```

## No results after filtering

Keep the result count in a `role="status"` element outside the empty state, so the change is announced. Label the empty state with the heading it contains.

**Example: Nothing matches**

```html
<form role="search" onsubmit="return false">
  <input type="search" name="q" value="beta" aria-label="Search projects" />
</form>
<p role="status" id="results-count">0 projects match "beta"</p>
<section data-empty="compact" aria-labelledby="no-match-title">
  <figure aria-hidden="true">
    <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
      stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" focusable="false">
      <circle cx="11" cy="11" r="7" />
      <path d="m20 20-3.5-3.5" />
    </svg>
  </figure>
  <h3 id="no-match-title">Nothing matches your search</h3>
  <p>Check the spelling or try another term.</p>
  <footer>
    <button type="button" class="secondary">Clear search</button>
  </footer>
</section>
```

## Inside a table

To keep the table header, put a compact empty state in one cell that spans every column. The cell loses its padding and the state loses its own frame, which becomes the table's.

**Example: Empty table**

```html
<figure>
  <table>
    <thead>
      <tr>
        <th scope="col">Invoice</th>
        <th scope="col">Customer</th>
        <th scope="col">Amount</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td colspan="3">
          <section data-empty="compact">
            <p>No invoices yet.</p>
          </section>
        </td>
      </tr>
    </tbody>
  </table>
</figure>
```

## Toggling with a list

Use the `hidden` attribute to switch between the list and the empty state. Keep the `role="status"` count outside the hidden regions, and do not move focus.

```js
const list = document.querySelector("#projects");
const empty = document.querySelector("#projects-empty");
function syncEmpty() {
  const count = list.children.length;
  list.hidden = count === 0;
  empty.hidden = count > 0;
}
syncEmpty(); // call after every change to the list
```

## Accessibility

- The icon is decorative, so the `figure` has `aria-hidden="true"`.
- The heading and text say what is missing and what to do next. Do not rely on the icon.
- Actions are real links and buttons in the `footer`, so they are in the tab order.
- An empty state with `hidden` is not displayed and not announced.

## Customization

| Variable | Default | Description |
| --- | --- | --- |
| `--pico-empty-state-max-inline-size` | `28rem` | Maximum width of the block. |
| `--pico-empty-state-padding-block` | `calc(var(--pico-block-spacing-vertical) * 2)` | Vertical padding. The compact value uses var(--pico-spacing). |
| `--pico-empty-state-icon-size` | `1.5rem` | Size of the icon. The compact value is 1.25rem. |
| `--pico-empty-state-icon-color` | `var(--pico-muted-color)` | Icon color. |
| `--pico-empty-state-icon-background` | `var(--pico-card-sectioning-background-color)` | Circle behind the icon. |
| `--pico-empty-state-border-color` | `var(--pico-form-element-border-color)` | Color of the dashed frame. |

