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.

CategoryFeedback
TypeCSS only (.mv-empty)
Statusstable
Keywordsempty, empty-state, état vide, no-data, zero, placeholder, error, onboarding

When to use

Avoid when

Install

node scripts/add.mjs empty --out ./src/marvelous

AI 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

NameTypeDefaultDescription
data-variantcompact | 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) | solidOn .mv-empty: dashed border, or solid with a surface background.
data-toneaccent | dangerOn .mv-empty-media: tints the tile.
data-shapecircleOn .mv-empty-media: round tile.
data-decorationrings | grid | noneringsOn .mv-empty-media: decoration behind the tile.

CSS classes

NameDescription
mv-emptyCentered container (a section with aria-labelledby, or role="status" for a search result).
mv-empty-mediaTile holding an <svg> icon; ring decoration behind it.
mv-empty-title / -descriptionTitle and explanatory text.
mv-empty-actionsAction buttons.
mv-empty-footerSecondary 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.