Avatar

Round (or rounded / square) user picture with five sizes, an automatic initials fallback when the image fails or is still loading, and a paired AvatarGroup for stacked rows with a built-in +N overflow pill.

A workerJSAB🎡
ARMSILCT+3

Usage

import { Avatar, AvatarGroup } from '@bwo-ui/react';

<Avatar src="/me.jpg" alt="Cristian Cuna" fallback="CC" />
<Avatar fallback="JS" size="lg" />

<AvatarGroup max={3}>
  <Avatar fallback="AR" />
  <Avatar fallback="MS" />
  <Avatar fallback="IL" />
  <Avatar fallback="CT" />
  <Avatar fallback="DV" />
</AvatarGroup>

Sizes

Five sizes β€” xs (24 px) β†’ xl (80 px). xs is built for inline-with-text density (mentions, comment rails); sm and md are general purpose; lg reads as a profile callout; xl is the hero size for profile pages and onboarding.

ARxs
ARsm
ARmd
ARlg
ARxl
<Avatar size="xs" fallback="AR" />
<Avatar size="sm" fallback="AR" />
<Avatar             fallback="AR" />  {/* md, default */}
<Avatar size="lg" fallback="AR" />
<Avatar size="xl" fallback="AR" />

Shapes

The default circle works for human avatars. rounded (8 px radius) reads better for brand / company / product avatars; square is the editorial choice β€” handy for project-thumbnail rows that want a hard-edged look.

Workercircle
Workerrounded
Workersquare
<Avatar shape="circle"  src="/u.jpg" fallback="WK" />
<Avatar shape="rounded" src="/u.jpg" fallback="WK" />
<Avatar shape="square"  src="/u.jpg" fallback="WK" />

Image + fallback

Pass an src and Avatar will render the image once it loads. While loading, the fallback is shown after a brief delay (fallbackDelay, default 300 ms) to avoid a flicker on fast networks. If the image errors, the fallback persists.

The fallback prop accepts anything β€” initials (the convention), an emoji, an inline SVG icon. If you don't pass it, children are used instead.

A workerimage
ARinitials
broken β†’ fallback
🎡emoji
icon
{/* Initials (most common) */}
<Avatar src="/u.jpg" alt="Ana Radu" fallback="AR" />

{/* Loading delay tuned for slow networks */}
<Avatar src="/large.jpg" fallback="AR" fallbackDelay={600} />

{/* Decorative emoji */}
<Avatar fallback="🎡" />

{/* Custom icon */}
<Avatar
  fallback={<UserIcon />}
  style={{ background: 'var(--bwo-grey-4)' }}
/>

For coloured initial bubbles like a directory listing, set the background and color via inline style β€” Avatar forwards every native span attribute.

<Avatar
  fallback="AR"
  style={{ background: '#ff481f', color: '#fff' }}
/>

AvatarGroup

Stack avatars in an overlapping row. Use max to cap the visible count β€” any extras collapse into a +N pill. Every member gets a 2 px white ring so the overlap reads cleanly even on tinted backgrounds. The size and shape on the group cascade to children that don't set their own.

ARMSILCT
ARMSIL+4
ARMSILCTDV+2
ARMS+26
{/* No cap β€” show every avatar */}
<AvatarGroup>
  <Avatar fallback="AR" />
  <Avatar fallback="MS" />
  <Avatar fallback="IL" />
  <Avatar fallback="CT" />
</AvatarGroup>

{/* Cap at 3 β€” extras become +N */}
<AvatarGroup max={3}>
  <Avatar fallback="AR" />
  <Avatar fallback="MS" />
  <Avatar fallback="IL" />
  <Avatar fallback="CT" />
  <Avatar fallback="DV" />
  <Avatar fallback="AP" />
  <Avatar fallback="EV" />
</AvatarGroup>

{/* Large size cascades to children */}
<AvatarGroup max={5} size="lg">
  {/* … */}
</AvatarGroup>

