Empty state

.empty

Empty state — the panel that stands in for a list with nothing in it: a decorative mark, a heading, one line of explanation, and usually one action.

Examples

Nothing here yet

View “Nothing here yet” as

No invoices yet

Invoices you create appear here, newest first.

<div class="empty">
  <h3 class="empty-title">No invoices yet</h3>
  <p class="empty-description">Invoices you create appear here, newest first.</p>
  <div class="empty-actions">
    <button class="btn btn-primary" type="button">New invoice</button>
  </div>
</div>

A heading, a sentence, and the one action that ends the state — that is the whole component, and there is deliberately nothing above it.

.empty-media is still there if your product has art or a glyph worth showing, and --empty-media-size sizes it; the default examples leave it out because on a component whose job is to be quiet, the picture is the largest thing on the screen and the first thing you stop reading past.

A filter that matched nothing

View “A filter that matched nothing” as

No results for “kingfisher”

Try a shorter word, or clear the filter.

<div class="empty">
  <h3 class="empty-title">No results for “kingfisher”</h3>
  <p class="empty-description">Try a shorter word, or clear the filter.</p>
  <div class="empty-actions">
    <button class="btn" type="button">Clear filter</button>
  </div>
</div>

Two empty states that look alike and are not: "you have not made one yet" offers the action that creates one, and "your filter matched nothing" offers the action that clears the filter.

Getting them the wrong way round is the usual failure — a "Create invoice" button on a search that found nothing is an answer to a question nobody asked. An empty state is never a live region: it is what the region contains, not an announcement.

Tokens 17

Level 2, declared on .empty itself. Set any of them on that selector to restyle this component without touching the skin.

Level 2 tokens declared by empty-state
TokenDefault
--empty-actions-gapvar(--space-2)
--empty-description-colorvar(--color-on-surface-muted)
--empty-description-font-sizevar(--font-size-sm)
--empty-description-measurevar(--measure-narrow)
--empty-gapvar(--space-3)
--empty-media-colorvar(--color-on-surface-subtle)
--empty-media-size2.5rem
--empty-padding-blockvar(--space-12)
--empty-padding-inlinevar(--space-6)
--empty-title-colorvar(--color-on-surface)
--empty-title-font-sizevar(--font-size-lg)
--empty-bgtransparent
--empty-border-colortransparent
--empty-border-stylesolid
--empty-border-widthvar(--border-width)
--empty-colorvar(--color-on-surface)
--empty-radiusvar(--radius-box)

Variants and states

Variants

  • .empty-boxed
  • .empty-error
  • .empty-lg
  • .empty-sm

Inside it

  • .empty-media
  • .empty-actions
  • .empty-description
  • .empty-title

State it reads

None.

Before you ship it

What you have to do 4 requirements

  1. Put .empty-title on a real heading at the level the page's outline calls for. See above for each of the three ways to get it wrong.
  2. Put aria-hidden="true" on the illustration. Nothing in CSS can add it and nothing can detect its absence.
  3. Put role="status" on the container that swaps, not on .empty, and have that container in the page before the first search.
  4. Use .empty-error for a load failure and nothing else. It is the one case where the panel means something is wrong.

src/css/components/empty-state.css · npx mostlycss add empty-state