Standardised "no rows" panel for list views.
Three visual variants, all with the same attr shape:
variant="compact"(default) — small centered text with optional icon and CTA. No card chrome. Fits inside an existing section. Use for "no rows match this filter" or "no items in this sub-list".variant="card"— same content ascompactbut wrapped in acard bg-base-100 shadowfor slightly more visual presence. Use when the empty-state stands alone in the page body (search-result- empty pages, etc.).variant="featured"— dashed-border card with a big icon, large heading, description, and CTA. Use for the "your list is genuinely empty, here's how to get started" first-run state.
Attributes
title— Required heading text.icon— Optional Heroicon name shown above the title.description— Optional sub-text below the title.variant—"compact"(default) or"featured".class— Extra classes on the wrapper.compactdefaults topy-16when this isnil; pass"py-10"etc. to override.
Slots
inner_block— Optional CTA content rendered below the description.:cta— Same asinner_block(compat alias used by some callers).
Examples
<%!-- Compact (default) — "no rows match this filter" --%>
<.empty_state icon="hero-clipboard-document-list" title={gettext("No projects match.")} />
<%!-- Compact with CTA --%>
<.empty_state icon="hero-rectangle-stack" title={gettext("No tasks yet.")}>
<.link navigate={Paths.new_task()} class="link link-primary text-sm">
{gettext("Create your first")}
</.link>
</.empty_state>
<%!-- Featured — first-run hero state --%>
<.empty_state
variant="featured"
icon="hero-cpu-chip"
title={gettext("No Endpoints Yet")}
description={gettext("Create your first AI endpoint to get started.")}
>
<.link navigate={...} class="btn btn-primary btn-lg">
<.icon name="hero-plus" class="w-5 h-5 mr-2" /> {gettext("Create First Endpoint")}
</.link>
</.empty_state>