Empty

ui-empty fills the space where content would be when there is none yet. It is a dashed, centred box built from parts: ui-empty-header holds optional ui-empty-media, a ui-empty-title and a ui-empty-description, and ui-empty-content holds the actions underneath. It has no role of its own.

Previewindex.html
No projects yet You have not created any projects. Get started by creating your first one.
Create project Import project
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Empty demo</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
  <script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
  <style>
    .demo-full {
      width: 100%;
    }
    .demo-row-tight {
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      gap: calc(var(--spacing) * 2);
    }
  </style>
</head>
<body>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/button.html"></import-element>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/empty.html"></import-element>

  <div class="demo-full">
    <ui-empty>
      <ui-empty-header>
        <ui-empty-media variant="icon">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><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"/></svg>
        </ui-empty-media>
        <ui-empty-title>No projects yet</ui-empty-title>
        <ui-empty-description>You have not created any projects. Get started by creating your first one.</ui-empty-description>
      </ui-empty-header>
      <ui-empty-content>
        <div class="demo-row-tight">
          <ui-button>Create project</ui-button>
          <ui-button variant="outline">Import project</ui-button>
        </div>
      </ui-empty-content>
    </ui-empty>
  </div>
</body>
</html>

Examples

Without media

Leave out ui-empty-media for a text-only empty state.

Previewindex.html
No results Try a different search term or clear the filters. Clear filters
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Empty demo</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
  <script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
  <style>
    .demo-full {
      width: 100%;
    }
  </style>
</head>
<body>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/button.html"></import-element>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/empty.html"></import-element>

  <div class="demo-full">
    <ui-empty>
      <ui-empty-header>
        <ui-empty-title>No results</ui-empty-title>
        <ui-empty-description>Try a different search term or clear the filters.</ui-empty-description>
      </ui-empty-header>
      <ui-empty-content>
        <ui-button variant="outline" size="sm">Clear filters</ui-button>
      </ui-empty-content>
    </ui-empty>
  </div>
</body>
</html>

With an avatar

Without variant, ui-empty-media is a plain wrapper, so an avatar or an avatar group keeps its own look.

Previewindex.html
LR User offline This user is currently offline. You can leave a message to notify them. Leave a message
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Empty demo</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
  <script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
  <style>
    .demo-full {
      width: 100%;
    }
  </style>
</head>
<body>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/avatar.html"></import-element>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/button.html"></import-element>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/empty.html"></import-element>

  <div class="demo-full">
    <ui-empty>
      <ui-empty-header>
        <ui-empty-media>
          <ui-avatar size="lg"><ui-avatar-fallback>LR</ui-avatar-fallback></ui-avatar>
        </ui-empty-media>
        <ui-empty-title>User offline</ui-empty-title>
        <ui-empty-description>This user is currently offline. You can leave a message to notify them.</ui-empty-description>
      </ui-empty-header>
      <ui-empty-content>
        <ui-button size="sm">Leave a message</ui-button>
      </ui-empty-content>
    </ui-empty>
  </div>
</body>
</html>

With an input

ui-empty-content is a centred column capped at calc(var(--spacing) * 96), so it also fits a search box and a note.

Previewindex.html
404 - Not found The page you are looking for does not exist. Try searching for what you need.

Need help? Contact support

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Empty demo</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
  <script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
  <style>
    .demo-full {
      width: 100%;
    }
    .demo-muted {
      margin: 0;
      font-size: var(--text-sm);
      color: var(--muted-foreground);
    }
  </style>
</head>
<body>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/empty.html"></import-element>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/input.html"></import-element>

  <div class="demo-full">
    <ui-empty>
      <ui-empty-header>
        <ui-empty-title>404 - Not found</ui-empty-title>
        <ui-empty-description>The page you are looking for does not exist. Try searching for what you need.</ui-empty-description>
      </ui-empty-header>
      <ui-empty-content>
        <div class="demo-full">
          <ui-input type="search" aria-label="Search pages" placeholder="Search pages"></ui-input>
        </div>
        <p class="demo-muted">Need help? <a href="#">Contact support</a></p>
      </ui-empty-content>
    </ui-empty>
  </div>
