Empty state
Yuno's empty-state family has three structurally distinct siblings that share the same intent ('no data / no result yet + optional next step') but do NOT share a common anatomy — so they ship as named siblings under one pattern, NOT as one component with a `variant` prop: EmptyStateInline (shipping — compact card with an icon tile, fits inside a ChartCard / TableCard body), EmptyStateHero (shipping — 592×444 hero for full-page / full-section empty states like Rules, Blocklists, Payments; ported from yuno-emptystates-tables), and EmptyStateIllustration (planned — same compact layout as Inline with an SVG instead of a Phosphor icon). Consumers import a sibling directly (`import { EmptyStateInline, EmptyStateHero }`) or via the namespace (`import { EmptyState }; <EmptyState.Inline />` / `<EmptyState.Hero />`) — matching how the kit already exposes compound patterns.
1. EmptyStateInline
The compact sibling. Sized to sit inside a ChartCard / TableCard body (or any narrow-ish content area) so the empty state feels like content, not a page hero.
Anatomy
The Inline sibling is a bg-muted rounded-lg container that grows to fill its flex parent (flex-1) and centers its content. Inside, a 40×40 icon tile on bg-background (contrast against the muted surface) holds a Phosphor icon at size-5 weight="light". Below the tile, a title (text-sm foreground semibold) + optional description (text-xs muted) stack with gap-1. An optional action slot renders below the text stack (canonical: outline / sm Button).
No Insight listed yet
You'll see all your insights here. Not sure where to start?
- 1Container
bg-muted rounded-lg with flex-1 min-h-0 so it grows to fill a flex parent, plus items-center + justify-center to anchor its content. Padding: px-6 py-10. Text is centered by default.
- 2Icon tile
size-10 rounded-md bg-background centered on the muted surface, holding a Phosphor icon at size-5 weight="light". The bg-background contrast is what makes the tile read as a container instead of a floating icon.
- 3Title
text-sm text-foreground font-semibold. One clear line summarizing what's missing ("No Insight listed yet", "No results found").
- 4Description
Optional text-xs text-muted-foreground below the title. Use it to hint at what the user could do ("Try clearing filters", "Not sure where to start?") or explain the state. Omit when the title carries the full message.
- 5Action slot
Optional CTA below the text stack. Canonical: <Button variant="outline" size="sm"> with a route (asChild + <a>) or a plain onClick. Skip when there is no useful next step — an empty state without an action is still legit.
Recipes
The full shape ported from Insights' NoInsightEmptyBody. Use when the empty state has a real next step the user should take (visit guides, add a first record, connect a provider).
No Insight listed yet
You'll see all your insights here. Not sure where to start?
<EmptyStateInline
icon={ChartLine}
title="No Insight listed yet"
description="You'll see all your insights here. Not sure where to start?"
action={
<Button variant="outline" size="sm" asChild>
<a href="#">Visit our guides</a>
</Button>
}
/>Drop the action slot when the empty state is temporary or self-resolving (a filter returned no results, a search matched nothing). The user's next step is to change the filter above, not to click a button inside the empty state.
No results found
Try clearing filters or expanding the date range.
<EmptyStateInline
icon={MagnifyingGlass}
title="No results found"
description="Try clearing filters or expanding the date range."
/>Drop both description and action for the most compact form. Reserve for empty states that live inside a small container where extra copy would dominate the surface.
No data available
<EmptyStateInline
icon={ChartPie}
title="No data available"
/>The canonical composition: EmptyStateInline sits as the direct child of a ChartCard body. ChartCard's Main has flex-1 min-h-0, so EmptyStateInline's flex-1 lets it fill the card and center — the empty card visually matches the rhythm of its neighboring cards in a grid.
Insights
No Insight listed yet
You'll see all your insights here. Not sure where to start?
<ChartCard title="Insights">
<EmptyStateInline
icon={ChartLine}
title="No Insight listed yet"
description="You'll see all your insights here. Not sure where to start?"
action={
<Button variant="outline" size="sm" asChild>
<a href="#">Visit our guides</a>
</Button>
}
/>
</ChartCard>2. EmptyStateHero
The full-page / full-section sibling. Ported from yuno-emptystates-tables. Text stack (tagline + big 4xl title + description + primary Button) opposite a 592×444 illustration slot. Responsive: stacks on narrow; flips to row-reverse (illustration LEFT, text RIGHT) at xl+. The illustration slot accepts any ReactNode — an <iframe> pointing at a yuno-emptystates-tables animation, an inline <svg>, a static <img>. The 592×444 default matches the animation assets 1-to-1 so any of the 9 canonical animations drops in.
The full shape used across Rules, Blocklists, Allowlists, Payments, Installments, Recipients, Subscriptions, Payment links, Chargebacks. The illustration slot renders whatever the caller passes — typically an <iframe> loading the matching animation from yuno-emptystates-tables (light + dark files).
No rules created yet
Create your first Rule
Set up custom velocity rules to control payment outcomes. Identify users, monitor transactions, and decide when to approve, review, or bypass checks — all without code, directly from the Yuno dashboard.
// Canonical shape — pass whatever illustration you want in the slot.
// For the 9 vendored Yuno animations, use AnimationFrame: it swaps
// light/dark with the kit theme and scales the fixed 592x444 scene to
// its container instead of cropping it.
import { EmptyStateHero, AnimationFrame } from "@/components/patterns/empty-state";
<EmptyStateHero
tagline="No rules created yet"
title="Create your first Rule"
description="Set up custom velocity rules to control payment outcomes…"
action={
<Button>
<Plus weight="light" /> Create rule
</Button>
}
illustration={
<iframe
src="/empty-state/animations/rules/light.html"
title="Rules empty state animation"
className="h-full w-full border-0"
loading="lazy"
/>
}
/>Drop the tagline when the title alone carries the 'no data yet' tone (e.g. "Manage your Chargebacks" — the surrounding section context already implies it). Smaller vertical rhythm, still hero-scale.
Manage your Chargebacks
Track and respond to chargebacks efficiently. Monitor disputes, submit evidence, and manage the entire chargeback lifecycle from a single view in the Yuno dashboard.
<EmptyStateHero
title="Manage your Chargebacks"
description="Track and respond to chargebacks efficiently…"
action={<Button>View chargebacks</Button>}
illustration={<iframe src="/empty-state/animations/chargebacks/light.html" … />}
/>Container-query responsive (Tailwind 4 @container + @5xl:) — the layout reads the Hero's PARENT width, not the browser viewport. When the container is ≥ 1024px: flips to flex-row-reverse (illustration LEFT, text RIGHT). When the container is < 1024px: stacks flex-col (illustration below text). This matches production behavior AND lets the doc's ScaledPreview render at 1280 native + scale visually while keeping the row-reverse layout intact — a viewport-based `xl:` breakpoint would have broken that trick.
// Purely CSS. The pattern flips automatically:
// - narrow (<xl): flex-col, illustration BELOW text
// - xl+: flex-row-reverse, illustration LEFT, text RIGHT
//
// The illustration slot uses width: min(100%, illustrationSize.width) +
// aspect-ratio so it shrinks proportionally at narrow widths without
// cropping the animation inside. Resize the browser to see the flip.Available animations
The kit vendors the 9 canonical Yuno empty-state animations under public/empty-state/animations/{slug}/{light|dark}.html so prototypes can drop them into the illustration slot immediately — no assets to hunt for. These are meant as high-fidelity placeholders while PMs / designers show the empty-state to stakeholders; a final illustration for the specific feature you're prototyping can swap them out later. Each slug has both a light and a dark variant, and AnimationFrame picks the right one for you: it observes the kit theme and scales the fixed 592x444 scene to its container rather than cropping it. Import it from the pattern, the same place EmptyStateHero comes from.
| Slug | Iframe src (light) | Iframe src (dark) |
|---|---|---|
| rules | /empty-state/animations/rules/light.html | /empty-state/animations/rules/dark.html |
| blocklists | /empty-state/animations/blocklists/light.html | /empty-state/animations/blocklists/dark.html |
| allowlists | /empty-state/animations/allowlists/light.html | /empty-state/animations/allowlists/dark.html |
| payments | /empty-state/animations/payments/light.html | /empty-state/animations/payments/dark.html |
| installments | /empty-state/animations/installments/light.html | /empty-state/animations/installments/dark.html |
| recipients | /empty-state/animations/recipients/light.html | /empty-state/animations/recipients/dark.html |
| subscriptions | /empty-state/animations/subscriptions/light.html | /empty-state/animations/subscriptions/dark.html |
| payment-links | /empty-state/animations/payment-links/light.html | /empty-state/animations/payment-links/dark.html |
| chargebacks | /empty-state/animations/chargebacks/light.html | /empty-state/animations/chargebacks/dark.html |
Import
Copy this import line at the top of the file where you compose this molecule.
// Direct sibling imports (recommended)
import {
EmptyStateInline,
EmptyStateHero,
} from "@/components/patterns/empty-state";
// Namespace import (matches the Accordion.Root pattern in the kit)
import { EmptyState } from "@/components/patterns/empty-state";
// then: <EmptyState.Inline … /> or <EmptyState.Hero … />Props
Everything else is forwarded to the underlying elements via ...props.
EmptyStateInline
| Prop | Type | Default | Description |
|---|---|---|---|
| icon | PhosphorIcon (required) | — | Phosphor icon component (from @phosphor-icons/react/dist/ssr). Pass the component itself — the pattern renders it at size-5 with weight="light". |
| title | string (required) | — | Primary text. text-sm foreground semibold. |
| description | string | — | Optional muted subtitle below the title. text-xs muted. |
| action | ReactNode | — | Optional CTA below the text stack. Canonical: <Button variant="outline" size="sm"> with a route (asChild + <a>) or a plain onClick. |
| className | string | — | Extra classes on the outer container. Use to override the bg-muted / rounded-lg surface if you need a transparent variant sitting on a colored background. |
EmptyStateHero
| Prop | Type | Default | Description |
|---|---|---|---|
| tagline | string | — | Optional small label above the title (text-sm muted medium). Canonical: "No rules created yet", "No payments yet". |
| title | string (required) | — | Big heading. text-4xl bold tracking-tight leading-10. |
| description | string | — | Optional paragraph below the title. Constrained to max-w-lg (512px) so long copy still reads as a paragraph. |
| action | ReactNode | — | CTA below the description. Canonical: a shadcn <Button> (default variant, size default = h-9) with the action label ("Create rule", "Add recipient"). |
| illustration | ReactNode (required) | — | The illustration to render opposite the text stack. Canonical: <iframe> pointing at one of the vendored animations under /empty-state/animations/{slug}/{light|dark}.html. Also accepts inline <svg> or <img>. |
| illustrationSize | { width: number; height: number } | { width: 592, height: 444 } | Illustration container size. Defaults to the canonical Yuno 592×444 matching the vendored animation assets. Override only for differently-proportioned illustrations. |
| className | string | — | Extra classes on the outer container. |
Related
Cross-links to the atoms this molecule composes and sibling patterns.
- Chart cardCanonical host of the Inline sibling — ChartCard's Main body is designed to absorb EmptyStateInline (flex-1 min-h-0).
- Table cardSame host relationship for empty-state Tables — drop EmptyStateInline in the body when the table has no rows.
- ButtonThe canonical action slot content — outline variant, size sm.
- IconographyThe tile icon uses Phosphor weight="light" (kit canon).