docs / patterns / empty-state
EmptyState
a centered placeholder for a zero-data view: an icon, a message, and the one action that gets the user out of empty.
intensity · structuresrsc · server
provenance
authored in usvalayer
core / patternsintensity
structures · organizes a region, stays out of the contentcomposition
fills the slot the data would occupy: a Panel body, a grid cell, a pagethe action is one Button, usually the way out of emptynever beside content, it marks the absence of itnot for failures. an error gets its own treatment, this is legitimately emptya11y
the title is anh3 · the icon badge is aria-hiddenjest-axelive demo · try it out
No projects yet
Create your first project to see it show up here.
customize
variantsolid surface or dashed outline
titlethe primary message
descriptionsupporting copy below the title
iconglyph badge above the title
actionthe way out of empty
actionLabeltext on the action button
usage
import { EmptyState } from "usva/patterns/empty-state";
import { Button } from "usva/primitives/button";
<EmptyState
icon={<FolderIcon />}
title="No projects yet"
description="Create your first project to see it show up here."
action={<Button>New project</Button>}
/>props
| prop | type | default | notes |
|---|---|---|---|
| icon | React.ReactNode | — | glyph in a rounded badge above the title. decorative. |
| title | React.ReactNode | — | the primary message. |
| description | React.ReactNode | — | supporting copy below the title. |
| action | React.ReactNode | — | the way out of empty, rendered below the description. |
| variant | "solid" | "dashed" | "solid" | solid surface or a dashed transparent outline. |
get it
npx shadcn add https://usva.build/r/empty-state.jsonsource · components/ui/empty-state.tsxexactly what this command copies
import * as React from "react";
import { cn } from "@/lib/utils";
export interface EmptyStateProps
extends Omit<React.HTMLAttributes<HTMLDivElement>, "title"> {
icon?: React.ReactNode;
title: React.ReactNode;
description?: React.ReactNode;
action?: React.ReactNode;
variant?: "solid" | "dashed";
}
export const EmptyState = React.forwardRef<HTMLDivElement, EmptyStateProps>(
(
{
className,
icon,
title,
description,
action,
variant = "solid",
...props
},
ref,
) => (
<div
ref={ref}
className={cn(
"group flex flex-col items-center justify-center rounded-2xl border border-border px-6 py-14 text-center",
"transition-tint duration-slow ease-soft hover:border-border-strong",
variant === "dashed" ? "border-dashed bg-transparent" : "bg-surface",
className,
)}
{...props}
>
{icon != null && (
<div
aria-hidden="true"
className={cn(
"mb-4 flex h-11 w-11 items-center justify-center rounded-xl border border-border bg-surface-2 text-faint [&_svg]:h-5 [&_svg]:w-5",
"transition-control duration-slow ease-soft",
"group-hover:-translate-y-0.5 group-hover:border-border-strong group-hover:text-accent group-hover:glow-accent",
"motion-reduce:transition-none motion-reduce:group-hover:translate-y-0",
)}
>
{icon}
</div>
)}
<h3 className="text-balance text-base font-semibold tracking-[-0.01em] text-ink">
{title}
</h3>
{description != null && (
<p className="mt-2 max-w-sm text-sm leading-relaxed text-pretty text-muted">
{description}
</p>
)}
{action != null && <div className="mt-6">{action}</div>}
</div>
),
);
EmptyState.displayName = "EmptyState";