Skip to content

Empty State

Composable placeholder content for empty views with centered and inline layout variants. Size propagates to all parts through the root's data-size attribute; variant controls root layout only.

Preview

Installation

$pnpm exec dgadd add ui/empty-state
[Installs to]src/components/ui/empty-state[Item]ui/empty-state

dgadd ships in @diffgazer/add on npm: pnpm add -D @diffgazer/add puts it on pnpm exec, and npx @diffgazer/add runs it without the install.

UI components require Tailwind CSS v4. Local copy mode imports src/styles/styles.css; package mode uses @diffgazer/ui CSS from the installed package.

Usage

tsx
import { EmptyState, EmptyStateDescription, EmptyStateMessage } from "@/components/ui/empty-state";export default function EmptyStateDefault() {  return (    <EmptyState>      <EmptyStateMessage>No reviews found</EmptyStateMessage>      <EmptyStateDescription>Start a new review to see results here.</EmptyStateDescription>    </EmptyState>  );}

Examples

Variants

Preview

Keyboard hint

Preview

Dynamic (live)

Preview

API Reference

EmptyState

NameTypeDefaultDescription
variant"centered" | "inline""centered"Root layout. Centered stacks children vertically; inline aligns them horizontally.
size"sm" | "md" | "lg""md"Spacing and font-size scale propagated to all subparts via data-size attribute.
livebooleanfalseWhen true, adds role="status" and aria-live="polite" so screen readers announce the empty state. Keep a live EmptyState mounted across the results→empty transition (render it unconditionally, empty while results exist, and swap its children) rather than conditionally mounting it with its message already inside.
childrenReactNode—EmptyState subparts (Icon, Message, Description, Actions).

EmptyStateIcon

NameTypeDefaultDescription
childrenReactNode—Visual marker. Always rendered with aria-hidden.

EmptyStateMessage

NameTypeDefaultDescription
childrenReactNode—Primary empty-state copy.

EmptyStateDescription

NameTypeDefaultDescription
childrenReactNode—Supporting copy.

EmptyStateActions

NameTypeDefaultDescription
childrenReactNode—Action buttons or links.

EmptyStateHint

NameTypeDefaultDescription
childrenReactNode—Keyboard affordance copy, typically Kbd chips plus two or three words.

Accessibility

Notes

Layout Variants

centered variant for full-page empty states, inline variant for embedded contexts.

Size

sm for compact embedded contexts, md (default) for standard use, lg for full-page empty states. Size propagates to all parts through the root's data-size attribute.

Icon

EmptyStateIcon scales its font size with the root size, so a text glyph or a currentColor icon inherits the theme in both palettes. Avoid color emoji: they render identically in light and dark and break the monochrome identity.

Compound Composition

Compose semantic parts for icon, message, description, and actions. There is no React context here: the root stamps data-size and every part reads it through group-data variants, so a copy-mode consumer must keep the root's group/es class. Variant controls root layout only.

Keyboard Hint

EmptyStateHint is the keyboard affordance: compose it with Kbd so an empty screen ends with the key that fills it instead of a full stop. It is deliberately non-interactive — on a touch surface there is no key to press, so render EmptyStateActions (a real button) there and let the Hint be the desktop affordance; the two compose, Actions above and Hint below. Kbd is a peer composition, so copy-mode consumers who never use the hint are not forced to pull it. The inline variant lays its root out as a row, so a Message + Hint pair needs flex-col on that instance to stack.

Writing Empty States

Name what is missing, name the action, name the key. Active voice, present tense, no apology and no 'oops'. Keep the hint to about three words — under live it is announced right after the message, so a sentence there becomes a paragraph in the ear.

Accessibility

For empty states that appear dynamically (e.g., after filtering returns no results), set live on the root. This adds role="status" and aria-live="polite" so screen readers announce the change. A live EmptyState must stay mounted across the results→empty transition: render it unconditionally (empty while results exist) and swap its children, instead of conditionally mounting it already containing its message — many screen-reader/browser pairs do not announce a live region inserted with content already inside it.

Source

Install via CLI: pnpm exec dgadd add ui/empty-state.

Highlighted source loads after this disclosure opens. Browse the source repository.