Skip to content

Empty state

An empty state is a centered placeholder for “nothing here yet” or “no results”: an optional decorative icon, a title, a description, and an optional action area. CSS-only. See Accessibility for the heading level and decorative-icon guidance.

No projects yet

Create your first project to start tracking work.

Learn more
Live example
<div class="re-empty-state">
  <div class="re-empty-state__icon" aria-hidden="true">
    <svg viewBox="0 0 24 24" width="48" height="48" fill="none" focusable="false">
      <path
        d="M3 8.5 6 4h12l3 4.5M3 8.5V19a1 1 0 0 0 1 1h16a1 1 0 0 0 1-1V8.5M3 8.5h6l1.5 3h3L15 8.5h6"
        stroke="currentColor"
        stroke-width="1.5"
        stroke-linejoin="round"
      />
    </svg>
  </div>
  <h3 class="re-empty-state__title">No projects yet</h3>
  <p class="re-empty-state__description">Create your first project to start tracking work.</p>
  <div class="re-empty-state__actions">
    <button class="re-button" data-variant="primary" type="button">New project</button>
    <a class="re-button" data-variant="secondary" href="#docs">Learn more</a>
  </div>
</div>

data-size="sm" shrinks the padding, icon and type for cards and table cells.

No results

Try a different search term.

Live example
<div class="re-empty-state" data-size="sm">
  <h3 class="re-empty-state__title">No results</h3>
  <p class="re-empty-state__description">Try a different search term.</p>
</div>

data-bordered adds a dashed panel border — handy for upload affordances.

Drop files to upload

or click to browse from your computer.

Live example
<div class="re-empty-state" data-bordered>
  <div class="re-empty-state__icon" aria-hidden="true">
    <svg viewBox="0 0 24 24" width="48" height="48" fill="none" focusable="false">
      <path
        d="M12 16V4m0 0L8 8m4-4 4 4M4 16v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2"
        stroke="currentColor"
        stroke-width="1.5"
        stroke-linecap="round"
        stroke-linejoin="round"
      />
    </svg>
  </div>
  <h3 class="re-empty-state__title">Drop files to upload</h3>
  <p class="re-empty-state__description">or click to browse from your computer.</p>
</div>

Put .re-empty-state-cell on the <td> so the cell’s own padding and small table font don’t fight the block.

Name Status Updated

No matching rows

Adjust your filters and try again.

Live example
<table class="re-table">
  <thead>
    <tr>
      <th scope="col">Name</th>
      <th scope="col">Status</th>
      <th scope="col">Updated</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td colspan="3" class="re-empty-state-cell">
        <div class="re-empty-state" data-size="sm">
          <h3 class="re-empty-state__title">No matching rows</h3>
          <p class="re-empty-state__description">Adjust your filters and try again.</p>
          <div class="re-empty-state__actions">
            <button class="re-button" data-variant="ghost" type="button">
              Clear filters
            </button>
          </div>
        </div>
      </td>
    </tr>
  </tbody>
</table>
  • Vertical rhythm is gap-only, so omitting the icon or actions reflows cleanly.

The empty state is CSS-only — a passive container with no interactive behavior of its own. Any keyboard, focus, and announcement semantics come from the native elements you place inside it.

  • Semantics — Author the title as a real heading at the level that fits the page outline (<h2><h4>); the component never forces one. The action area holds native .re-button controls (<button> / <a href>), so they keep their native focus order and :focus-visible ring — see the button page.
  • Decorative icon — The .re-empty-state__icon slot is mute to assistive tech: mark it aria-hidden="true" and set focusable="false" on the inner <svg> (as the examples do) so it’s skipped and never tab-focusable.
  • Async announcement — To announce an empty state that appears after a load or filter, wrap it in a role="status" aria-live="polite" region that already exists in the DOM before the content swaps in; injecting the live region together with the message won’t reliably announce.

See the accessibility guide for the project-wide stance.