Skip to content
Component installs need the registry setup— the Base UI shadcn project, the @vegastack namespace and the Cloudflare Access service token.
VegaStack Design

Empty

A zero-data placeholder — icon, title, description, and actions, with intent tints and a plain, card, or dashed container.

Status
stable
Since
0.1.0
Accessibility pattern
heading + supporting content

Last updated

No messages yet

Your inbox is empty. New messages will appear here as they arrive.

Install

Add Empty from the VegaStack registry. The CLI verifies the item's integrity hash before writing it.

pnpm dlx shadcn@latest add @vegastack/empty

Usage

import {
  Empty,
  EmptyContent,
  EmptyDescription,
  EmptyMedia,
  EmptyTitle,
} from "@/components/ui/empty";
import { Inbox } from "lucide-react";

<Empty variant="dashed">
  <EmptyHeader>
    <EmptyMedia>
      <Inbox />
    </EmptyMedia>
    <EmptyTitle>No messages</EmptyTitle>
    <EmptyDescription>Your inbox is empty.</EmptyDescription>
  </EmptyHeader>
</Empty>;

Anatomy

Empty is a compound component. Compose the parts inside the root:

Empty — data-slot="empty"
EmptyContent — data-slot="empty-content"
EmptyDescription — data-slot="empty-description"
EmptyHeader — data-slot="empty-header"
EmptyIllustration — data-slot="empty-illustration"
EmptyMedia — data-slot="empty-media"
EmptyTitle — data-slot="empty-title"
EmptyValue — data-slot="empty-value"
<Empty variant="dashed" size="md">
  <EmptyHeader>
    <EmptyMedia intent="default">{/* lucide icon */}</EmptyMedia>
    <EmptyTitle>Title</EmptyTitle>
    <EmptyDescription>Supporting description text.</EmptyDescription>
  </EmptyHeader>
  <EmptyContent>{/* action buttons */}</EmptyContent>
</Empty>
  • Empty — the centered container (data-slot="empty"). Owns size and variant.
  • EmptyMedia — a tinted circular chip around a lucide-react icon (data-slot="empty-media", decorative aria-hidden). Owns the intent tint.
  • EmptyTitle — the primary heading (<h3> by default, data-slot="empty-title"). Owns as, so the page picks the heading level.
  • EmptyDescription — supporting body text (<p>, data-slot="empty-description").
  • EmptyContent — a centered row of call-to-action controls (data-slot="empty-content").

Examples

Sizes

size controls vertical density only — sm (py-8) for empties inside cards, md (py-12) standalone, and lg (py-16) for full-page zero-data screens.

No messages yet

Density sm — vertical padding scales from compact (in-card) to full-page.

No messages yet

Density md — vertical padding scales from compact (in-card) to full-page.

No messages yet

Density lg — vertical padding scales from compact (in-card) to full-page.

Variants

variant is the one container axis. plain (the default) has no border and no fill, so the block blends into the surrounding layout; dashed draws the classic drop-zone outline; card paints a filled bg-card panel with the one hairline, which reads as a self-contained block on any background. There is no way to ask for a dashed card — that combination had no defined outcome.

Dashed

The drop-zone outline over the parent background — no fill.

Card

Filled bg-card panel — a self-contained block that reads on any background.

No messages yet

The borderless default — no dashed outline, blends into the surrounding layout.

Without an icon

EmptyMedia and EmptyContent are both optional. A title-and-description-only empty is a common compact form — for example, a "no filters applied" hint.

No filters applied

A compact title-and-description empty with no icon chip and no actions.

Variant × size matrix

The two root axes combine freely. This grid crosses variant (dashed vs. card) with size (sm vs. lg) to show how padding, panel fill, and the dashed outline interact.

dashed · sm

variant=dashed × size=sm

dashed · lg

variant=dashed × size=lg

card · sm

variant=card × size=sm

card · lg

variant=card × size=lg

Intents

