Item
A generic list row with media, content, and actions.
Installation
Included in poetry-ui — available as
poetry_item
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 item
Default
A simple item with title and description.
<%= poetry_item(class: "w-full max-w-md") do |item| %>
<% item.with_title { "Basic Item" } %>
<% item.with_description { "A simple item with title and description." } %>
<% end %>
Avatar
Last seen 5 months ago
Invite your team to collaborate on this project.
<%# Upstream item-avatar: a member row (image avatar + invite action)
and the team stack - overlapped avatars ride the data-slot seam
(ring + grayscale per avatar). Same inline SVG stand-in photo as
the Avatar page. %>
<% image = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJnIiB4MT0iMCIgeTE9IjAiIHgyPSIxIiB5Mj0iMSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjNjM2NmYxIi8+PHN0b3Agb2Zmc2V0PSIuNTUiIHN0b3AtY29sb3I9IiM4YjVjZjYiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNlYzQ4OTkiLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48cmVjdCB3aWR0aD0iNjQiIGhlaWdodD0iNjQiIGZpbGw9InVybCgjZykiLz48Y2lyY2xlIGN4PSIzMiIgY3k9IjI1IiByPSIxMCIgZmlsbD0iI2ZmZiIgb3BhY2l0eT0iLjkiLz48cGF0aCBkPSJNMTIgNjRjMi0xMyAxMC0xOSAyMC0xOXMxOCA2IDIwIDE5eiIgZmlsbD0iI2ZmZiIgb3BhY2l0eT0iLjkiLz48L3N2Zz4=" %>
<div class="flex w-full max-w-lg flex-col gap-6">
<%= poetry_item(variant: :outline) do |item| %>
<% item.with_media { poetry_avatar(src: image, size: :lg, label: "Evil Rabbit") { "ER" } } %>
<% item.with_title { "Evil Rabbit" } %>
<% item.with_description { "Last seen 5 months ago" } %>
<% item.with_actions do %>
<%= poetry_button(size: :"icon-sm", variant: :outline, label: "Invite", class: "rounded-full") do %>
<%= poetry_icon(name: :plus) %>
<% end %>
<% end %>
<% end %>
<%= poetry_item(variant: :outline) do |item| %>
<% item.with_media do %>
<div class="flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background *:data-[slot=avatar]:grayscale">
<%= poetry_avatar(src: image, label: "shadcn", class: "hidden sm:flex") { "CN" } %>
<%= poetry_avatar(src: image, label: "maxleiter", class: "hidden sm:flex") { "LR" } %>
<%= poetry_avatar(src: image, label: "evilrabbit") { "ER" } %>
</div>
<% end %>
<% item.with_title { "No Team Members" } %>
<% item.with_description { "Invite your team to collaborate on this project." } %>
<% item.with_actions do %>
<%= poetry_button(size: :sm, variant: :outline) { "Invite" } %>
<% end %>
<% end %>
</div>
Dropdown
<%# Upstream item-dropdown: rich menu rows - each menu item nests an
xs Item (tiny grayscale avatar + username over email). The Item is
presentational; the menu item stays the interactive element. %>
<% image = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJnIiB4MT0iMCIgeTE9IjAiIHgyPSIxIiB5Mj0iMSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjNjM2NmYxIi8+PHN0b3Agb2Zmc2V0PSIuNTUiIHN0b3AtY29sb3I9IiM4YjVjZjYiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNlYzQ4OTkiLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48cmVjdCB3aWR0aD0iNjQiIGhlaWdodD0iNjQiIGZpbGw9InVybCgjZykiLz48Y2lyY2xlIGN4PSIzMiIgY3k9IjI1IiByPSIxMCIgZmlsbD0iI2ZmZiIgb3BhY2l0eT0iLjkiLz48cGF0aCBkPSJNMTIgNjRjMi0xMyAxMC0xOSAyMC0xOXMxOCA2IDIwIDE5eiIgZmlsbD0iI2ZmZiIgb3BhY2l0eT0iLjkiLz48L3N2Zz4=" %>
<% people = [
{ username: "shadcn", email: "shadcn@vercel.com", initials: "CN" },
{ username: "maxleiter", email: "maxleiter@vercel.com", initials: "LR" },
{ username: "evilrabbit", email: "evilrabbit@vercel.com", initials: "ER" }
] %>
<%= poetry_dropdown_menu(align: :end) do |menu| %>
<% menu.with_trigger(variant: :outline) do %>
Select <%= poetry_icon(name: :"chevron-down") %>
<% end %>
<% menu.with_group do |group| %>
<% people.each do |person| %>
<% group.with_item do %>
<%= poetry_item(size: :xs, class: "w-full p-2") do |item| %>
<% item.with_media { poetry_avatar(src: image, label: person[:username], class: "size-6.5 grayscale") { person[:initials] } } %>
<% item.with_title { person[:username] } %>
<% item.with_description { person[:email] } %>
<% end %>
<% end %>
<% end %>
<% end %>
<% end %>
Group
All invoices are up to date.
One transfer awaits confirmation.
<%# Rows in poetry_item_group (role=list); each row wears role=listitem. %>
<div class="w-full max-w-md">
<%= poetry_item_group do %>
<%= poetry_item(variant: :muted, size: :sm, role: "listitem") do |item| %>
<% item.with_title { "Invoices synced" } %>
<% item.with_description { "All invoices are up to date." } %>
<% end %>
<%= poetry_item_separator %>
<%= poetry_item(size: :sm, role: "listitem") do |item| %>
<% item.with_title { "Payment pending" } %>
<% item.with_description { "One transfer awaits confirmation." } %>
<% end %>
<% end %>
</div>
Image
The Wandering Signals, 2024
<%# media_variant: :image sizes and rounds the thumbnail cell automatically. %>
<%= poetry_item(media_variant: :image, class: "w-full max-w-md") do |item| %>
<% item.with_media do %>
<%= image_tag "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEVjZvEU9kYIAAAACklEQVR4nGNiAAAABgADNjd8qAAAAABJRU5ErkJggg==",
alt: "Album artwork", class: "size-full object-cover" %>
<% end %>
<% item.with_title { "Midnight Frequencies" } %>
<% item.with_description { "The Wandering Signals, 2024" } %>
<% item.with_actions do %>
<%= poetry_button(size: :sm, variant: :outline) { "Play" } %>
<% end %>
<% end %>
Link
<%# tag: :a with href makes the whole row a link - never wrap an Item in a bare <a>. %>
<%= poetry_item(tag: :a, href: "/settings/billing", variant: :outline, class: "w-full max-w-md") do |item| %>
<% item.with_title { "Billing settings" } %>
<% item.with_description { "Update your card and review past invoices." } %>
<% item.with_actions do %>
<%= poetry_icon(name: :"chevron-right") %>
<% end %>
<% end %>
Outline with icon and actions
<div class="w-full max-w-md">
<%= poetry_item(variant: :outline, media_variant: :icon) do |item| %>
<% item.with_media { poetry_icon(name: :"badge-check") } %>
<% item.with_title { "Your profile has been verified." } %>
<% item.with_actions do %>
<%= poetry_button(size: :sm, variant: :outline) { "View" } %>
<% end %>
<% end %>
</div>
Sizes
The standard size for most use cases.
A compact size for dense layouts.
The most compact size available.
<%# The three densities side by side - default, sm, and xs (the
tightest, good for dense activity logs). %>
<div class="flex w-full max-w-md flex-col gap-6">
<%= poetry_item(variant: :outline, media_variant: :icon) do |item| %>
<% item.with_media { poetry_icon(name: :inbox) } %>
<% item.with_title { "Default Size" } %>
<% item.with_description { "The standard size for most use cases." } %>
<% end %>
<%= poetry_item(variant: :outline, size: :sm, media_variant: :icon) do |item| %>
<% item.with_media { poetry_icon(name: :inbox) } %>
<% item.with_title { "Small Size" } %>
<% item.with_description { "A compact size for dense layouts." } %>
<% end %>
<%= poetry_item(variant: :outline, size: :xs, media_variant: :icon) do |item| %>
<% item.with_media { poetry_icon(name: :inbox) } %>
<% item.with_title { "Extra Small Size" } %>
<% item.with_description { "The most compact size available." } %>
<% end %>
</div>
Variants
Transparent background with no border.
Outlined style with a visible border.
Muted background for secondary content.
<%# The three visual variants - default (transparent), outline, and
muted (secondary-content background). %>
<div class="flex w-full max-w-md flex-col gap-6">
<%= poetry_item(media_variant: :icon) do |item| %>
<% item.with_media { poetry_icon(name: :inbox) } %>
<% item.with_title { "Default Variant" } %>
<% item.with_description { "Transparent background with no border." } %>
<% end %>
<%= poetry_item(variant: :outline, media_variant: :icon) do |item| %>
<% item.with_media { poetry_icon(name: :inbox) } %>
<% item.with_title { "Outline Variant" } %>
<% item.with_description { "Outlined style with a visible border." } %>
<% end %>
<%= poetry_item(variant: :muted, media_variant: :icon) do |item| %>
<% item.with_media { poetry_icon(name: :inbox) } %>
<% item.with_title { "Muted Variant" } %>
<% item.with_description { "Muted background for secondary content." } %>
<% end %>
</div>
With header and footer
12 seats, billed annually.
<%# The header slot sits above the row content; the footer slot sits below it. %>
<%= poetry_item(variant: :outline, class: "w-full max-w-md") do |item| %>
<% item.with_header do %>
<%= poetry_badge(variant: :info) { "Renewal" } %>
<% end %>
<% item.with_title { "Team plan" } %>
<% item.with_description { "12 seats, billed annually." } %>
<% item.with_footer do %>
<span class="text-muted-foreground text-sm">Next charge on August 1.</span>
<% end %>
<% end %>
API
Poetry::Ui::Item::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 |
|---|---|---|---|
| size: | Symbol | one of: default, sm, xs; defaults to :default |
The row's density. |
| variant: | Symbol | one of: default, outline, muted; defaults to :default |
The row's visual treatment - :outline boxes it, :muted recedes. |
| media_variant: | Symbol | one of: default, icon, image; defaults to :default |
The media treatment - :icon for a glyph, :image for a thumbnail. |
| tag: | Symbol | defaults to :div |
The root element - tag: :a (href via passthrough) makes the whole row clickable. |
Slots
| Writer | Description |
|---|---|
| with_actions | The trailing actions cell - buttons, a menu, a switch. |
| with_description | The muted description line (clamps to two lines). |
| with_footer | Full-width row below the media/content columns. |
| with_header | Full-width row above the media/content columns. |
| with_media | The leading media cell - a glyph or thumbnail (see media_variant). |
| with_title | The title row. |
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=item] | The row root (a div by default; tag: :a for a clickable row) - variant and density land here as data attributes |
| [data-slot=item-media] | The leading media cell - sized and rounded by its variant |
| [data-slot=item-content] | The center column collecting title, description, and loose content |
| [data-slot=item-title] | The title row |
| [data-slot=item-description] | The muted description line |
| [data-slot=item-header] | Full-width row above the media/content columns |
| [data-slot=item-actions] | The trailing actions cell |
| [data-slot=item-footer] | Full-width row below the media/content columns |
State attributes
| Part | Attribute | Condition | Values |
|---|---|---|---|
| item | data-variant | the row's visual variant | default · outline · muted |
| item | data-size | the row's density | default · sm · xs |
| item-media | data-variant | the media treatment | default · icon · image |