</body>
</html>

Shown when a list is empty

Switch between the list and the empty state with <is-visible> and <is-hidden>.

Previewindex.htmlempty-inbox-demo.html
  • Archive
Inbox zero Every message has been archived. Restore messages
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Empty demo</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
  <script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
</head>
<body>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/button.html"></import-element>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/empty.html"></import-element>

  <import-element from="./empty-inbox-demo.html"></import-element>
  <empty-inbox-demo></empty-inbox-demo>
</body>
</html>
<custom-element name="empty-inbox-demo">
  <script>
    const inbox = useState({
      messages: [
        { id: 1, subject: "Weekly report" },
        { id: 2, subject: "Invoice INV-204" },
      ],
      get empty() {
        return this.messages.length === 0;
      },
    });

    function archive(message) {
      inbox.messages = inbox.messages.filter((item) => item.id !== message.id);
    }

    function restore() {
      inbox.messages = [
        { id: 1, subject: "Weekly report" },
        { id: 2, subject: "Invoice INV-204" },
      ];
    }
  </script>
  <style>
    :host {
      display: block;
      width: 100%;
    }
    ul {
      display: grid;
      gap: calc(var(--spacing) * 2);
      margin: 0;
      padding: 0;
      list-style: none;
    }
    li {
      display: flex;
      align-items: center;
      justify-content: space-between;
      padding: calc(var(--spacing) * 2) calc(var(--spacing) * 3);
      border: 1px solid var(--border);
      border-radius: var(--radius-md);
      font-size: var(--text-sm);
    }
  </style>
  <is-hidden when="#inbox.empty">
    <ul>
      <li repeat="#inbox.messages" as="message" key="id">
        <slot name="#message.subject"></slot>
        <ui-button size="sm" variant="ghost" onclick="archive(message)">Archive</ui-button>
      </li>
    </ul>
  </is-hidden>
  <is-visible when="#inbox.empty">
    <ui-empty>
      <ui-empty-header>
        <ui-empty-title>Inbox zero</ui-empty-title>
        <ui-empty-description>Every message has been archived.</ui-empty-description>
      </ui-empty-header>
      <ui-empty-content>
        <ui-button size="sm" variant="outline" onclick="restore()">Restore messages</ui-button>
      </ui-empty-content>
    </ui-empty>
  </is-visible>
</custom-element>

API

Parts

Part Slot Renders
ui-empty The dashed, centred box. A flex column with a gap between header and content.
ui-empty-header (default) A centred column for media, title and description, capped at calc(var(--spacing) * 96).
ui-empty-media (default) A wrapper above the title for an icon, avatar or image. Place it in ui-empty-header.
ui-empty-title (default) A heading (level 3 unless level says otherwise) in var(--text-lg). Place it in ui-empty-header.
ui-empty-description (default) Supporting text in muted var(--text-sm). Place it in ui-empty-header.
ui-empty-content (default) Actions or inputs under the header, in a centred column capped at calc(var(--spacing) * 96).

Every part has a single default slot that takes any content.

ui-empty-media attributes

Attribute Type Default Description
variant "icon" Shows the content in a muted, rounded calc(var(--spacing) * 10) square. Without it the media is a plain wrapper.

A slotted <svg> is sized to calc(var(--spacing) * 6).

ui-empty-title attributes

Attribute Type Default Description
level number, 1 to 6 3 The heading level, so the title fits the page outline.

The other parts have no attributes.

CSS parts

Element Part Description
ui-empty-title title The heading around the title text.

Accessibility

The element has no role. ui-empty-title is a heading with aria-level from level (3 by default). If the empty state appears after a user action (such as a search), consider announcing it with a live region of your own.

Edit this page on GitHub · View as Markdown