{/* Explicit total when children are only a sample */}
<AvatarGroup max={2} size="sm" total={28}>
  <Avatar fallback="AR" />
  <Avatar fallback="MS" />
</AvatarGroup>

Hover an avatar in the stack to lift it forward β€” every member animates independently, which is useful for revealing names on hover via a tooltip layer.

Status indicator (recipe)

Avatars commonly carry a tiny status dot (online / idle / busy / offline). There's no dedicated prop β€” the avatar's overflow: hidden would clip any absolutely-positioned child anyway β€” so the canonical recipe is to wrap the avatar in a positioned span and drop a coloured dot at the bottom-right corner. The dot wears a 2 px white ring so it pops off the avatar even on busy photos.

AROnline
MSIdle
ILBusy
CTOffline
function AvatarWithStatus({ status, ...avatarProps }) {
  const color = {
    online:  '#16a34a',
    idle:    '#ffc446',
    busy:    '#ff481f',
    offline: 'var(--bwo-grey-3)',
  }[status];
  return (
    <span style={{ position: 'relative', display: 'inline-flex' }}>
      <Avatar {...avatarProps} />
      <span
        aria-hidden
        style={{
          position: 'absolute',
          bottom: 0,
          right: 0,
          width: 12,
          height: 12,
          borderRadius: '50%',
          background: color,
          boxShadow: '0 0 0 2px var(--bwo-white)',
        }}
      />
    </span>
  );
}

Comment / message recipe

The classic comment-row layout: md avatar on the left, a name + relative timestamp on the first line, body text on the second. xs works well here when the surrounding text is dense.

AR
Ana Radu2 min ago

Pushed the new IconButton parity branch β€” variants, sizes, and the toolbar pattern docs are in.

MS
Mihai Stoica8 min ago

Love the elevation treatment on solid. Going to wire it into the dashboard hero today.

<div style={{ display: 'flex', gap: 12 }}>
  <Avatar fallback="AR" style={{ background: '#ff481f', color: '#fff' }} />
  <div style={{ flex: 1 }}>
    <div style={{ display: 'flex', gap: 8, alignItems: 'baseline' }}>
      <span style={{ fontWeight: 600 }}>Ana Radu</span>
      <span style={{ fontSize: 11, color: 'var(--bwo-text-body)' }}>2 min ago</span>
    </div>
    <p>Pushed the new IconButton parity branch…</p>
  </div>
</div>

Accessibility

Avatar props

PropTypeDefaultDescription
srcstringβ€”Image source URL. Falls back to `fallback` while loading or on error.
altstringβ€”Image alt text. Pass an empty string for decorative avatars (e.g. next to a visible name).
fallbackReactNodeβ€”What to render while the image is loading or has errored β€” initials, emoji, icon. Falls back to children if omitted.
fallbackDelaynumber300Milliseconds before the fallback shows while the image is still loading β€” prevents flicker on fast networks.
size'xs' | 'sm' | 'md' | 'lg' | 'xl''md'24 / 32 / 40 / 56 / 80 px square.
shape'circle' | 'rounded' | 'square''circle'`circle` = 50 % radius, `rounded` = 8 px radius (good for brand avatars), `square` = no radius.
…restHTMLAttributes<HTMLSpanElement>β€”All native span attributes β€” `onClick`, `style`, `aria-*` β€” are forwarded. Use `style` for coloured initial bubbles.

AvatarGroup props

PropTypeDefaultDescription
maxnumberβ€”Maximum number of visible avatars. Remaining children collapse into a `+N` overflow pill. Omit to show every child.
size'xs' | 'sm' | 'md' | 'lg' | 'xl''md'Cascaded to every child avatar (and the overflow pill) that doesn’t set its own size.
shape'circle' | 'rounded' | 'square''circle'Cascaded to every child avatar that doesn’t set its own shape.
totalnumberβ€”Explicit total when the rendered children are only a sample of a larger set β€” the overflow pill will read `+(total βˆ’ visible)` instead of `+(children βˆ’ visible)`.
…restHTMLAttributes<HTMLDivElement>β€”Native div attributes are forwarded.