Skip to content

Empty state

hc-empty is a centered empty-state block: a media slot (icon or illustration), a title, a description, and optional actions. Use it for “no results”, “nothing here yet”, and first-run states. Token-driven spacing, no JavaScript — and it pairs naturally with htmx “no results” partial swaps.

No results

We couldn’t find anything matching your search. Try a different term or clear the filters.

PartRequiredPurpose
hc-empty__mediaoptionalIcon / illustration slot.
hc-empty__titleyesThe short headline.
hc-empty__descriptionoptionalOne or two sentences of guidance.
hc-empty__actionsoptionalOne or more recovery actions.

The media slot is square (--hc-empty-media-size); an inline <svg> or <img> inside it fills the box. The description is width-capped (--hc-empty-description-max-width) so long copy stays readable.

Have the list endpoint return either the rows or this empty block as its partial. Wrap the target in an aria-live region so swapping in the empty state is announced.

<div id="results" aria-live="polite"
data-hx-get="/search?q=widgets"
data-hx-trigger="load">
<!-- The server returns the results list, or, when there are none: -->
<div class="hc-empty">
<div class="hc-empty__media" aria-hidden="true">🔍</div>
<p class="hc-empty__title">No matches for "widgets"</p>
<p class="hc-empty__description">Check the spelling or try a broader term.</p>
<div class="hc-empty__actions">
<a class="hc-button" data-hx-get="/search" data-hx-target="#results">Reset search</a>
</div>
</div>
</div>

The behaviour stays with htmx — hc-empty is just the presentation of the “no results” response.

  • The role is left to the author so it fits the context. As a static region of a page, no role is needed. When it is swapped in by htmx as a result of user input, wrap the target (or the block) in aria-live="polite" (as above) so the new state is announced.
  • Set aria-hidden="true" on a decorative media glyph / illustration so it is not announced — the title carries the meaning.
  • Pick a heading level for the title that fits the page outline when the empty state owns a section: use <h2 class="hc-empty__title"> (etc.) instead of <p>. The class styles either element.
  • Keep the recovery action’s label specific (“Clear filters”, “Add the first item”) rather than a bare “OK”.
Token pathPurpose
empty.paddingBlock padding.
empty.media-sizeMedia slot size + glyph size.
empty.media-fgMedia (icon) color.
empty.media-marginGap below the media.
empty.title-font-size / -font-weight / -fgTitle type.
empty.description-font-size / -fgDescription type.
empty.description-max-widthDescription line-length cap.
empty.description-marginGap below the title.
empty.actions-marginGap above the actions.
Show the generated CSS variables
--hc-empty-padding
--hc-empty-media-size | -media-fg | -media-margin
--hc-empty-title-font-size | -title-font-weight | -title-fg
--hc-empty-description-font-size | -description-fg
--hc-empty-description-max-width | -description-margin
--hc-empty-actions-margin
  • Spinner — the loading state that precedes an empty (or populated) result.
  • Skeleton — placeholder shapes while content loads.
  • Card — a common container for an empty state inside a panel.