Files
skills/docs/ui/react/react-ui/src/components/empty/empty.md
2026-05-29 08:20:56 +01:00

6.7 KiB

Empty

(no submodules)

- empty.variants — `emptyVariants` is the cva config……
  • empty.variants — emptyVariants is the cva config that powers the <Empty> chassis component (with emptyHeaderVariants, `emptyMediaV…

  • empty.variants — emptyVariants is the cva config……

  • empty.variants — emptyVariants is the cva config that powers the <Empty> chassis component (with emptyHeaderVariants, `emptyMediaV…

  • empty.variants — emptyVariants is the cva config……

  • empty.variants — emptyVariants is the cva config that powers the <Empty> chassis component (with emptyHeaderVariants, `emptyMediaV…

  • empty.variants — emptyVariants is the cva config…

  • empty.variants — emptyVariants is the cva config that powers the <Empty> chassis component (with emptyHeaderVariants, `emptyMediaV…

  • empty.variants — emptyVariants is the cva config that powers the <Empty> chassis component (with emptyHeaderVariants, `emptyMediaV…

Purpose

<Empty> is the chassis empty-state primitive for @suaveplan/react-ui. It mirrors the upstream shadcn empty registry item (per Wave 3a's reconciliation finding #6) and ships as a family of six components — <Empty> (root), <EmptyHeader>, <EmptyMedia>, <EmptyTitle>, <EmptyDescription>, <EmptyContent> — that compose into the conventional empty-state layout. Per the shadcn skill rule, ALWAYS use <Empty> for empty states rather than building custom empty-state markup — hand-rolled empty states diverge across teams in spacing, iconography, and CTA layout.

The root <Empty> exposes a size axis with three rungs (sm / default / lg) that control vertical breathing room. The component renders role="status" with aria-live="polite" so assistive tech announces the empty state when it appears (e.g. when a search returns no results) without interrupting the user.

Features

  • Six composition primitives — Empty, EmptyHeader, EmptyMedia, EmptyTitle, EmptyDescription, EmptyContent.
  • Three sizes via the size axis (sm / default / lg) on the root.
  • role="status" + aria-live="polite" so AT announces the empty state when it appears.
  • <EmptyMedia> is aria-hidden="true" because the title / description carries the accessible name.
  • <EmptyTitle> renders as <h3>, <EmptyDescription> renders as <p> so the document outline stays clean.
  • All sub-components forward refs.

Usage: Basic Example

import {
    Empty,
    EmptyContent,
    EmptyDescription,
    EmptyHeader,
    EmptyMedia,
    EmptyTitle,
} from "@suaveplan/react-ui";
import { FolderIcon } from "lucide-react";

export function NoProjects() {
    return (
        <Empty>
            <EmptyHeader>
                <EmptyMedia>
                    <FolderIcon className="size-6" />
                </EmptyMedia>
                <EmptyTitle>No projects yet</EmptyTitle>
                <EmptyDescription>
                    Get started by creating a new project.
                </EmptyDescription>
            </EmptyHeader>
            <EmptyContent>
                <button type="button">Create project</button>
            </EmptyContent>
        </Empty>
    );
}

Usage: Advanced Example

import {
    Empty,
    EmptyContent,
    EmptyDescription,
    EmptyHeader,
    EmptyMedia,
    EmptyTitle,
} from "@suaveplan/react-ui";
import { useTranslation } from "@suaveplan/react-i18n";
import { InboxIcon } from "lucide-react";

export function EmptyInbox() {
    const { t } = useTranslation();
    return (
        <Empty size="lg">
            <EmptyHeader>
                <EmptyMedia className="bg-sky-500/10 text-sky-600">
                    <InboxIcon className="size-6" />
                </EmptyMedia>
                <EmptyTitle>{t("react-ui.empty.defaultTitle")}</EmptyTitle>
                <EmptyDescription>
                    {t("react-ui.empty.defaultDescription")}
                </EmptyDescription>
            </EmptyHeader>
        </Empty>
    );
}

API Reference

<Empty>

forwardRef<HTMLDivElement, EmptyProps> — root container. Renders role="status" and aria-live="polite".

Prop Type Default Description
size "sm" | "default" | "lg" "default" Vertical breathing room.
all HTMLAttributes<HTMLDivElement> native native Forwarded as-is.

<EmptyHeader>

forwardRef<HTMLDivElement, EmptyHeaderProps> — flex column wrapping <EmptyMedia>, <EmptyTitle>, <EmptyDescription>.

<EmptyMedia>

forwardRef<HTMLDivElement, EmptyMediaProps> — wrapper for the icon / illustration. Always aria-hidden="true".

<EmptyTitle>

forwardRef<HTMLHeadingElement, EmptyTitleProps> — heading rendered as <h3>.

<EmptyDescription>

forwardRef<HTMLParagraphElement, EmptyDescriptionProps> — supplementary description rendered as <p>.

<EmptyContent>

forwardRef<HTMLDivElement, EmptyContentProps> — flex row for action affordances.

Errors

The Empty components do NOT call useTranslation() so they do not interact with the i18n provider directly. The chassis ships translation keys (react-ui.empty.defaultTitle, react-ui.empty.defaultDescription, react-ui.empty.noResults) that consumers can use as fallbacks via t('react-ui.empty.defaultTitle') at the call site.

Implementation Notes

  • role="status" + aria-live="polite" is set unconditionally on the root so AT announces the empty state when it appears (e.g. when a search returns no results) without interrupting the user. Consumers needing a more aggressive announcement should wrap in their own aria-live="assertive" container; consumers wanting silence should pass role="presentation" to override.
  • <EmptyMedia> is aria-hidden="true" because the icon is decorative — the accessible name comes from the title and description text.
  • The chassis intentionally does NOT inject a default fallback title / description — the bundled translation keys (react-ui.empty.defaultTitle, react-ui.empty.defaultDescription) are convenience fallbacks consumers opt into via t(). Hardcoding fallback text would force every consumer to translate it even when their own copy already exists.
  • The size axis affects only the root's gap and p — sub-component spacing remains constant. This keeps the visual rhythm consistent across all empty-state sizes and means consumers can swap size without rewriting their composition.
  • <EmptyContent> uses flex-wrap so action buttons stack vertically on narrow viewports rather than overflow horizontally.