# Empty state

The words that fill an area when what belongs there is missing or failed to load, naming the cause and offering the next step.

Block: `smbk-empty-state`. Status: experimental. Figma: the Figma file, https://www.figma.com/community/file/1688324308358870630. Desktop and mobile: differ by presentation only.

## Options

| Axis | Values | Default | Meaning |
|---|---|---|---|
| `scope` | `inline`, `centered`, `page` | `inline` | Inline sits at the start of a list or a panel it stands in for. Centered fills a larger area, centred, with a larger title. Page is the whole page, as a missing page or a page that failed to load. |
| `tone` | `default`, `warning`, `danger` | `default` | Warning and danger color the icon for a state that needs care or an error. The words carry the meaning; the color only backs them up. |
| `size` | `md`, `sm` | `md` | Small is for one panel among many, as on a dashboard: the title in a label style, a smaller icon and smaller gaps, so an empty panel reads no louder than its neighbors. |

Parts: `smbk-empty-state__icon`, `smbk-empty-state__text`, `smbk-empty-state__title`, `smbk-empty-state__description`, `smbk-empty-state__context`, `smbk-empty-state__actions`, `smbk-empty-state__reference`, `smbk-empty-state__hidden`

## Markup

```html
<div class="smbk-empty-state smbk-empty-state--scope-inline smbk-empty-state--tone-default smbk-empty-state--size-md">
  <i
    class="ph ph-magnifying-glass smbk-icon smbk-icon--size-md smbk-empty-state__icon"
    aria-hidden="true"
  ></i>
  <div class="smbk-empty-state__text">
    <h2 class="smbk-empty-state__title">No notes match these filters</h2>
    <p class="smbk-empty-state__description">Try fewer filters, or clear them all to see every note.</p>
  </div>
  <div class="smbk-empty-state__actions">
    <button type="button" class="smbk-btn smbk-btn--tone-brand smbk-btn--size-md">
      <span class="smbk-btn__label">Clear filters</span>
    </button>
    <a
      class="smbk-link smbk-link--size-md"
      href="#preview"
    ><span class="smbk-link__label">Learn more</span></a>
  </div>
</div>
```

## React

```tsx
import { EmptyState } from './design-system/components/empty-state/empty-state.js';
import { Button } from './design-system/components/button/button.js';
import { Link } from './design-system/components/link/link.js';

<EmptyState scope="inline" tone="default" size="md" icon="MagnifyingGlass" title="No notes match these filters" description="Try fewer filters, or clear them all to see every note." primaryAction={<Button size="md">Clear filters</Button>} secondaryAction={<Link size="md" href="#preview">Learn more</Link>} />
```

## Stylesheet

