Empty State
What to show when a list, grid, search or collection has no items: what is empty, why, and what to do next.
import {
EmptyState,
EmptyStateAction,
EmptyStateHeading,
EmptyStateText,
} from '@fairgarden-private/design/components/EmptyState'
Render it in place of the list or grid. Pass role="status" when it replaces dynamic results. Text stays in the scope's full ink, never a faint gray. It is a sanctioned place for one ornament: a sticker drawing or a trail, never both. Don't use it for errors or loading.
Kinds
Kinds
framed (default) centers the message in a dashed frame; dashed means provisional, a place not yet filled. EmptyStateTrail draws the short-tail trail, a dashed line in the accent that ends in a triangle pointing down at the action. illustrated adds a sticker in EmptyStateDrawing (152 px, 64 px below 360 px of container): pass the art's viewBox, its halo path and its line art, and the Sticker draws the halo and line in its own light scope. From 1024 px of container the drawing sits beside the text, capped at 480 px. Use a drawing or a trail, never both. inline is one sentence in place of the content inside a dense panel, with no frame. Use an outline Button, or the solid one when it is the page's main next step.
No saved trails yet
Save a trail from its page and it will wait for you here.
No sightings this week
Be the first to log a bird on the preserve.
Readings will appear here after the first sync.
import { Button } from '@fairgarden-private/design/components/Button'
import {
EmptyState,
EmptyStateAction,
EmptyStateDrawing,
EmptyStateHeading,
EmptyStateText,
EmptyStateTrail,
} from '@fairgarden-private/design/components/EmptyState'
import { StickerDetail } from '@fairgarden-private/design/components/Sticker'
import styles from './kinds.module.css'
/** Framed (default) with the trail, illustrated with a sticker, and inline. */
export function EmptyStateKinds() {
return (
<div className={styles.stack}>
<EmptyState>
<EmptyStateTrail />
<EmptyStateHeading>No saved trails yet</EmptyStateHeading>
<EmptyStateText>Save a trail from its page and it will wait for you here.</EmptyStateText>
<EmptyStateAction>
<Button>Browse Trails</Button>
</EmptyStateAction>
</EmptyState>
<EmptyState kind="illustrated">
<EmptyStateDrawing
viewBox="0 0 152 152"
halo={
<path d="M36 128c-8 0-8-16 0-16h4c-4-12-10-32-10-54 0-12 14-14 28-8 4-12 22-20 42-18h12c8 0 6 12 4 20-6 20-18 28-28 32v28h28c8 0 8 16 0 16z" />
}
>
{/* A seedling: silhouette at the S weight, the leaf veins one step lighter. */}
<path d="M76 120V64" />
<path d="M76 84c-16 0-32-10-36-28 18-2 32 8 36 28z" />
<path d="M76 72c14-2 28-14 32-30-18 0-30 12-32 30z" />
<path d="M44 120h64" />
<StickerDetail>
<path d="M74 82 50 60M78 70l24-22" />
</StickerDetail>
<circle cx="76" cy="120" r="3" fill="currentColor" stroke="none" />
</EmptyStateDrawing>
<EmptyStateHeading>No sightings this week</EmptyStateHeading>
<EmptyStateText>Be the first to log a bird on the preserve.</EmptyStateText>
<EmptyStateAction>
<Button variant="solid">Log a Sighting</Button>
</EmptyStateAction>
</EmptyState>
<div className={styles.panel}>
<EmptyState kind="inline">
<EmptyStateText>Readings will appear here after the first sync.</EmptyStateText>
</EmptyState>
</div>
</div>
)
}
On paper and forest
On paper and forest
The frame, text and trail re-resolve on every ground; a sticker keeps its own light face. When a filter caused the empty state, set filtered: it becomes a polite status region and is left out of print. Name the filter and offer "Clear Filters".
paper
Nothing here yet
New events appear every Monday.
forest field
Nothing here yet
New events appear every Monday.
import { Button } from '@fairgarden-private/design/components/Button'
import {
EmptyState,
EmptyStateAction,
EmptyStateHeading,
EmptyStateText,
} from '@fairgarden-private/design/components/EmptyState'
import { Ground } from '@fairgarden-private/design/components/Ground'
import styles from './grounds.module.css'
/** The dashed frame and text re-resolve on a page ground and inside a deep field. */
export function EmptyStateGrounds() {
return (
<div className={styles.row}>
<Ground kind="face" preset="paper" className={styles.face}>
<p className={styles.name}>paper</p>
<Empty />
</Ground>
<Ground kind="field" preset="forest" className={styles.face}>
<p className={styles.name}>forest field</p>
<Empty />
</Ground>
</div>
)
}
function Empty() {
return (
<EmptyState>
<EmptyStateHeading>Nothing here yet</EmptyStateHeading>
<EmptyStateText>New events appear every Monday.</EmptyStateText>
<EmptyStateAction>
<Button>See Past Events</Button>
</EmptyStateAction>
</EmptyState>
)
}
API Reference
EmptyState
The empty state, rendered in place of a list or grid: an optional
EmptyStateDrawing or EmptyStateTrail (one, never both), an
EmptyStateHeading, an EmptyStateText and an EmptyStateAction (an
outline Button, or the solid one when it is the page’s main next step).
Pass role="status" when it replaces dynamic results (automatic with
filtered). Never set its text lighter than the scope’s ink.
| Prop | Type | Description |
|---|---|---|
filtered | | A filter caused it: the root becomes a polite status region
( |
kind | |
|
primary | | Primary Radix scale: the frame, heading and text. Never defaulted; omitted, it inherits the scope [D133]. |
secondary | | Secondary Radix scale: only the trail ornament ( |
EmptyStateDrawing and EmptyStateTrail
The drawing (illustrated): a §6.5 Sticker at --ds-size-art-s with its
halo, decorative (aria-hidden); the spot size --size-px-9 below
360 px. Pass the asset’s viewBox, its pre-expanded halo path and its
line art as children. The sticker brings its own white scope, so it
looks the same on every ground and ignores the empty state’s props.
| Prop | Type | Description |
|---|---|---|
halo | | The halo layer: the asset’s pre-expanded path, 8 px ( |
viewBox | | The asset’s viewBox, drawn at its native size: |
children | | The line art, stroked in |
The trail, the alternative to a drawing: the short-tail ornament-trail
(utils/Ornament), from its origin circle to a marker-terminal that
points down at the action, dashed in --role-accent, decorative. Hidden
if it would touch type.
EmptyStateHeading, EmptyStateText and EmptyStateAction
What is empty: type-itemhead in --primary12, centered. Renders <h3>.
Why, and what to do next: type-body-ui in --primary12, at most 40ch.
When a filter caused it, name the filter (“No events match ‘Virtual’").
The next step: an outline Button (md), or the solid one when it is the page’s main next step; “Clear Filters” after a filter (title case).
Additional types
emptyState
type emptyState = emptyStateEmptyStateActionProps
Props for EmptyStateAction: div props and render.
type EmptyStateActionProps = useRender.ComponentProps<'div'>EmptyStateDrawingProps
Props for EmptyStateDrawing: div props, render, and the sticker’s art (viewBox, halo, line art as children).
type EmptyStateDrawingProps = {
/**
* The line art, stroked in `--role-heading` at the silhouette weight
* (`--ds-stroke-3` at S, `--ds-stroke-4` at L), round caps and joins, no
* fill; give ink-spot parts `fill="currentColor"`, and wrap interior lines
* in `StickerDetail`. No text inside the art.
*/
children: React.ReactNode;
/**
* The asset's viewBox, drawn at its native size: `0 0 152 152` for S,
* `0 0 312 312` for L (other aspect ratios keep the width).
*/
viewBox: string;
/**
* The halo layer: the asset's pre-expanded path, 8 px (`--ds-space-halo`)
* outside every part, merged into one silhouette with rounded concave
* bridges and no sharp outer corners. Never a runtime stroke, outline or
* blur. Filled `--primary1`; no edge, ever. Ignored with `bare`.
*/
halo?: React.ReactNode;
}EmptyStateHeadingProps
Props for EmptyStateHeading: heading props and render (default <h3>).
type EmptyStateHeadingProps = useRender.ComponentProps<'h3'>EmptyStateKind
The three empty-state builds (§12.16).
type EmptyStateKind = 'framed' | 'inline' | 'illustrated'EmptyStateProps
Props for EmptyState: div props, render, the kind, filtered and the color axes.
type EmptyStateProps = React.ClassAttributes<HTMLDivElement> &
React.HTMLAttributes<HTMLDivElement> &
__type & {
kind?: EmptyStateKind;
filtered?: boolean;
primary?:
| 'ruby'
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| null;
secondary?:
| 'ruby'
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| 'amber'
| 'blue'
| 'cyan'
| 'grass'
| 'green'
| 'jade'
| 'lime'
| 'mint'
| 'orange'
| 'sky'
| 'teal'
| 'yellow'
| null;
}EmptyStateTextProps
Props for EmptyStateText: paragraph props and render.
type EmptyStateTextProps = useRender.ComponentProps<'p'>EmptyStateTrailProps
Props for EmptyStateTrail: div props and render; the trail itself is drawn.
type EmptyStateTrailProps = Omit<useRender.ComponentProps<'div', {}, HTMLProps>, 'children'>Specification: DESIGN-SYSTEM.md §12.16 (empty state) and §5.10.2 (module reflow).