Canvas Design System
Main Site Tokens

Avatar

Identifies a person, falling back to their initials when there's no photo.

When to Use

Use when: You're representing a specific person — the header account control, a participant list, a comment author, a facilitator stack. The initials fallback means the layout holds even when nobody has uploaded a photo.
Don't use when: The subject isn't a person. Use an icon for a generic actor, an <img> for a logo or thumbnail, and Badge for a count that happens to sit near people.

Variants

Sizes

Five sizes. xs and sm for dense lists and stacks, md for the default inline case, lg and xl for profile headers.

AB
BS
CW
DL
EJ
<?= ws_avatar('Alice Brown', ['size' => 'xs']) ?>
<?= ws_avatar('Carol White', ['size' => 'md']) ?>
<?= ws_avatar('Eve Johnson', ['size' => 'xl']) ?>

With an image

Passing image replaces the initials. The name is still required — it becomes the image's alt text.

John Doe
Jane Smith
Alex Johnson
<?= ws_avatar('John Doe', ['image' => '/uploads/avatars/12.jpg']) ?>

Custom colour

Overrides the initials background. Use it to encode a stable attribute like team or role — not to decorate. Contrast against white text is your responsibility once you override.

PU
CU
RU
<?= ws_avatar('Plan User', ['color' => '#0284C7']) ?>

Ring

A surface-coloured ring that separates an avatar from a busy background, or marks the current user in a stack.

Ring User
RI
<?= ws_avatar('User', ['ring' => true]) ?>

States

The status dot is the only stateful part. It's presentational — the component never derives it, so the caller supplies the current value.

OU
OU
BU
StateBehavior
No statusDefault. No dot is rendered at all.
onlineGreen dot (--color-success), bottom-right, ringed in --bg-surface so it reads against the avatar.
offlineGrey dot (--gray-400).
busyRed dot (--color-error).
Image fails to loadNo fallback — the broken image stays and the initials do not reappear, because the branch is decided server-side. Pass image only when you know the file exists.
<?= ws_avatar('User', ['status' => 'online']) ?>
<?= ws_avatar('User', ['status' => 'busy']) ?>

Real-World Usage

The facilitator stack in the planner's inspector rail: small avatars, overlapped via .ws-avatar-group, with a ring separating each from the one behind it and a status dot on the person currently presenting.

BB
RC
SO
<div class="ws-avatar-group">
    <?php foreach ($facilitators as $f): ?>
        <?= ws_avatar($f['name'], [
            'size'   => 'sm',
            'ring'   => true,
            'image'  => $f['avatar_url'] ?? null,
            'status' => $f['is_presenting'] ? 'online' : null,
        ]) ?>
    <?php endforeach; ?>
</div>

Options

OptionTypeDefaultPurpose
$namestring''Positional and effectively required. Drives the initials, the title, and the image alt text.
imagestringnullPhoto URL. When set, the initials branch isn't rendered.
sizestring'md'xs | sm | md | lg | xl
colorstringnullHex background for the initials variant. Emitted as an inline style.
ringboolfalseAdds the surface-coloured separating ring
statusstringnullonline | offline | busy. Omit for no dot.
idstringnullElement ID
classstring''Additional CSS classes
attrsarray[]Extra HTML attributes as key/value pairs
info Initials are derived from the first and last whitespace-separated parts of name — "Bill Bulman" gives BB, a single-word name gives one letter. Names are escaped for the title and alt, but the derived initials are echoed raw; they come from your own data, so keep it that way.

Accessibility

ConcernBehavior
Image variantThe <img> carries alt="{name}", so screen readers announce the person's name. It also loads lazily.
Initials variantThe container's title="{name}" is the only full name available; the visible initials are read as letters. Adequate when the name also appears in adjacent text — otherwise pair the avatar with a visible label.
KeyboardNot focusable. If you wrap an avatar in a link or button, that wrapper owns the focus treatment — the ring option is decorative, not a focus indicator.
Status dotPurely visual, with no text equivalent. If presence matters to the task, state it in words nearby rather than relying on the colour of a 8px dot.
ContrastThe default initials pairing is token-controlled. A custom color bypasses that check — verify it against the white initials before shipping.

Tokens

TokenUsed for
--avatar-bgDefault initials background
--mainsite-primary / --mainsite-darkBrand fill and ring accent
--text-inverseInitials text on the coloured fill
--bg-surfaceThe ring, and the outline around the status dot
--color-successOnline status dot
--gray-400Offline status dot
--color-errorBusy status dot
--text-nano / --text-caption / --text-small / --text-h4 / --text-h2Initials size across xsxl
--font-body / --font-semiboldInitials face and weight

CSS Classes

ClassPurpose
.ws-avatarBase container
.ws-avatar--xs / --sm / --md / --lg / --xlSize modifiers
.ws-avatar--ringSeparating ring
.ws-avatar--initialsEmitted whenever no image is supplied — currently has no CSS rule. It's a styling hook you can target, not something that changes the rendering today.
.ws-avatar__imageThe <img>
.ws-avatar__initialsInitials <span>
.ws-avatar__statusStatus dot
.ws-avatar__status--online / --offline / --busyStatus dot colours
.ws-avatar-groupOverlapping stack wrapper. Applied by you to a container of avatars — the helper never emits it.

Files

FilePurpose
includes/components/helpers.phpws_avatar() helper function
includes/components/avatar.phpTemplate — initials derivation and markup
includes/components/components.cssStyles (.ws-avatar rules)