Avatar
A user's image with an initials fallback.
Installation
Included in poetry-ui — available as
poetry_avatar
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 avatar
Default
Open Default standalone
<%= poetry_avatar(label: "Matt Solt") { "MS" } %>
Badge
Open Badge standalone
<%# The badge is a decorative presence dot — the status meaning lives in
label:, the avatar's accessible name. %>
<%= poetry_avatar(size: :sm, label: "Ada Lovelace (online)") do |avatar| %>
<% avatar.with_badge { "" } %>
AL
<% end %>
Badge with icon
Open Badge with icon standalone
<%# The badge slot can hold an icon, not just a bare dot — the theme sizes an
<svg> inside it. The status meaning still lives in label: (the accessible
name); the icon stays decorative. Note the badge glyph shows at default/lg,
not sm. %>
<%= poetry_avatar(size: :lg, label: "Grace Hopper (verified)") do |avatar| %>
<% avatar.with_badge { poetry_icon(name: :check) } %>
GH
<% end %>
Dropdown
Open Dropdown standalone
<%# The avatar as the user-menu button (upstream avatar#dropdown): it
composes INTO with_trigger's icon Button, which owns the menu aria
wiring no matter what renders inside; label: names the otherwise
image-only control. (Same inline SVG stand-in photo as With image.) %>
<% image = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJnIiB4MT0iMCIgeTE9IjAiIHgyPSIxIiB5Mj0iMSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjNjM2NmYxIi8+PHN0b3Agb2Zmc2V0PSIuNTUiIHN0b3AtY29sb3I9IiM4YjVjZjYiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNlYzQ4OTkiLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48cmVjdCB3aWR0aD0iNjQiIGhlaWdodD0iNjQiIGZpbGw9InVybCgjZykiLz48Y2lyY2xlIGN4PSIzMiIgY3k9IjI1IiByPSIxMCIgZmlsbD0iI2ZmZiIgb3BhY2l0eT0iLjkiLz48cGF0aCBkPSJNMTIgNjRjMi0xMyAxMC0xOSAyMC0xOXMxOCA2IDIwIDE5eiIgZmlsbD0iI2ZmZiIgb3BhY2l0eT0iLjkiLz48L3N2Zz4=" %>
<%= poetry_dropdown_menu(align: :start) do |menu| %>
<% menu.with_trigger(variant: :ghost, size: :icon, label: "Open user menu", class: "rounded-full") do %>
<%= poetry_avatar(src: image, label: "Grace Hopper") { "GH" } %>
<% end %>
<% menu.with_group do |group| %>
<% group.with_item { "Profile" } %>
<% group.with_item { "Billing" } %>
<% group.with_item { "Settings" } %>
<% end %>
<% menu.with_separator %>
<% menu.with_item(variant: :destructive) { "Log out" } %>
<% end %>
Group
Open Group standalone
+3
<div class="p-4">
<%= poetry_avatar_group do %>
<%= poetry_avatar(label: "Grace Hopper") { "GH" } %>
<%= poetry_avatar(label: "Ada Lovelace") { "AL" } %>
<%= poetry_avatar(label: "Annie Easley") { "AE" } %>
<%= poetry_avatar_group_count { "+3" } %>
<% end %>
</div>
Group with icon
Open Group with icon standalone
<%# The overflow chip's content is arbitrary — swap the "+N" count for an icon.
The theme sizes an <svg> inside the count chip automatically. The icon stands
alone here, so it carries its own label: for screen readers. %>
<div class="p-4">
<%= poetry_avatar_group do %>
<%= poetry_avatar(label: "Katherine Johnson") { "KJ" } %>
<%= poetry_avatar(label: "Dorothy Vaughan") { "DV" } %>
<%= poetry_avatar(label: "Mary Jackson") { "MJ" } %>
<%= poetry_avatar_group_count { poetry_icon(name: :ellipsis, label: "More members") } %>
<% end %>
</div>
Sizes
Open Sizes standalone
<div class="flex flex-wrap items-center gap-4">
<%= poetry_avatar(size: :sm, label: "Ada Lovelace") { "AL" } %>
<%= poetry_avatar(label: "Matt Solt") { "MS" } %>
<%= poetry_avatar(size: :lg, label: "Annie Easley") { "AE" } %>
</div>
With image
Open With image standalone
<%# The image layers over the initials fallback — if it ever fails to
load, the initials show through. (Inline SVG silhouette stands in
for a real profile photo.) %>
<% image = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJnIiB4MT0iMCIgeTE9IjAiIHgyPSIxIiB5Mj0iMSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjNjM2NmYxIi8+PHN0b3Agb2Zmc2V0PSIuNTUiIHN0b3AtY29sb3I9IiM4YjVjZjYiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNlYzQ4OTkiLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48cmVjdCB3aWR0aD0iNjQiIGhlaWdodD0iNjQiIGZpbGw9InVybCgjZykiLz48Y2lyY2xlIGN4PSIzMiIgY3k9IjI1IiByPSIxMCIgZmlsbD0iI2ZmZiIgb3BhY2l0eT0iLjkiLz48cGF0aCBkPSJNMTIgNjRjMi0xMyAxMC0xOSAyMC0xOXMxOCA2IDIwIDE5eiIgZmlsbD0iI2ZmZiIgb3BhY2l0eT0iLjkiLz48L3N2Zz4=" %>
<%= poetry_avatar(src: image, label: "Grace Hopper") { "GH" } %>
API
Poetry::Ui::Avatar::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 |
|---|---|---|---|
| label: | String | required | The person's name - the avatar's accessible name (blank raises). The required flag also carries the fact to the registry so static checks see it. |
| size: | Symbol | one of: default, sm, lg; defaults to :default |
The diameter axis. |
| src: | String | The image URL; without it only the initials fallback shows. |
Slots
| Writer | Description |
|---|---|
| with_badge | Decorative presence dot, bottom-right; keep the status meaning in label:. |
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=avatar] | Root span (role=img carrying the accessible name) - fallback, image, and badge layer inside it |
| [data-slot=avatar-fallback] | The initials layer (the content block) - always in the DOM, showing until the image covers it |
| [data-slot=avatar-image] | The <img> layered absolutely over the fallback - only when src: is given; a failed load paints nothing |
| [data-slot=avatar-badge] | The decorative presence dot (the badge slot), bottom-right |
State attributes
| Part | Attribute | Condition | Values |
|---|---|---|---|
| avatar | data-size | always - the resolved size | default · sm · lg |