Components Avatars

Petal Pro is the full SaaS app this is built for

Auth, billing, admin, and Claude Code integration included. One purchase, unlimited projects.

Avatars

Identity on screen - photos when you have them, deterministic initials and generative art when you don't, plus presence dots, six sizes from 2xs and the stacked team group.
Identity without photos

The placeholder system, from plain to generative: monogram initials, a deterministic colour or gradient hashed from the name, and art - mesh draws a soft gradient orb (initials optional), dither a two-tone pixel blend. Same name, same result, every render. No JavaScript, no dependencies.

heex
<div class="flex flex-wrap items-center justify-center gap-4">
<.avatar name="Sarah Chen" size="lg" />
<.avatar name="Sarah Chen" size="lg" random_color />
<.avatar name="Sarah Chen" size="lg" random_gradient />
<.avatar name="Sarah Chen" size="lg" art="mesh" />
<.avatar name="Sarah Chen" size="lg" art="mesh" initials />
<.avatar name="Sarah Chen" size="lg" art="dither" />
</div>
Rounded for orgs

Circles are the convention for people; shape="rounded" gives orgs, teams and workspaces proportional soft corners instead. Deliberately independent of the radius dial - avatars are identity, not surface.

heex
<div class="flex flex-wrap items-center justify-center gap-4">
<.avatar name="Petal Framework" size="lg" shape="rounded" random_gradient />
<.avatar name="Acme Corp" size="lg" shape="rounded" art="mesh" />
<.avatar name="Initech" size="lg" shape="rounded" art="dither" />
<.avatar name="Globex" size="lg" shape="rounded" random_color />
</div>
Six sizes, four presence states

The size ladder from 2xs to xl - 2xs is for dense rows: a table cell, a compact activity feed - and the status dot: online, busy, away, offline, ringed so it reads against any image or placeholder. Presence works on every avatar form.

heex
<div class="flex flex-col items-center gap-6">
<div class="flex flex-wrap items-end justify-center gap-3">
<.avatar name="Sarah Chen" size="2xs" random_color />
<.avatar name="Sarah Chen" size="xs" random_color />
<.avatar name="Sarah Chen" size="sm" random_color />
<.avatar name="Sarah Chen" size="md" random_color />
<.avatar name="Sarah Chen" size="lg" random_color />
<.avatar name="Sarah Chen" size="xl" random_color />
</div>
<div class="flex flex-wrap items-center justify-center gap-4">
<.avatar name="Amelia Ward" size="md" art="mesh" status="online" />
<.avatar name="Jonah Reyes" size="md" art="mesh" status="busy" />
<.avatar name="Priya Anand" size="md" art="mesh" status="away" />
<.avatar name="Tom Hale" size="md" art="mesh" status="offline" />
</div>
</div>
The team stack

avatar_group overlaps your hosted image URLs into the classic team pile; max caps the row and folds the rest into a +N bubble. shape passes through to every avatar and the bubble. The URLs here are inline SVG stand-ins - swap in real photos and nothing else changes.

data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'><rect width='8' height='8' fill='%23f59e0b'/></svg> data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'><rect width='8' height='8' fill='%2310b981'/></svg> data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'><rect width='8' height='8' fill='%236366f1'/></svg>
+2
heex
<div class="flex flex-col items-center gap-4">
<.avatar_group
size="md"
max={3}
avatars={[
"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'><rect width='8' height='8' fill='%23f59e0b'/></svg>",
"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'><rect width='8' height='8' fill='%2310b981'/></svg>",
"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'><rect width='8' height='8' fill='%236366f1'/></svg>",
"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'><rect width='8' height='8' fill='%23ec4899'/></svg>",
"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'><rect width='8' height='8' fill='%2306b6d4'/></svg>"
]}
/>
</div>
Properties
Attribute Type Default Description
alt string nil alt text for avatar image
art string nil generative art instead of initials: mesh is a soft multi-hue gradient, dither an ordered-dither blend - both hashed from the name, so the same name always draws the same avatar. Pure art by default; the name still labels the avatar for screen readers, and a photo src wins when present
one of: nil, "mesh", "dither"
class any nil CSS class
initials boolean false mesh only: overlay the monogram on the art as a dark hue-tinted monogram (the mesh reads light, so it always contrasts). Ignored for dither - a discrete pixel pattern never lets text sit cleanly, so dither stays pure art
name string nil name for placeholder initials
random_color boolean false generates a random color for placeholder initials avatar
random_gradient boolean false generates a gradient background for placeholder initials, hashed from the name like random_color - same name, same gradient. Wins over random_color when both are set
rest global
shape string "circle" circle is the convention for people; rounded (proportional soft corners) suits orgs, teams and workspaces. Deliberately independent of the --pc-radius dial - avatars are identity, not surface, and a sharp-cornered theme still wants soft avatars
one of: "circle", "rounded"
size string "md"
one of: "2xs", "xs", "sm", "md", "lg", "xl"
src string nil hosted avatar URL
status string nil presence dot on the bottom-right corner, ringed to separate from the image
one of: nil, "online", "offline", "busy", "away"