```css
/* smbk-empty-state — Empty state. Figma "Empty state" 7117:731 (Scope × Tone; Icon, Description and Reference
 * booleans). A block of words that stands in for missing or failed content: an icon in the tone's icon colour, the
 * title over a line saying what to do, the actions (a brand button, then a link), and an optional reference in Code
 * SM. Inline lines it all up at the start, tight to its edges; Centered and Page centre it, with more room round it,
 * a larger title and line, and the larger icon. It draws no surface: it fills whatever holds it. */

.smbk-empty-state {
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--smbk-space-stack-xl);
  padding: var(--smbk-space-inset-md);
  border-radius: var(--smbk-radius-container-md);
}

.smbk-empty-state__icon {
  flex: none;
  color: var(--smbk-color-icon-subtle);
  font-size: var(--smbk-size-icon-lg);
}

.smbk-empty-state__text {
  display: flex;
  flex-direction: column;
  gap: var(--smbk-space-text-caption);
  align-self: stretch;
}

.smbk-empty-state__title {
  margin: 0;
  color: var(--smbk-color-text-default);
  font-family: var(--smbk-typography-heading-sm-font-family);
  font-size: var(--smbk-typography-heading-sm-font-size);
  font-weight: var(--smbk-typography-heading-sm-font-weight);
  line-height: var(--smbk-typography-heading-sm-line-height);
  letter-spacing: var(--smbk-typography-heading-sm-letter-spacing);
  text-transform: var(--smbk-typography-heading-sm-text-transform);
  text-wrap: pretty;
}

.smbk-empty-state__description {
  margin: 0;
  color: var(--smbk-color-text-subtle);
  font-family: var(--smbk-typography-body-sm-font-family);
  font-size: var(--smbk-typography-body-sm-font-size);
  font-weight: var(--smbk-typography-body-sm-font-weight);
  line-height: var(--smbk-typography-body-sm-line-height);
  letter-spacing: var(--smbk-typography-body-sm-letter-spacing);
  text-wrap: pretty;
}

.smbk-empty-state__context {
  align-self: stretch;
}

.smbk-empty-state__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--smbk-space-inline-lg);
}

.smbk-empty-state__reference {
  margin: 0;
  color: var(--smbk-color-text-subtle);
  font-family: var(--smbk-typography-code-sm-font-family);
  font-size: var(--smbk-typography-code-sm-font-size);
  font-weight: var(--smbk-typography-code-sm-font-weight);
  line-height: var(--smbk-typography-code-sm-line-height);
  letter-spacing: var(--smbk-typography-code-sm-letter-spacing);
}

/* The status a screen reader reads the words from, when the page asks for it; the words show above. */
/* .smbk-empty-state__hidden: hidden from view, kept for screen readers and the keyboard, by the shared rule in visually-hidden/visually-hidden.css. */

/* ---- tone: the icon's colour; the words carry the meaning. The default tone is the block's own look. ---- */

.smbk-empty-state--tone-warning .smbk-empty-state__icon {
  color: var(--smbk-color-feedback-warning-icon);
}

.smbk-empty-state--tone-danger .smbk-empty-state__icon {
  color: var(--smbk-color-feedback-danger-icon);
}

/* ---- centered and page: centred in the space, with room round it and a measure for the words ---- */

.smbk-empty-state--scope-centered,
.smbk-empty-state--scope-page {
  align-items: center;
  margin-inline: auto;
  text-align: center;
}

.smbk-empty-state--scope-centered .smbk-empty-state__actions,
.smbk-empty-state--scope-page .smbk-empty-state__actions {
  justify-content: center;
}

.smbk-empty-state--scope-centered .smbk-empty-state__icon,
.smbk-empty-state--scope-page .smbk-empty-state__icon {
  font-size: var(--smbk-size-icon-2xl);
}

.smbk-empty-state--scope-centered {
  max-width: var(--smbk-size-width-640);
  padding: var(--smbk-space-inset-3xl);
}

.smbk-empty-state--scope-centered .smbk-empty-state__title {
  font-family: var(--smbk-typography-heading-md-font-family);
  font-size: var(--smbk-typography-heading-md-font-size);
  font-weight: var(--smbk-typography-heading-md-font-weight);
  line-height: var(--smbk-typography-heading-md-line-height);
  letter-spacing: var(--smbk-typography-heading-md-letter-spacing);
  text-transform: var(--smbk-typography-heading-md-text-transform);
}

.smbk-empty-state--scope-centered .smbk-empty-state__description {
  font-family: var(--smbk-typography-body-md-font-family);
  font-size: var(--smbk-typography-body-md-font-size);
  font-weight: var(--smbk-typography-body-md-font-weight);
  line-height: var(--smbk-typography-body-md-line-height);
  letter-spacing: var(--smbk-typography-body-md-letter-spacing);
}

.smbk-empty-state--scope-page {
  max-width: var(--smbk-size-content-md);
  padding: var(--smbk-space-section-md);
}

.smbk-empty-state--scope-page .smbk-empty-state__title {
  font-family: var(--smbk-typography-heading-lg-font-family);
  font-size: var(--smbk-typography-heading-lg-font-size);
  font-weight: var(--smbk-typography-heading-lg-font-weight);
  line-height: var(--smbk-typography-heading-lg-line-height);
  letter-spacing: var(--smbk-typography-heading-lg-letter-spacing);
  text-transform: var(--smbk-typography-heading-lg-text-transform);
}

.smbk-empty-state--scope-page .smbk-empty-state__description {
  font-family: var(--smbk-typography-body-lg-font-family);
  font-size: var(--smbk-typography-body-lg-font-size);
  font-weight: var(--smbk-typography-body-lg-font-weight);
  line-height: var(--smbk-typography-body-lg-line-height);
  letter-spacing: var(--smbk-typography-body-lg-letter-spacing);
}

/* ---- small: one panel among many, as on a dashboard. The title in Label LG straight on its line in Body SM,
   the medium icon and the medium stack step, so the empty panel reads no louder than the panels beside it or
   the page's own heading. Inline keeps its tight edges; centered keeps its centring with less room round it. Last,
   so it wins over each scope's own sizes. ---- */

.smbk-empty-state--size-sm {
  gap: var(--smbk-space-stack-md);
}

.smbk-empty-state--size-sm .smbk-empty-state__icon {
  font-size: var(--smbk-size-icon-md);
}

.smbk-empty-state--size-sm .smbk-empty-state__text {
  gap: var(--smbk-space-text-label);
}

.smbk-empty-state--size-sm .smbk-empty-state__title {
  font-family: var(--smbk-typography-label-lg-font-family);
  font-size: var(--smbk-typography-label-lg-font-size);
  font-weight: var(--smbk-typography-label-lg-font-weight);
  line-height: var(--smbk-typography-label-lg-line-height);
  letter-spacing: var(--smbk-typography-label-lg-letter-spacing);
  text-transform: var(--smbk-typography-label-lg-text-transform);
}

.smbk-empty-state--size-sm .smbk-empty-state__description {
  font-family: var(--smbk-typography-body-sm-font-family);
  font-size: var(--smbk-typography-body-sm-font-size);
  font-weight: var(--smbk-typography-body-sm-font-weight);
  line-height: var(--smbk-typography-body-sm-line-height);
  letter-spacing: var(--smbk-typography-body-sm-letter-spacing);
}

.smbk-empty-state--size-sm.smbk-empty-state--scope-centered,
.smbk-empty-state--size-sm.smbk-empty-state--scope-page {
  padding: var(--smbk-space-inset-xl);
}

.smbk-empty-state--size-sm.smbk-empty-state--scope-centered .smbk-empty-state__icon,
.smbk-empty-state--size-sm.smbk-empty-state--scope-page .smbk-empty-state__icon {
  font-size: var(--smbk-size-icon-lg);
}
```

