Skip to content
Component installs need the registry setup
VegaStack Design

Empty

A zero-data placeholder — media, title, description and actions — for an empty list, a cleared inbox, or a missing page.

Status
stable
Since
0.1.0
Accessibility pattern
heading + supporting content

Last updated

No Projects Yet
You haven't created any projects yet. Get started by creating your first project.
Learn More

Install

Configure registry credentials first using the Quickstart. Before copying this item or any transitive registry dependency, verify the signed manifest and save the exact bytes for Empty before installation. Retain the digest printed by this command.

pnpm exec vegastack-design verify empty

Install with the pinned shadcn CLI. Bare shadcn does not verify VegaStack signature or integrity.

pnpm dlx shadcn@4.21.0 add @vegastack/empty

Run the exact offline --post-write command printed by the preflight, using its saved item path and independently retained --expected-integrity digest. Use the saved preflight for every transitive registry dependency too; stop on any mismatch before using the components.

Usage

import {
  Empty,
  EmptyContent,
  EmptyDescription,
  EmptyHeader,
  EmptyMedia,
  EmptyTitle,
} from "@/components/ui/empty";

<Empty>
  <EmptyHeader>
    <EmptyMedia variant="icon">
      <Icon />
    </EmptyMedia>
    <EmptyTitle>No data</EmptyTitle>
    <EmptyDescription>No data found</EmptyDescription>
  </EmptyHeader>
  <EmptyContent>
    <Button>Add data</Button>
  </EmptyContent>
</Empty>;
No Projects Yet
You haven't created any projects yet. Get started by creating your first project.
Learn More

An empty state always shows an icon. Empty takes an icon prop and renders it in the icon tile; with no icon and no composed EmptyMedia it falls back to lucide Inbox. A composed EmptyMedia replaces the default. The tile is a 48px bordered muted plate with a 24px icon, and the tile sizes whatever icon you pass, so never put a size-* class on it.

<Empty icon={<SearchX />}>
  <EmptyHeader>
    <EmptyTitle>No matches</EmptyTitle>
    <EmptyDescription>
      Try a different search or clear the filters.
    </EmptyDescription>
  </EmptyHeader>
</Empty>

Compact. size="sm" is the small inline empty — "No tasks yet" inside a card — with a 16px padding, an 8px gap and a 40px tile with a 20px icon. DataList's built-in noResults and emptyState use the default size:

<Empty size="sm" icon={<ListTodo />}>
  <EmptyHeader>
    <EmptyTitle>No tasks yet</EmptyTitle>
  </EmptyHeader>
</Empty>
No tasks yet

When to use which — in a DataList, never hand-roll either:

  • Nothing yet (DataList emptyState) — the list has no records at all. Say what is missing and offer the one create action.
  • No matches (DataList noResults, or NoResultsEmpty elsewhere) — a search or filter matched nothing. SearchX, "No matches", a short description and "Clear filters" wired to the FilterRow's onClear.

Which empty state a surface needs — nothing yet, no matches, couldn't load, blocked — and its default copy is set by the empty-state tiers. A search or filter that matched nothing is "No matches" with a "Clear filters" action, and a failed load is role="alert" with "Try again".

Anatomy

Empty — data-slot="empty"
EmptyContent — data-slot="empty-content"
EmptyDescription — data-slot="empty-description"
EmptyHeader — data-slot="empty-header"
EmptyMedia — data-slot="empty-icon"
EmptyTitle

Examples

Composition

Use the following composition to build an Empty state:

Empty
├── EmptyHeader
│   ├── EmptyMedia
│   ├── EmptyTitle
│   └── EmptyDescription
└── EmptyContent
No data
EmptyHeader holds the media, the title and the description; EmptyContent holds whatever the reader does next.

Outline

Use the border utility class to create an outline empty state — the root already carries border-dashed, so border alone turns the dashed outline on.

Cloud Storage Empty
Upload files to your cloud storage to access them anywhere.

Background

Use the bg-* and bg-gradient-* utilities to add a background to the empty state.

No Notifications
You're all caught up. New notifications will appear here.

Avatar

Use the EmptyMedia component to display an avatar in the empty state.

AL
User Offline
This user is currently offline. You can leave a message to notify them or try again later.

Avatar Group

Use the EmptyMedia component to display an avatar group in the empty state.

ALGHAT
No Team Members
Invite your team to collaborate on this project.

InputGroup

EmptyContent takes an InputGroup, which keeps the leading icon and the trailing Kbd hint in their own addons, outside the editable box.

Page not found
The page you're looking for doesn't exist. Try searching for what you need below.
/
Need help? Contact support

RTL

Every part is written in logical properties, so a right-to-left document mirrors the media chip, the action row and the description without any extra work.

No Projects Yet
You haven't created any projects yet.
لا توجد مشاريع بعد
لم تقم بإنشاء أي مشاريع بعد.

As a heading

EmptyTitle renders a div by default. Pass render to make it the real heading at the level the page needs; the classes and the data-slot stay the same.

<EmptyTitle render={<h3 />}>No projects yet</EmptyTitle>

No projects yet

Create a project to see it listed here.

API Reference

PropTypeDefaultDescription
iconReact.ReactNode—
size"default" | "sm"—

EmptyHeader adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).

PropTypeDefaultDescription
variant"default" | "icon" | null—

EmptyTitle adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).

EmptyDescription adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).

EmptyContent adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).

Accessibility

  • Every part renders a plain <div>, so the empty state adds no landmark and no implicit heading. Where the placeholder replaces a named region, give the surrounding container the heading.
  • EmptyTitle is styled text by default, not a heading element. When the empty state stands in for a section, pass render={<h2 />} (or the level the outline needs) so the title is the real heading (API-22).
  • EmptyMedia is decorative: the icon inside it carries no accessible name, and the title plus the description are what a screen reader announces.
  • EmptyContent holds real controls — a Button, an Input, a link — and each keeps its own focus outline from base.css; the placeholder never suppresses one.
ContractStates tested
Behaviourdefault, empty, error
Accessibilitynative-or-base-ui-semantics, browser-accessibility-test
Visualdefault, error, empty

Do / Don't

Do
Say what is missing and give one obvious next action in EmptyContent.
Don't
Ship a bare illustration with no title, description, or way forward.
Do
Inside a DataList, use its `empty` prop — the list draws the Empty in its own row.
Don't
Put a bordered Empty inside or above a table, or a second empty state beside the list's own.

Deviations

Upstream's file plus packages/ui/upstream/patches/empty.patch. Every hunk:

  • TYP-15 — the title's tracking-tight is dropped: the title is text-sm, in the copy tier, which the theme holds at zero tracking.
  • API-22 — EmptyTitle renders through Base UI's useRender, so it takes render and the heading level is set on the part. The default div, the data-slot and the classes are upstream's.
  • API-16 — "use client" is added: useRender calls React.useRef internally, so the module needs the client boundary to import in a Server Component.
  • API-29 — Empty always renders an icon: an icon prop (lucide Inbox by default) drawn as a leading EmptyMedia variant="icon", hidden when the composition brings its own EmptyMedia; and size="sm" is the compact inline empty.
  • DOC-2 — cn is imported from @vegastack/design.
  • LAY-17 — EmptyTitle and EmptyDescription take wrap-anywhere: a long unbroken word (a quoted search, a long name) breaks inside the empty state instead of running past it.

On this page