Component
Empty State Card
Fill a content area when there is nothing to display. Pick a type — no-result-found, 404-error, or no-data — to set the illustration, heading, and description, then override the copy for your context. Backgrounds and text read from the --components-emptystatecard-* tokens; the card corner radius is always 0.
Playground
Type
Illustration
Action button
Title
Description
Action label
Secondary label
Preview
No Search Result Found
We couldn't find anything matching your search. Try different keywords or clear your filters.
JSX
<EmptyStateCard
type="no-result-found"
showIllustration
showAction
title="No Search Result Found"
description="We couldn't find anything matching your search. Try different keywords or clear your filters."
actionLabel="Search Again"
onAction={() => {}}
secondaryActionLabel="Back to Home"
onSecondaryAction={() => {}}
/>Props
The full EmptyStateCardProps API. Props marked Required have no default.
| Prop | Type | Default | Description |
|---|---|---|---|
type | 'no-result-found' | '404-error' | 'no-data' | 'no-result-found' | Selects the default illustration, heading, and description. Maps to the three Figma variants. |
showIllustration | boolean | true | Show / hide the decorative illustration above the heading (272px, aria-hidden). |
showAction | boolean | true | Show / hide the action area below the description — a full-width row (24px gap) with a filled primary Button and a ghost text Button. |
title | string | type default | Overrides the default heading. Rendered as an <h2> in H2 SemiBold 28. |
description | string | type default | Overrides the default description. Rendered as a <p> in H4 Regular 18. |
actionLabel | string | type default | Overrides the default label of the filled primary action button. |
onAction | () => void | — | Click handler for the primary action button. |
secondaryActionLabel | string | type default | Overrides the default label of the secondary ghost (text) action button. |
onSecondaryAction | () => void | — | Click handler for the secondary action button. |
className | string | — | Layout-only classes (positioning). Never override colour, type, or radius. |
Usage guidelines
When to use
- Fill a content area that has nothing to display — zero search results, a missing route, or a section with no data yet.
- Pick the type that matches the situation: no-result-found, 404-error, or no-data.
- Override title and description with copy specific to the product context, and give the action a descriptive label.
- Keep it centred in the main content region so screen-reader users land on the heading.
When not to use
- Don't use it for loading states — use a SkeletonLoader while data is being fetched.
- Don't use it for recoverable form errors — use an Alert with variant="error" instead.
- Don't build it from a raw <div> with inline styles, and never add rounded-* — the card radius is always 0.
- Don't set showAction without wiring onAction / onSecondaryAction handlers, and don't pass a custom Button as a child.
nexus/components/empty-state-card.tsx
import type { ComponentType, SVGProps } from "react";
import {
Illustration1NoResultFoundIcon,
Illustration2404ErrorIcon,
Illustration3NoDataIcon,
} from "nexus/icons";
import { Button } from "./button";
type IconType = ComponentType<SVGProps<SVGSVGElement>>;
export type EmptyStateCardType = "no-result-found" | "404-error" | "no-data";
export interface EmptyStateCardProps {
/** Selects the default illustration, heading, and description. */
type?: EmptyStateCardType;
/** Show / hide the decorative illustration above the heading. */
showIllustration?: boolean;
/** Show / hide the action button below the description. */
showAction?: boolean;
/** Overrides the type's default heading. */
title?: string;
/** Overrides the type's default description. */
description?: string;
/** Overrides the type's default primary action label. */
actionLabel?: string;
/** Click handler for the primary action button. */
onAction?: () => void;
/** Overrides the type's default secondary (text) action label. */
secondaryActionLabel?: string;
/** Click handler for the secondary action button. */
onSecondaryAction?: () => void;
/** Layout-only classes (positioning). Never color, type, or radius. */
className?: string;
}
interface TypeDefaults {
title: string;
description: string;
actionLabel: string;
secondaryActionLabel: string;
Illustration: IconType;
}
// Defaults per the Figma `M-EmptyStateCard` variants (node 480:2083).
const TYPE_DEFAULTS: Record<EmptyStateCardType, TypeDefaults> = {
"no-result-found": {
title: "No Search Result Found",
description:
"We couldn't find anything matching your search. Try different keywords or clear your filters.",
actionLabel: "Search Again",
secondaryActionLabel: "Back to Home",
Illustration: Illustration1NoResultFoundIcon,
},
"404-error": {
title: "Something Went Wrong",
description:
"We ran into an unexpected issue. This is on us — try refreshing the page or come back in a moment.",
actionLabel: "Refresh Page",
secondaryActionLabel: "Contact Support",
Illustration: Illustration2404ErrorIcon,
},
"no-data": {
title: "This is Empty For Now",
description:
"No entries have been added to this section yet. Get started by creating your first one, it takes a moment.",
actionLabel: "Get Started",
secondaryActionLabel: "Learn More",
Illustration: Illustration3NoDataIcon,
},
};
const cx = (...classes: (string | false | undefined)[]) =>
classes.filter(Boolean).join(" ");
/**
* Nexus EmptyStateCard — fills a content area when there is nothing to
* display (zero search results, a missing route, or a section with no data
* yet). `type` selects the default illustration, heading, and description;
* override with `title` / `description` for the specific product context.
* Per the spec (instruction/nexus-design-system.md §6.6), corner radius is
* always 0. The action area (Figma node 480:2083) is a full-width row with a
* 24px gap holding two official Buttons: a filled primary and a ghost text
* action, both label-only.
*/
export const EmptyStateCard = ({
type = "no-result-found",
showIllustration = true,
showAction = true,
title,
description,
actionLabel,
onAction,
secondaryActionLabel,
onSecondaryAction,
className,
}: EmptyStateCardProps) => {
const defaults = TYPE_DEFAULTS[type];
const Illustration = defaults.Illustration;
const heading = title ?? defaults.title;
const body = description ?? defaults.description;
const primaryLabel = actionLabel ?? defaults.actionLabel;
const secondaryLabel = secondaryActionLabel ?? defaults.secondaryActionLabel;
return (
<div
className={cx(
"flex min-w-[504px] max-w-[1024px] flex-col items-center justify-center gap-24 rounded-none bg-[var(--components-emptystatecard-color-background-white)] p-24 text-center",
className,
)}
>
{showIllustration && (
<Illustration width={272} height={272} aria-hidden="true" />
)}
<div className="flex flex-col items-center gap-16">
<h2 className="typography-h2-semibold-28 text-[var(--components-emptystatecard-color-text-heading)]">
{heading}
</h2>
<p className="typography-h4-regular-18 text-[var(--components-emptystatecard-color-text-information)]">
{body}
</p>
</div>
{showAction && (
<div className="flex w-full items-center justify-center gap-24">
<Button
variant="primary"
support="default"
size="md"
showLeftIcon={false}
showRightIcon={false}
onClick={onAction}
className="flex-1"
>
{primaryLabel}
</Button>
<Button
variant="primary"
support="ghost"
size="md"
showLeftIcon={false}
showRightIcon={false}
onClick={onSecondaryAction}
className="flex-1"
>
{secondaryLabel}
</Button>
</div>
)}
</div>
);
};