## Tokens it reads

```json
{
  "space": {
    "stack": {
      "xl": {
        "$type": "dimension",
        "$value": "{{space.24}}",
        "$description": "Gap between things stacked one above another, extra large."
      },
      "md": {
        "$type": "dimension",
        "$value": "{{space.12}}",
        "$description": "Gap between things stacked one above another, medium."
      }
    },
    "inset": {
      "md": {
        "$type": "dimension",
        "$value": "{{space.8}}",
        "$description": "Padding inside a component, medium."
      },
      "3xl": {
        "$type": "dimension",
        "$value": "{{space.32}}",
        "$description": "Padding inside a component, 3× large."
      },
      "xl": {
        "$type": "dimension",
        "$value": "{{space.16}}",
        "$description": "Padding inside a component, extra large."
      }
    },
    "text": {
      "caption": {
        "$type": "dimension",
        "$value": "{{space.stack.xs}}",
        "$description": "A title to the small grey line under it: 4 on phones, 8 on wide screens."
      },
      "label": {
        "$type": "dimension",
        "$value": "{{space.0}}",
        "$description": "Between a label or title and the line of supporting text right under it, inside a component. None: untrimmed line height already spaces the two lines."
      }
    },
    "inline": {
      "lg": {
        "$type": "dimension",
        "$value": "{{space.12}}",
        "$description": "Gap between things side by side, large."
      }
    },
    "section": {
      "md": {
        "$type": "dimension",
        "$value": "{{space.64}}",
        "$description": "Rhythm of page sections, medium."
      }
    }
  },
  "radius": {
    "container": {
      "md": {
        "$type": "dimension",
        "$value": "{{radius.12}}",
        "$description": "Corners of cards, panels and dialogs, medium."
      }
    }
  },
  "color": {
    "icon": {
      "subtle": {
        "$type": "color",
        "$value": "{{color.grey.700}}",
        "$description": "Quiet icons."
      }
    },
    "text": {
      "default": {
        "$type": "color",
        "$value": "{{color.grey.900}}",
        "$description": "Main text."
      },
      "subtle": {
        "$type": "color",
        "$value": "{{color.grey.700}}",
        "$description": "Secondary text: descriptions, metadata."
      }
    },
    "feedback": {
      "warning": {
        "icon": {
          "$type": "color",
          "$value": "{{color.orange.600}}",
          "$description": "The warning tone’s own icon (a warning sign), so the tone is never shown by colour alone."
        }
      },
      "danger": {
        "icon": {
          "$type": "color",
          "$value": "{{color.red.600}}",
          "$description": "The danger tone’s own icon (an alert sign), so the tone is never shown by colour alone."
        }
      }
    }
  },
  "size": {
    "icon": {
      "lg": {
        "$type": "dimension",
        "$value": "{{size.24}}",
        "$description": "Icon box, large."
      },
      "2xl": {
        "$type": "dimension",
        "$value": "{{size.40}}",
        "$description": "Icon box, 2× large."
      },
      "md": {
        "$type": "dimension",
        "$value": "{{size.20}}",
        "$description": "Icon box, medium."
      }
    },
    "width": {
      "640": {
        "$type": "dimension",
        "$value": "640px",
        "$description": "A fixed width of 640 px. Raw step: use a semantic size."
      }
    },
    "content": {
      "md": {
        "$type": "dimension",
        "$value": "{{size.width.720}}",
        "$description": "Widest a content column may grow, medium."
      }
    }
  },
  "typography": {
    "heading-sm": {
      "$type": "typography",
      "$value": {
        "fontFamily": "Funnel Display, Funnel Display Fallback, system-ui, sans-serif",
        "fontSize": {
          "min": "24px",
          "max": "32px"
        },
        "fontWeight": 400,
        "lineHeight": 1.2,
        "letterSpacing": "0px"
      }
    },
    "body-sm": {
      "$type": "typography",
      "$value": {
        "fontFamily": "Geist, Geist Fallback, system-ui, sans-serif",
        "fontSize": "14px",
        "fontWeight": 400,
        "lineHeight": 1.5,
        "letterSpacing": "0px"
      }
    },
    "code-sm": {
      "$type": "typography",
      "$value": {
        "fontFamily": "Geist Mono, Geist Mono Fallback, ui-monospace, monospace",
        "fontSize": "12px",
        "fontWeight": 400,
        "lineHeight": 1.5,
        "letterSpacing": "0px"
      },
      "$description": "Code SM: keys (Kbd) and short code in a line of text. Geist Mono."
    },
    "heading-md": {
      "$type": "typography",
      "$value": {
        "fontFamily": "Funnel Display, Funnel Display Fallback, system-ui, sans-serif",
        "fontSize": {
          "min": "28px",
          "max": "40px"
        },
        "fontWeight": 400,
        "lineHeight": 1.2,
        "letterSpacing": "0px"
      }
    },
    "body-md": {
      "$type": "typography",
      "$value": {
        "fontFamily": "Geist, Geist Fallback, system-ui, sans-serif",
        "fontSize": "16px",
        "fontWeight": 400,
        "lineHeight": 1.5,
        "letterSpacing": "0px"
      }
    },
    "heading-lg": {
      "$type": "typography",
      "$value": {
        "fontFamily": "Funnel Display, Funnel Display Fallback, system-ui, sans-serif",
        "fontSize": {
          "min": "32px",
          "max": "48px"
        },
        "fontWeight": 400,
        "lineHeight": 1.2,
        "letterSpacing": "0px"
      }
    },
    "body-lg": {
      "$type": "typography",
      "$value": {
        "fontFamily": "Geist, Geist Fallback, system-ui, sans-serif",
        "fontSize": "20px",
        "fontWeight": 400,
        "lineHeight": 1.5,
        "letterSpacing": "0px"
      }
    },
    "label-lg": {
      "$type": "typography",
      "$value": {
        "fontFamily": "Geist, Geist Fallback, system-ui, sans-serif",
        "fontSize": "16px",
        "fontWeight": 500,
        "lineHeight": 1.4,
        "letterSpacing": "0px"
      },
      "$description": "Label LG: labels on controls and short UI text (buttons, chips, tabs, menu headings). Geist Medium, sentence case. Code and keys use Code SM and Code MD."
    }
  }
}
```

