Empty — .mv-empty
Pure-CSS empty state: an icon tile over fading rings or a grid, title, description, actions and footer; compact (lists, tables) and illustrated (fanned cards over a dot pattern) variants.
| Category | Feedback |
|---|---|
| Type | CSS only (.mv-empty) |
| Status | stable |
| Keywords | empty, empty-state, état vide, no-data, zero, placeholder, error, onboarding |
When to use
- A list, table or dashboard has no data yet and should explain why and offer a first action
- A search or filter returns no results and must suggest clearing filters or trying other terms
- A compact placeholder is needed inside a table or panel, or an illustrated one for a first-run screen
Avoid when
- The data is still loading rather than genuinely empty → use Skeleton instead
- The message is an error or warning about content that is otherwise present → use Alert instead
Install
node scripts/add.mjs empty --out ./src/marvelousAI agent with the Marvelous UI MCP server: install_components({ slugs: ["empty"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).
Files copied (dependencies included): tokens/tokens.css, core/base.css, components/empty/empty.css.
Usage
Canonical markup — start from it and customize with attributes, data-* and CSS variables:
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,20rem),1fr));gap:1.25rem;width:100%;align-items:stretch">
<!-- Default: rings + dashed border -->
<section class="mv-empty" data-border aria-labelledby="em-demo-t1">
<div class="mv-empty-media">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/><path d="M12 10v6M9 13h6"/></svg>
</div>
<h3 class="mv-empty-title" id="em-demo-t1">No projects yet</h3>
<p class="mv-empty-description">Create your first project to keep your tasks, files and deadlines in one place.</p>
<div class="mv-empty-actions">
<button class="mv-button" data-variant="outline" data-size="sm">Import</button>
<button class="mv-button" data-size="sm">Create project</button>
</div>
<p class="mv-empty-footer"><a href="#">Read the getting started guide</a></p>
</section>
<!-- Illustrated: fanned cards + dot grid -->
<section class="mv-empty" data-variant="illustrated" data-border="solid" aria-labelledby="em-demo-t2">
<div class="mv-empty-media" data-tone="accent">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M22 12h-6l-2 3h-4l-2-3H2"/><path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"/></svg>
</div>
<h3 class="mv-empty-title" id="em-demo-t2">Your inbox is empty</h3>
<p class="mv-empty-description">You’re all caught up! New messages from your customers will show up here.</p>
<div class="mv-empty-actions">
<button class="mv-button" data-variant="secondary" data-size="sm">Write a message</button>
</div>
</section>
<!-- Compact, inside a list panel -->
<div style="border:1px solid var(--mv-border);border-radius:var(--mv-radius-xl);background:var(--mv-surface);overflow:hidden">
<div style="display:flex;align-items:center;gap:.5rem;padding:.625rem .875rem;border-bottom:1px solid var(--mv-border);font-size:.8125rem;color:var(--mv-fg-muted)">
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg>
invoice march 2026
</div>
<div class="mv-empty" data-variant="compact" role="status">
<div class="mv-empty-media">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5M8.5 11h5"/></svg>
</div>
<p class="mv-empty-title">No results</p>
<p class="mv-empty-description">No invoices match “invoice march 2026”.</p>
<div class="mv-empty-actions"><button class="mv-button" data-variant="ghost" data-size="sm">Clear search</button></div>
</div>
</div>
<!-- Error: grid decoration, danger tone -->
<section class="mv-empty" aria-labelledby="em-demo-t4">
<div class="mv-empty-media" data-tone="danger" data-shape="circle" data-decoration="grid">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 9v4M12 17h.01"/><path d="M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z"/></svg>
</div>
<h3 class="mv-empty-title" id="em-demo-t4">Couldn’t load analytics</h3>
<p class="mv-empty-description">The analytics service isn’t responding. Try again in a few moments.</p>
<div class="mv-empty-actions">
<button class="mv-button" data-variant="outline" data-size="sm">Try again</button>
</div>
</section>
</div>API
Attributes
| Name | Type | Default | Description |
|---|---|---|---|
data-variant | compact | illustrated | (standard) | On .mv-empty: a tighter version without decoration, or an illustrated version with fanned cards (which spread on hover) and a dot pattern. |
data-border | (empty) | solid | On .mv-empty: dashed border, or solid with a surface background. | |
data-tone | accent | danger | On .mv-empty-media: tints the tile. | |
data-shape | circle | On .mv-empty-media: round tile. | |
data-decoration | rings | grid | none | rings | On .mv-empty-media: decoration behind the tile. |
CSS classes
| Name | Description |
|---|---|
mv-empty | Centered container (a section with aria-labelledby, or role="status" for a search result). |
mv-empty-media | Tile holding an <svg> icon; ring decoration behind it. |
mv-empty-title / -description | Title and explanatory text. |
mv-empty-actions | Action buttons. |
mv-empty-footer | Secondary line (help link). |
Accessibility
Decorations are pseudo-elements (invisible to screen readers) and icons are aria-hidden: the title carries the message. Use a real heading (h2/h3) for a page-level empty state, and role="status" when it appears after a search or filter so it gets announced. Reduced motion: the hover fan becomes instant.