GitHub

Empty State

Centered placeholder for empty lists or zero-result views: optional icon, required title, optional description, and slotted actions.

Stable .astro import EsaEmptyState from '@esa/ecology/esa-empty-state.astro';

Preview

No results

Try adjusting your filters.

<EmptyState title="No results" description="Try adjusting your filters." />

Sizes

No results

Try adjusting your filters.

No results

Try adjusting your filters.

No results

Try adjusting your filters.

No results

Try adjusting your filters.

<EmptyState size="xs" title="No results" description="Try adjusting your filters." />
<EmptyState size="sm" title="No results" description="Try adjusting your filters." />
<EmptyState size="md" title="No results" description="Try adjusting your filters." />
<EmptyState size="lg" title="No results" description="Try adjusting your filters." />

Icon & actions

Pass an icon via the icon slot and action buttons via the default slot. The title alone is enough for a minimal state.

No documents yet

Upload your first document to get started.

<EmptyState title="No documents yet" description="Upload your first document to get started.">
  <svg slot="icon">…</svg>
  <button type="button">Upload document</button>
</EmptyState>

API

PropTypeDefaultDescription
title string required Required. The primary heading of the empty state.
description string — Optional supporting line below the title.
size 'xs' | 'sm' | 'md' | 'lg' 'md' Scales icon, text, and padding.

Slots

PropTypeDefaultDescription
icon slot — Inline SVG, scaled to the size variant.
default slot — Action buttons rendered below the description.

Accessibility

  • The title renders as an <h3> — keep heading order coherent within the page.
  • The icon is decorative (aria-hidden); meaning comes from the title and description.
  • Action buttons in the default slot keep native button semantics and focus order.

Theming surface

Every public token this component reads, extracted from its source at build time, split by blast radius: the hooks that belong to this component, then the shared and system tokens it consumes alongside everything else. The Resolves to column walks each token's real lineage — component → semantic → primitive → raw value — so you can see exactly where re-pointing it lands. A spoke re-skins by overriding these in its theme-<slug>.css; component internals are never edited. No hook for what you need? File it with /request-lego.

Type roles it names

This component does not set a size, weight, face or tracking of its own — it names a composite, and the composite carries all five. A composite is a class rather than a token, so none of it appears in the tables below; re-theme text by re-pointing the token family, not by editing the component.

RoleRe-pointReach
.typography-body-md
  • --typography-body-md-font-family 'DM Sans', sans-serif via --typography-font-family-sans → --font-family-dm-sans
  • --typography-body-md-font-size clamp(0.75rem, 0.66rem + 0.44vw, 0.9375rem) via --font-size-200
  • --typography-body-md-font-weight 350 via --typography-font-weight-regular → --font-weight-350
  • --typography-body-md-letter-spacing 0.01em via --letter-spacing-default
  • --typography-body-md-line-height 1.6 via --line-height-normal
+28 more components esa-app-shell, esa-breadcrumbs, esa-card, esa-checkbox, esa-checkbox-group, esa-collapsible, esa-combobox, esa-command-palette, esa-confirm-dialog, esa-container, esa-danger-zone, esa-dialog, esa-dropdown-menu, esa-entity-search, esa-form-field, esa-header-nav, esa-input-tag, esa-nav-dropdown, esa-page-header, esa-popover, esa-progress-bar, esa-radio-group, esa-select, esa-side-dialog, esa-snackbar-item, esa-switch-toggle, esa-tab-layout, esa-textarea
.typography-body-sm
  • --typography-body-sm-font-family 'DM Sans', sans-serif via --typography-font-family-sans → --font-family-dm-sans
  • --typography-body-sm-font-size clamp(0.6875rem, 0.61rem + 0.38vw, 0.875rem) via --font-size-150
  • --typography-body-sm-font-weight 350 via --typography-font-weight-regular → --font-weight-350
  • --typography-body-sm-letter-spacing 0.01em via --letter-spacing-default
  • --typography-body-sm-line-height 1.6 via --line-height-normal
+20 more components esa-alert-box, esa-breadcrumbs, esa-button-toggle, esa-card, esa-checkbox-group, esa-combobox, esa-command-palette, esa-date-picker, esa-file-upload, esa-form-field, esa-input-tag, esa-link-column, esa-loading-overlay, esa-progress-bar, esa-radio-group, esa-search-panel, esa-select, esa-stat, esa-text-field, esa-textarea
.typography-body-xs
  • --typography-body-xs-font-family 'DM Sans', sans-serif via --typography-font-family-sans → --font-family-dm-sans
  • --typography-body-xs-font-size clamp(0.625rem, 0.56rem + 0.32vw, 0.75rem) via --font-size-100
  • --typography-body-xs-font-weight 350 via --typography-font-weight-regular → --font-weight-350
  • --typography-body-xs-letter-spacing 0.01em via --letter-spacing-default
  • --typography-body-xs-line-height 1.6 via --line-height-normal
