State
A self-contained block for empty / loading / error / success states. Used inside cards, sections, panels, or table containers when content is unavailable or an operation needs feedback.
When to use
- Empty: a list, table, or section has no items to display.
- Loading: content is being fetched and a Spinner or Skeleton isn't the right shape (e.g. a centered "Loading projects…" with a Spinner icon inside the State block).
- Error: an operation failed and the user can retry or see details.
- Success: a one-time confirmation occupies a region (typically replaced by content after a moment, or persists as a "done" view).
For inline loading, prefer Spinner or Dots. For layout-shaped
placeholders, prefer Skeleton. State is for when the whole
container should communicate a single status.
Variants
| Variant | Class | Use for |
|---|---|---|
| Default | .state |
Canvas-tone background, generous pad |
| Bordered | .state.-bordered |
Raised background + hairline border |
| Compact | .state.-compact |
Smaller padding + title size |
data-state attribute drives icon and title colors:
| Value | Effect |
|---|---|
empty |
Icon: ink-subtle. Title: ink-strong. |
loading |
Icon: accent. Title: ink-strong. |
error |
Icon: danger. Title: danger. |
success |
Icon: success. Title: success. |
Anatomy
<section class="state" data-state="empty">
<div class="state__icon">📭</div>
<h3 class="state__title">No projects yet</h3>
<p class="state__description">Create your first project to get started.</p>
<div class="state__action">
<button class="button -primary">New project</button>
</div>
</section>
<section class="state" data-state="loading">
<div class="state__icon">
<span class="spinner -l" role="status" aria-label="Loading"></span>
</div>
<h3 class="state__title">Loading projects…</h3>
</section>
<section class="state -bordered" data-state="error">
<div class="state__icon">⚠</div>
<h3 class="state__title">Couldn't load</h3>
<p class="state__description">
The server returned an error. Check your connection and try again.
</p>
<div class="state__action">
<button class="button -primary">Retry</button>
<button class="button -ghost">Cancel</button>
</div>
</section>
<section class="state -compact" data-state="success">
<div class="state__icon">✓</div>
<h3 class="state__title">All done</h3>
<p class="state__description">Your changes have been saved.</p>
</section>
Every part is optional except the wrapper. Authors compose only what the state needs.
Tokens used
From semantic tier
--color-surface-canvas— default background--color-surface-raised— bordered variant--color-ink-strong,--color-ink-regular,--color-ink-subtle--color-accent— loading state icon--color-danger— error state icon + title--color-success— success state icon + title--color-border-muted— bordered variant--space-inset-block-l/-m— padding--space-gap-elements-m/-s--size-control-xl— icon area--radius-card--border-width-hairline--font-heading-title-*,--font-heading-section-*,--font-body-regular-*,--font-display-statement-*
Accessibility
- Wrap in
<section>so screen readers can navigate to it as a landmark. - The title (
.state__title) is a real heading element — use<h2>–<h4>matching the document outline. - For
data-state="loading", the inner Spinner or status indicator carriesrole="status"andaria-label. - For
data-state="error", consider anaria-live="polite"parent region so error appearance is announced. - The icon glyph (emoji or SVG) is presentational. If using an emoji,
add
aria-hidden="true"to the.state__icon; if using an SVG, addaria-hidden="true"directly on the SVG element.
Edge cases
- Long descriptions: the description is capped at
max-inline-size: 48chso it doesn't sprawl across very wide containers. - Two actions: the
.state__actionrow is a flex container — two buttons sit side by side with the standard gap. - No icon: acceptable. Skip
.state__iconentirely; the title takes its place. - Composed inside a Card: the State block already has padding and
background; place it as the Card body directly (no extra
.card__bodywrapper).
Do
- Use
data-stateso the visual treatment matches the message. - Keep titles short — 3-6 words. The description carries detail.
- Always provide an action for error states.
Don't
- Don't render multiple
<h3>s inside one State block. - Don't use State for content that loads in pieces. Use Skeleton for layout-aware placeholders.
- Don't fake an error state with an empty list. Distinguish "no data yet" (empty) from "fetch failed" (error).