## Purpose

Fills an area when what belongs there is missing or failed to load: a list with nothing in it yet, a search with no results, a report the reader may not see, a page that is not there. It names the cause and offers the next best step, so the reader is never left facing a blank space or a bare "Something went wrong".

## Anatomy

- The block, which draws no surface: it fills whatever holds it.
- The icon part, an optional icon in the tone's icon color. Warning and danger have their own; an empty state in the default tone shows one only when given, chosen for its cause.
- The text part: the title part, a heading that says what is missing or what went wrong, over the optional description part, a line that says why and what would bring the content.
- The context part, optional free content: a search field holding the query, a short list of links.
- The actions part: the next best step, usually a brand button, then an optional second, quieter step, usually a link.
- The reference part, an optional reference in a code style, for the reader to quote to support after an error on the server.
- The hidden part, the status a screen reader reads the words from, when the page asks for it.

## When to use

- In place of a list, a table or a panel that has nothing to show: on first use, or after everything was cleared.
- In place of results, when a search or a filter matches nothing.
- Small, in one panel of a dashboard or a side panel that has nothing to show, among panels that do.
- When the content cannot be shown: no permission, offline, not found, an error on the server, or only part of it loaded.
- As a whole page, for a missing page or a page that failed to load.

## When not to use

- While the content is still loading. Show [skeletons](skeleton.md); show the empty state only once the answer has come.
- For a mistake in a form. Use the [field message](field-message.md) and the [error summary](error-summary.md).
- For a message about the whole page that sits above its content. Use an [alert](alert.md) or a [banner](banner.md).