EmptyMedia takes an intent that tints the chip — neutral default, info, or destructive — each mapping to semantic status tokens (bg-X-subtle text-X-text). Use destructive for error/failure empties, info for search/filter empties, and default for plain zero-data states.

No members yet

Invite teammates to start collaborating.

No results found

Try adjusting your search or filters.

Couldn’t load records

The request timed out. Check your connection and try again.

Illustration & value tiers

The empty-state system spans eight tiers; two are Wave-2 additions. EmptyIllustration renders one of six built-in monoline drawings (clipboard, bell, search, box, error, not-found) on a faint grid-paper ground — all currentColor, so the error tier is just text-destructive-text on the media slot, no extra asset. EmptyValue is the inline tier: a single faint phrase for a value slot ("No value", "Set a value…").

No tasks yet

Create your first task to get started.

No mailboxes configured

Configure a mailbox to unlock sending.

No results

Try a different query.

Playground

Combine the container variant and size with the media chip's variant and intent, then copy the generated JSX.

No messages

Your inbox is empty.

<Empty>
  <EmptyHeader>
    <EmptyMedia>
      <Inbox />
    </EmptyMedia>
    <EmptyTitle>No messages</EmptyTitle>
    <EmptyDescription>Your inbox is empty.</EmptyDescription>
  </EmptyHeader>
</Empty>

API Reference

Empty

PropTypeDefaultDescription
size"lg" | "md" | "sm""md"Vertical density — sm for inside cards, md standalone, lg for full-page empties.
variantEmptyVariant"plain"Container treatment. - plain: no border, no fill — blends into the parent surface (default). - card: a filled bg-card panel with the one hairline. - dashed: the classic dashed drop-zone outline.

Data attributes and CSS variables on Empty

AttributeValues
data-sizemirrors a prop or state value
data-slot"empty"
data-variantmirrors a prop or state value

EmptyMedia

PropTypeDefaultDescription
intentEmptyIntent"default"Color intent of the icon chip — neutral default, info, or destructive.
variant"default" | "icon""icon"icon wraps children in the tinted circular chip; default renders them bare (illustration, avatar, screenshot).

Data attributes and CSS variables on EmptyMedia

AttributeValues
data-intentmirrors a prop or state value
data-slot"empty-media"
data-variantmirrors a prop or state value

EmptyTitle

PropTypeDefaultDescription
asEmptyTitleAs'h3'The element to render. The default <h3> is a GUESS about the host page's heading outline: an empty state under an <h2> section wants h3, one that replaces a whole page body may want h2, and one inside a card that already has its own heading should not introduce a heading at all (p). Say which rather than inheriting the guess (B7-07). This is an as union rather than Base UI's polymorphic render, because useRender is a hook and Empty is server-safe — the whole file has no 'use client' and must keep it.

Data attributes and CSS variables on EmptyTitle

AttributeValues
data-slot"empty-title"

EmptyDescription

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

Data attributes and CSS variables on EmptyDescription

AttributeValues
data-slot"empty-description"

EmptyContent

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

Data attributes and CSS variables on EmptyContent

AttributeValues
data-slot"empty-content"

Accessibility

  • The title renders as a real heading (<h3> by default) so assistive technology can navigate to it. Set as to match the page's outline — h2 when the empty state replaces a whole page body, p when the surrounding card already carries the heading.
  • The icon chip is decorative (aria-hidden) — meaning is carried by the title and description text, never by the icon or color alone.
  • Action controls inside EmptyContent are real buttons/links with their own accessible names and :focus-visible rings.
  • The container is non-interactive; keyboard focus order flows through the action controls only.
KeyAction
TabMove focus to the first action control (when actions are present).
Enter / SpaceActivate the focused action control.
ContractStates tested
Behaviourdefault, empty, error
Accessibilitynative-or-base-ui-semantics, browser-accessibility-test
Visualdefault, error, empty

Do / Don't

Do
Pair a clear title with a description and a single primary action that resolves the empty state — e.g. Create your first project.
Don't
Leave a blank area or show only a spinner — distinguish empty (no data) from loading and explain what the user can do next.

On this page