+14 more components esa-breadcrumbs, esa-checkbox, esa-checkbox-group, esa-combobox, esa-command-palette, esa-entity-search, esa-field-error, esa-header-nav, esa-input-tag, esa-progress-bar, esa-radio-group, esa-search-panel, esa-select, esa-textarea
.typography-label-md
  • --typography-label-md-font-family 'DM Sans', sans-serif via --typography-font-family-sans → --font-family-dm-sans
  • --typography-label-md-font-size clamp(0.75rem, 0.66rem + 0.44vw, 0.9375rem) via --font-size-200
  • --typography-label-md-font-weight 500 via --typography-font-weight-medium → --font-weight-500
  • --typography-label-md-letter-spacing 0.01em via --letter-spacing-default
  • --typography-label-md-line-height 1.6 via --line-height-normal
+33 more components esa-app-bar, esa-breadcrumbs, esa-button-group, esa-button-toggle, esa-card, esa-checkbox-group, esa-color-picker, esa-combobox, esa-command-palette, esa-confirm-dialog, esa-danger-zone, esa-date-picker, esa-dialog, esa-entity-search, esa-file-upload, esa-filter-container, esa-form-field, esa-header-nav, esa-input-tag, esa-link-column, esa-page-header, esa-pillbox, esa-popover, esa-progress-bar, esa-radio-group, esa-range-slider, esa-select, esa-side-dialog, esa-sidebar-nav, esa-stat, esa-text-field, esa-textarea, esa-tooltip
.typography-label-md-strong
  • --typography-label-md-strong-font-family 'DM Sans', sans-serif via --typography-font-family-sans → --font-family-dm-sans
  • --typography-label-md-strong-font-size clamp(0.75rem, 0.66rem + 0.44vw, 0.9375rem) via --font-size-200
  • --typography-label-md-strong-font-weight 550 via --typography-font-weight-semibold → --font-weight-550
  • --typography-label-md-strong-letter-spacing 0.01em via --letter-spacing-default
  • --typography-label-md-strong-line-height 1.6 via --line-height-normal
+3 more components esa-avatar, esa-error-summary, esa-header-nav
.typography-label-sm-strong
  • --typography-label-sm-strong-font-family 'DM Sans', sans-serif via --typography-font-family-sans → --font-family-dm-sans
  • --typography-label-sm-strong-font-size clamp(0.6875rem, 0.61rem + 0.38vw, 0.875rem) via --font-size-150
  • --typography-label-sm-strong-font-weight 550 via --typography-font-weight-semibold → --font-weight-550
  • --typography-label-sm-strong-letter-spacing 0.01em via --letter-spacing-default
  • --typography-label-sm-strong-line-height 1.6 via --line-height-normal
+2 more components esa-alert-box, esa-collapsible
.typography-label-xs-strong
  • --typography-label-xs-strong-font-family 'DM Sans', sans-serif via --typography-font-family-sans → --font-family-dm-sans
  • --typography-label-xs-strong-font-size clamp(0.625rem, 0.56rem + 0.32vw, 0.75rem) via --font-size-100
  • --typography-label-xs-strong-font-weight 550 via --typography-font-weight-semibold → --font-weight-550
  • --typography-label-xs-strong-letter-spacing 0.01em via --letter-spacing-default
  • --typography-label-xs-strong-line-height 1.6 via --line-height-normal
+1 more component esa-avatar
.typography-title
  • --typography-title-font-family 'DM Sans', sans-serif via --typography-font-family-sans → --font-family-dm-sans
  • --typography-title-font-size clamp(1rem, 0.88rem + 0.6vw, 1.25rem) via --font-size-400
  • --typography-title-font-weight 500 via --typography-font-weight-medium → --font-weight-500
  • --typography-title-letter-spacing 0.01em via --letter-spacing-default
  • --typography-title-line-height 1.6 via --line-height-normal
+4 more components esa-avatar, esa-confirm-dialog, esa-dialog, esa-side-dialog

Wired to this component

No other component reads these — re-point them and esa-empty-state is the only thing that moves.

Token Tier Resolves to
--empty-state-icon-size-lg component 64px
--empty-state-icon-size-md component 48px
--empty-state-icon-size-sm component 32px
--empty-state-icon-size-xs component 24px

Shared tokens it reads

Owned by a family or by the semantic layer. Still overridable — but scope the override (esa-foo { --form-border-color: … }) unless you mean to move everything that reads it. Reach says what else that is.

Token Reach Resolves to
--color-content-default semantic every component #202020 via --color-gray-12
--color-content-default-secondary semantic every component #646464 via --color-gray-11
--spacing-100 primitive every component 0.25rem
--spacing-200 primitive every component 0.5rem
--spacing-300 primitive every component 0.75rem
--spacing-400 primitive every component 1rem
--spacing-600 primitive every component 2rem
--spacing-800 primitive every component 4rem