Components
Empty
A composable placeholder for empty states — no data, no results, or first-run views — with slots for an icon, title, description, and a call to action.
Basic usage
Compose EmptyHeader, EmptyMedia, and EmptyContent to guide the user toward a next action.
Icon only
The simplest form — an icon, title, and description with no action. Use EmptyMedia variant="icon" for the compact badge treatment.
No results
For search and filter results, use variant="default" on the media to render larger artwork and offer a reset action.
EmptyState — the composed shortcut
Nearly every empty state is the same four things: icon, title, description, action. EmptyState wraps the primitives above into a single prop-driven component so “nothing here yet” looks identical across every app in the ecosystem. Reach for the primitives only when you need a layout this does not cover. It renders no client-side state, so it works directly inside a React Server Component.
Props
The Empty primitives. Every part also forwards its native element props, and the emptyMediaVariants cva helper is exported for reusing the badge treatment elsewhere.
| Prop | Type | Default | Description |
|---|---|---|---|
Empty | div | — | Root container. Centers its children vertically and horizontally. It sets border-dashed but no border width, so add border yourself when you want the dashed outline to show. |
EmptyHeader | div | — | Groups the media, title, and description in a max-w-sm centered column. |
EmptyMedia | div | — | Holds the icon or illustration. Accepts a variant prop. |
EmptyMedia.variant | 'default' | 'icon' | 'default' | 'icon' renders a 40px muted rounded badge that sizes an unsized child SVG to 24px; 'default' is an unstyled wrapper for larger artwork. |
EmptyTitle | div | — | The primary headline summarizing the empty state. |
EmptyDescription | div | — | Supporting text. Anchor children are auto-styled as underlined links. |
EmptyContent | div | — | Slot for the call-to-action(s) — buttons, inputs, or links — below the header. Stacks vertically; add flex-row for a button pair. |
className | string | — | Additional Tailwind classes; available on every part. |
EmptyState — the composed version.
| Prop | Type | Default | Description |
|---|---|---|---|
titlerequired | React.ReactNode | — | The headline. The only required prop. |
description | React.ReactNode | — | Supporting text below the title. |
icon | IconComponent | — | The icon component itself (Inbox, not <Inbox />), rendered inside the muted badge. Passing a component rather than an element is deliberate: a Lucide icon is a forwardRef object, so an element and a component cannot be told apart at runtime. |
media | React.ReactNode | — | An arbitrary node — an illustration or image — used instead of icon. Takes precedence when both are given. |
action | React.ReactNode | — | Primary and secondary buttons, laid out in a centered row below the description. |
bordered | boolean | false | Adds the dashed outline. Use it when the empty state sits inside a table or list frame. |
size | 'sm' | 'md' | 'md' | 'sm' tightens padding, shrinks the icon badge to 36px and the title to text-base — for an empty region inside a small card. 'md' is the full-page treatment. |
...rest | React.ComponentProps<'div'> (minus title) | — | Forwarded to the underlying Empty root. |
Accessibility
- Decorative icons inside
EmptyMediaconvey no meaning on their own — theEmptyTitleandEmptyDescriptiontext carries the message for screen readers. - Always pair the empty state with a clear, actionable
EmptyTitleso users understand why the area is empty, not just that it is. - Links placed inside
EmptyDescriptionare automatically underlined for distinguishability beyond color alone. - Action buttons in
EmptyContentremain fully keyboard-focusable and inherit the brand focus ring. EmptyStaterenders itsiconwitharia-hidden="true"for you. When you passmediainstead, hide it yourself — it is decoration, and the title already carries the message.