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.
Basic HTML
Section titled “Basic HTML”No results
We couldn’t find anything matching your search. Try a different term or clear the filters.
<div class="hc-empty"> <div class="hc-empty__media" aria-hidden="true"> <!-- an icon, emoji, or <svg> illustration --> 📭 </div> <p class="hc-empty__title">No results</p> <p class="hc-empty__description"> We couldn't find anything matching your search. </p> <div class="hc-empty__actions"> <button class="hc-button" data-variant="primary" type="button">Clear filters</button> <button class="hc-button" type="button">Browse all</button> </div></div>Anatomy
Section titled “Anatomy”| Part | Required | Purpose |
|---|---|---|
hc-empty__media | optional | Icon / illustration slot. |
hc-empty__title | yes | The short headline. |
hc-empty__description | optional | One or two sentences of guidance. |
hc-empty__actions | optional | One 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.
htmx usage
Section titled “htmx usage”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.
Accessibility
Section titled “Accessibility”- 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”.
Theming tokens
Section titled “Theming tokens”| Token path | Purpose |
|---|---|
empty.padding | Block padding. |
empty.media-size | Media slot size + glyph size. |
empty.media-fg | Media (icon) color. |
empty.media-margin | Gap below the media. |
empty.title-font-size / -font-weight / -fg | Title type. |
empty.description-font-size / -fg | Description type. |
empty.description-max-width | Description line-length cap. |
empty.description-margin | Gap below the title. |
empty.actions-margin | Gap above the actions. |
CSS variables
Section titled “CSS variables”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