Skip to documentation content

Empty

Explain why a section is empty and help people take the next useful action.

empty-demo

Overview

Use Empty when a section has nothing to show yet: a dashboard before its first project, an inbox at zero, search results that matched nothing, an upload queue waiting for files. An empty state is a conversation, not a dead end — it should say what is missing, why, and what to do next if there is anything to do.

Anatomy

An empty state can include media, a title, a description, and content such as actions or a supporting input. EmptyHeader centers the media, title, and description as one block; EmptyContent holds whatever comes next. Omit regions you don't need — some states need no action (Inbox zero) and some need no illustration.

The container renders a rounded dashed outline by default, which suits drop targets and placeholders; add border alongside it for full dashed borders, as the examples do, or remove both for a plain centered layout.

Usage guidance

Write the title for the real cause. "No projects yet" tells people the space works and awaits input; "Nothing here" could mean a bug, a filter, or an outage. Match the action to the cause: create when the space is new, clear filters when a query came up empty, retry when data failed to arrive, and offer no button when there is genuinely nothing to do — celebrating an empty inbox beats inventing work for it.

Never show an empty state while content is still loading (use Skeleton) or when a request failed (show the error and a retry instead). Each of those three situations needs its own message; collapsing them into one "No data" state hides whether the problem will resolve on its own.

Accessibility

EmptyTitle renders a styled div, so put a real heading level inside it for people navigating by headings — the same pattern as Card. Keep the description as real text; do not bake the explanation into the illustration.

Treat EmptyMedia as decoration: pass aria-hidden="true" on decorative icons so screen readers skip straight to the title. The component's stacked backdrop tiles are already hidden from assistive technology. Actions inside EmptyContent are ordinary buttons and links — give them specific labels ("Reset filters", not just "OK") because they may be the only controls announced in the region.

Colors come from theme tokens, and the layout reflows cleanly at high zoom; the centered column keeps reading order natural for screen readers top to bottom.

Installation

npx honestui@latest add empty

Usage

import {
  Empty,
  EmptyContent,
  EmptyDescription,
  EmptyHeader,
  EmptyMedia,
  EmptyTitle,
} from "@/components/ui/empty";
<Empty>
  <EmptyHeader>
    <EmptyMedia variant="icon">
      <Icon />
    </EmptyMedia>
    <EmptyTitle>No projects yet</EmptyTitle>
    <EmptyDescription>Create a project to organize your work.</EmptyDescription>
  </EmptyHeader>
  <EmptyContent>
    <Button>Create project</Button>
  </EmptyContent>
</Empty>

Don't do this

Dead ends

// Bad
<Empty>
  <EmptyHeader>
    <EmptyTitle>Nothing here</EmptyTitle>
  </EmptyHeader>
</Empty>
// Good
<Empty className="border">
  <EmptyHeader>
    <EmptyMedia variant="icon"><SearchXIcon aria-hidden="true" /></EmptyMedia>
    <EmptyTitle><h2>No matching filters</h2></EmptyTitle>
    <EmptyDescription>Try clearing a status or date filter.</EmptyDescription>
  </EmptyHeader>
  <EmptyContent>
    <Button variant="secondary" size="sm">Reset filters</Button>
  </EmptyContent>
</Empty>

"Nothing here" leaves every question open: is this a bug, my filters, my permissions, or an empty database? And with no action offered, the only way out is the back button. Name the cause and hand over the next step whenever one exists.

Blaming the user

// Bad
<EmptyTitle>You broke it!</EmptyTitle>
<EmptyDescription>This page crashed because of your filters.</EmptyDescription>
// Good
<EmptyTitle><h2>No results</h2></EmptyTitle>
<EmptyDescription>No transactions match these filters right now.</EmptyDescription>

Accusatory copy turns a routine state into an indictment — and it's usually inaccurate, since empty states most often reflect normal circumstances, not user error. Describe the situation neutrally and keep the door open: "no results yet", "right now", "with these filters".

Meaning carried by the picture alone

// Bad
<EmptyHeader>
  <EmptyMedia variant="icon"><FolderIcon /></EmptyMedia>
</EmptyHeader>
// Good
<EmptyHeader>
  <EmptyMedia variant="icon"><FolderIcon aria-hidden="true" /></EmptyMedia>
  <EmptyTitle><h2>No projects yet</h2></EmptyTitle>
  <EmptyDescription>Create your first project to get started.</EmptyDescription>
</EmptyHeader>

An unexplained folder icon says something different to everyone and nothing to screen readers. The icon sets tone; the title and description carry the actual message. Mark the media decorative and make the text self-sufficient.

Examples

Match the message and recovery action to the reason the content is empty.

No matching results

A filter problem with a one-click way back.

empty-filtered-results

Empty inbox

A genuine achievement with no action attached.

empty-inbox-zero

Upload queue

A drop-target state inviting the next file.

empty-upload-queue

API reference

All parts accept native div props plus the following:

PartPropValuesDefault
Empty———
EmptyHeader———
EmptyMediavariantdefault, icondefault
EmptyTitle———
EmptyDescription———
EmptyContent———

Empty provides the centered, dashed-border container. EmptyHeader wraps media, title, and description with constrained width and centering. With variant="icon", EmptyMedia draws a bordered icon tile flanked by two rotated, aria-hidden backdrop tiles. EmptyTitle and EmptyDescription provide styling only — supply your own heading semantics inside the title. EmptyContent constrains actions to a readable measure.