Components · Data display & feedback
Empty state
The component behind a blank screen — a centred icon-tile, a heading, a one-line description, and the action that fills the void. Now a first-class spec in v1.2 (the Patterns section shows the composition; this is the tokenised primitive).
Preview
No projects yet
Create your first project to start deploying — import from Git or begin from a template.
Build with the skill
No package to install — hand this to your AI to generate or migrate the component in your stack.
Apply the Zetta "Empty state" component to this project using the zetta-design-md skill.
WHEN TO USE
- Migrating or fixing existing code (the common case): audit the current implementation against the `empty-state` spec (variants: default, compact) and CORRECT every deviation — replace off-spec values, don't only add new ones.
- New component: build it to the `empty-state` spec in this project's stack.
AUDIT & FIX (when the component already exists)
Compare it to the skill's spec and fix, don't just append: hardcoded colors/hex, off-scale radius, wrong fonts, dark/navy fills, shadows used in place of borders (or missing borders), brand lime used as a CTA or interaction (must be primary indigo), and any missing or wrong hover / focus / active / disabled / invalid state or missing 2px focus ring. Replace each with the correct Zetta token.
ALWAYS
- Pull the authoritative spec and tokens from the skill — never guess or hardcode values.
- Use this project's own component conventions and framework idioms.
- Honor the Zetta guardrails (brand = identity only, primary = interaction, shadows for overlays) and verify in Light, Dark, and Accessibility. Anatomy & rules
╭──────╮
│ ★ │ ← icon-tile-lg or illustration
╰──────╯
No projects yet
Create your first project to get
started with your team. ← description (max 360px wide)
[Create project] [Learn more] ← primary + secondary action
| State | Visual |
|---|---|
| Default | Illustration + heading + body + optional action |
| With action hover | Action button applies its own hover state |
When to use
- Tables with zero rows.
- Lists, grids, or panels with no content.
- Search results with no matches.
- Restricted or inaccessible content.
When not to use
- Loading states — use Skeleton instead.
- As a placeholder before the user interacts — only show after a data fetch completes.
Do
- Always include a heading. Description and action are optional but recommended.
- Make the primary action directly resolve the empty state ("Create project", "Add member").
- Match the icon or illustration to the type of missing content.
Don't
- Don't use generic headings like "Nothing here" — be specific about what is missing.
- Don't show more than two actions.
- Heading: "No [items] yet" or "No results for [X]". 3–6 words.
- Description: 1–2 sentences max. Explain why and what to do. "Create your first project to get started."
- Primary action: verb phrase matching the creation action. "Create project", not "Get started".
Keyboard
Empty State has no keyboard behavior of its own. When it includes an action button, keyboard interaction follows the standard Button spec.
Accessibility
- Heading renders as appropriate
<h>level matching the page hierarchy — not always<h2>. - If the empty state appears inside a table: render it as a
<tr><td colspan="N">row — do not replace the table with a div. - Primary action must be a
<button>or<a>— not a<div>.
Spec
Values and token references resolved straight from the Zetta spec.
empty-state
- display
- flex
- flexDirection
- column
- alignItems
- center
- justifyContent
- center
- textAlign
- center
- gap
- {spacing.md}
- paddingX
- {spacing.xl}
- paddingY
- {spacing.2xl}
empty-state-illustration
- width
- 80px
- height
- 80px
- marginBottom
- {spacing.sm}
empty-state-icon-tile
- marginBottom
- {spacing.sm}
empty-state-heading
- fontFamily
- Inter
- fontSize
- 18px
- fontWeight
- 600
- lineHeight
- 1.35
- textColor
- {colors.ink}
empty-state-description
- fontFamily
- Geist
- fontSize
- 14px
- fontWeight
- 400
- lineHeight
- 1.60
- textColor
- {colors.muted}
- maxWidth
- 360px
empty-state-actions
- display
- flex
- alignItems
- center
- gap
- {spacing.sm}
- marginTop
- {spacing.xs}
empty-state-compact
- paddingX
- {spacing.md}
- paddingY
- {spacing.lg}
empty-state-compact-heading
- fontFamily
- Geist
- fontSize
- 14px
- fontWeight
- 500
- textColor
- {colors.muted}
empty-state-compact-description
- fontSize
- 12px
- textColor
- {colors.muted}