Empty
An empty-state placeholder with an icon, message, and actions.
Installation
Included in poetry-ui — available as
poetry_empty
the moment you've installed Poetry,
with no per-component step. To own the source and edit it, copy it into your app:
bin/rails g poetry:add empty
Default
Open Default standalone
No projects yet
Get started by creating your first project.
<%= poetry_empty do |empty| %>
<% empty.with_title { "No projects yet" } %>
<% empty.with_description { "Get started by creating your first project." } %>
<% end %>
Background
Open Background standalone
You're all caught up
New messages will land here as your team replies.
<%# A tinted surface behind the empty state — a soft muted panel that lifts
it off the page. The background lives on the root class:; the header
slots and action stay the same. %>
<%= poetry_empty(media_variant: :icon, class: "rounded-lg bg-muted/50 p-8") do |empty| %>
<% empty.with_media { poetry_icon(name: :inbox) } %>
<% empty.with_title { "You're all caught up" } %>
<% empty.with_description { "New messages will land here as your team replies." } %>
<%= poetry_button(size: :sm) { "Compose message" } %>
<% end %>
Outline
Open Outline standalone
No saved views
Save a filter here to jump back to it in one click.
<%# The empty state framed in its own dashed outline — a self-contained
card the container can drop straight into a panel. The border rides on
the root via class:; the slots and content block are unchanged. %>
<%= poetry_empty(media_variant: :icon, class: "rounded-lg border border-dashed p-8") do |empty| %>
<% empty.with_media { poetry_icon(name: :bookmark) } %>
<% empty.with_title { "No saved views" } %>
<% empty.with_description { "Save a filter here to jump back to it in one click." } %>
<%= poetry_button(size: :sm, variant: :outline) { "Save this view" } %>
<% end %>
With avatar
Open With avatar standalone
No messages with Grace yet
Say hello to start the conversation.
<%# A single avatar as the media — the default (transparent) media variant
lets the avatar stand on its own instead of the rounded icon tile. Use
it to put a person at the center of the empty state. %>
<%= poetry_empty do |empty| %>
<% empty.with_media { poetry_avatar(label: "Grace Hopper", size: :lg) { "GH" } } %>
<% empty.with_title { "No messages with Grace yet" } %>
<% empty.with_description { "Say hello to start the conversation." } %>
<%= poetry_button(size: :sm) { "Send a message" } %>
<% end %>
With avatar group
Open With avatar group standalone
+4
This workspace is quiet
Invite a few teammates to start collaborating here.
<%# A stack of avatars as the media — poetry_avatar_group in the media slot
signals a team or shared space. The default media variant keeps the
group visible without the rounded tile. %>
<%= poetry_empty do |empty| %>
<% empty.with_media do %>
<%= poetry_avatar_group do %>
<%= poetry_avatar(label: "Ada Lovelace") { "AL" } %>
<%= poetry_avatar(label: "Annie Easley") { "AE" } %>
<%= poetry_avatar(label: "Katherine Johnson") { "KJ" } %>
<%= poetry_avatar_group_count { "+4" } %>
<% end %>
<% end %>
<% empty.with_title { "This workspace is quiet" } %>
<% empty.with_description { "Invite a few teammates to start collaborating here." } %>
<%= poetry_button(size: :sm) { "Invite teammates" } %>
<% end %>
With icon and actions
Open With icon and actions standalone
No projects yet
Get started by creating your first project.
<%= poetry_empty(media_variant: :icon) do |empty| %>
<% empty.with_media { poetry_icon(name: :folder) } %>
<% empty.with_title { "No projects yet" } %>
<% empty.with_description { "Get started by creating your first project." } %>
<%= poetry_button(size: :sm) { "Create project" } %>
<% end %>
With input
Open With input standalone
No results found
Try another term, or search the full catalog.
<%# The action that fixes the emptiness is a search — an input group drops
into the content block so the reader can look for what isn't listed yet.
The label stays present (sr-only) for the borderless group control. %>
<%= poetry_empty(media_variant: :icon) do |empty| %>
<% empty.with_media { poetry_icon(name: :search) } %>
<% empty.with_title { "No results found" } %>
<% empty.with_description { "Try another term, or search the full catalog." } %>
<div class="w-full max-w-xs">
<%= poetry_label(for_id: "empty-search", class: "sr-only") { "Search catalog" } %>
<%= poetry_input_group do %>
<%= poetry_input_group_addon do %>
<%= poetry_icon(name: :search) %>
<% end %>
<%= poetry_input_group_input(id: "empty-search", name: "q", placeholder: "Search the catalog…") %>
<% end %>
</div>
<% end %>
API
Poetry::Ui::Empty::Component — options are
constructor keywords (the poetry_* helper forwards them);
slots are composed inside the block. Generated from the gem's source documentation.
| Option | Type | Details | Description |
|---|---|---|---|
| media_variant: | Symbol | one of: default, icon; defaults to :default |
The media slot's treatment; :icon gives the rounded muted icon tile. |
| title_tag: | Symbol | one of: h1, h2, h3, h4, h5, h6; defaults to :h3 |
The title's heading level - a real heading element, so set it to fit the page outline. |
Slots
| Writer | Description |
|---|---|
| with_description | Muted copy under the title. |
| with_media | Optional leading visual above the title - an icon or illustration. |
| with_title | The headline, rendered as a real heading (see title_tag). |
Styling
Every part carries a stable data-slot attribute — target
[data-slot=…] from your own CSS to restyle it. State rides
data attributes on the parts below. This contract is verified against rendered DOM in CI.
| Part | Description |
|---|---|
| [data-slot=empty] | The empty-state root - the centered header and content stack |
| [data-slot=empty-header] | Wrapper around media/title/description - renders when at least one of those slots is set |
| [data-slot=empty-icon] | The media slot's box - media_variant: :icon gives the rounded muted icon tile |
| [data-slot=empty-title] | The title as a real heading (title_tag, h3 by default) |
| [data-slot=empty-description] | Muted copy under the title |
| [data-slot=empty-content] | The actions that fix the emptiness - the content block renders here |
State attributes
| Part | Attribute | Condition | Values |
|---|---|---|---|
| empty-icon | data-variant | always - the resolved media_variant | default · icon |