## Behavior

Inline lines everything up at the start, close to its edges, for the list or panel it stands in for. Centered places it in the middle of a larger area with more room round it, a larger icon, title and line. Page does the same for a whole page. Centered and page keep their words to a readable width and centre themselves in the space they are given; inline fills it. The words wrap.

Two sizes. Medium, the default, titles it in a heading style. Small is for one panel among many, as on a dashboard: the title in a label style straight over its line, a smaller icon and smaller gaps, so an empty panel reads no louder than the panels beside it or its own heading. Small keeps the scope's alignment: inline at the start, centered in the middle with less room round it.

With `announce`, a screen reader reads the title and the line as they appear, and again when they change, without moving focus: for an empty result after a search or a filter.

## Accessibility

### What it does for you

- It takes no focus. Its actions are ordinary buttons and links, after its words in the Tab order.
- The title is a heading of the level the page needs: 2 by default, 1 for a whole page.
- The icon is decorative, and the tone colors only the icon. The words and the icon's shape carry the meaning, never the color alone.
- With `announce`, a screen reader reads the title and the line as they appear, and again when they change, without moving focus.

### What you must do

- Show it in place of what it stands for, so a screen reader meets the words, not an empty table with its column headings.
- Set the heading level with `headingLevel` when the page needs another.
- Put any meaning the icon carries in the words.
- Name the cause in words. A reference or an error code alone is never enough, and never show the error's details.
- Set `announce` for an empty result after a search or a filter.

### How to test

- With a screen reader, the title is found as a heading of the right level, followed by its line.
- Tab through it: it takes no focus itself, and the actions come after its words.
- Trigger an empty result with `announce` set: the words are read as they appear, and focus does not move.
- With text at 200% and the page 320 pixels wide, the words wrap and stay whole.
- In a forced-color mode, the icon and the actions are still visible.

## Usage rules

### Do

- Name the cause: "No notes match these filters", not "Nothing here".
- Offer the step that fixes it: "Clear filters" for no results, "Ask for access" for no permission.
- Offer "Try again" and a reference only when the cause is an error on the server.
- Use the small size inside a panel or a card among others, with small buttons and links, and set the heading level under the panel's own title.

### Don't

- Show it before the content has had its chance to load.
- Use "Something went wrong" when the cause is known.
- Put a long explanation or the error's details in it.
- Use the medium size in one panel of a dashboard: its heading outweighs the panel's own title and the page's.
