Create your first project to get started.
The server returned an error. Check your connection and try again.
Your changes have been saved.
/*
* State — block
*
* A self-contained block for empty / loading / error / success states.
* Used inside cards, sections, or panels when content is unavailable
* or operation feedback should fill the surface. CSS-only.
*
* <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>
*/
@layer malevich.components {
.state {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
gap: var(--space-gap-elements-m);
padding-block: var(--space-inset-block-l);
padding-inline: var(--space-inset-block-l);
border-radius: var(--radius-card);
background-color: var(--color-surface-canvas);
color: var(--color-ink-strong);
}
.state__icon {
inline-size: var(--size-control-xl);
block-size: var(--size-control-xl);
display: flex;
align-items: center;
justify-content: center;
color: var(--color-ink-subtle);
font-family: var(--font-display-statement-family);
font-size: var(--font-display-statement-size);
font-weight: var(--font-display-statement-weight);
}
.state__title {
margin: 0;
font-family: var(--font-heading-title-family);
font-size: var(--font-heading-title-size);
font-weight: var(--font-heading-title-weight);
line-height: var(--font-heading-title-line-height);
letter-spacing: var(--font-heading-title-letter-spacing);
color: var(--color-ink-strong);
}
.state__description {
margin: 0;
max-inline-size: 48ch;
font-family: var(--font-body-regular-family);
font-size: var(--font-body-regular-size);
font-weight: var(--font-body-regular-weight);
line-height: var(--font-body-regular-line-height);
letter-spacing: var(--font-body-regular-letter-spacing);
color: var(--color-ink-regular);
}
.state__action {
display: inline-flex;
align-items: center;
gap: var(--space-gap-elements-s);
margin-block-start: var(--space-gap-elements-s);
}
/* Data-state hooks for icon color affordances. The icon's color
* follows the data-state so authors can render a single icon and
* have it tint correctly.
*/
.state[data-state="empty"] .state__icon {
color: var(--color-ink-subtle);
}
.state[data-state="loading"] .state__icon {
color: var(--color-accent);
}
.state[data-state="error"] .state__icon {
color: var(--color-danger);
}
.state[data-state="error"] .state__title {
color: var(--color-danger);
}
.state[data-state="success"] .state__icon {
color: var(--color-success);
}
.state[data-state="success"] .state__title {
color: var(--color-success);
}
/* Variants */
.state.-bordered {
border: var(--border-width-hairline) solid var(--color-border-muted);
background-color: var(--color-surface-raised);
}
.state.-compact {
padding-block: var(--space-inset-block-m);
padding-inline: var(--space-inset-block-m);
gap: var(--space-gap-elements-s);
}
.state.-compact .state__title {
font-family: var(--font-heading-section-family);
font-size: var(--font-heading-section-size);
font-weight: var(--font-heading-section-weight);
line-height: var(--font-heading-section-line-height);
letter-spacing: var(--font-heading-section-letter-spacing);
}
}
This component is pure CSS — no JavaScript required.
# State — AGENTS.md
> Auto-generated from `state.docs.md` + the modifier applicability matrix.
> Edit those source files; this file regenerates on build.
## Identity
- **Component:** `state`
- **Layer:** blocks
- **Status:** stable
- **Last updated:** 2026-05-19
## Summary
- **Empty:** a list, table, or section has no items to display.
## 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. |
## Modifier applicability
Per ADR-0007. Modifiers attach via `data-{category}="value"` attributes.
| Category | Accepts |
|---|---|
| `background` | `solid` |
| `surface` | `flat`, `elevated` |
| `effect` | — |
| `shader` | — |
| `rhythm` | `compact`, `regular` |
| `motion` | any value |
| `behavior` | — |
## Anatomy
```html
<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 consumed
### 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 contract
- 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
carries `role="status"` and `aria-label`.
- For `data-state="error"`, consider an `aria-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,
add `aria-hidden="true"` directly on the SVG element.
## Guidance
### Do
- Use `data-state` so 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).