Avatar
Identifies a person, falling back to their initials when there's no photo.
When to Use
<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.
<?= 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.
<?= 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.
<?= 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.
<?= 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.
| State | Behavior |
|---|---|
| No status | Default. No dot is rendered at all. |
online | Green dot (--color-success), bottom-right, ringed in --bg-surface so it reads against the avatar. |
offline | Grey dot (--gray-400). |
busy | Red dot (--color-error). |
| Image fails to load | No 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.
<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
| Option | Type | Default | Purpose |
|---|---|---|---|
$name | string | '' | Positional and effectively required. Drives the initials, the title, and the image alt text. |
image | string | null | Photo URL. When set, the initials branch isn't rendered. |
size | string | 'md' | xs | sm | md | lg | xl |
color | string | null | Hex background for the initials variant. Emitted as an inline style. |
ring | bool | false | Adds the surface-coloured separating ring |
status | string | null | online | offline | busy. Omit for no dot. |
id | string | null | Element ID |
class | string | '' | Additional CSS classes |
attrs | array | [] | Extra HTML attributes as key/value pairs |
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
| Concern | Behavior |
|---|---|
| Image variant | The <img> carries alt="{name}", so screen readers announce the person's name. It also loads lazily. |
| Initials variant | The 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. |
| Keyboard | Not 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 dot | Purely 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. |
| Contrast | The default initials pairing is token-controlled. A custom color bypasses that check — verify it against the white initials before shipping. |
Tokens
| Token | Used for |
|---|---|
--avatar-bg | Default initials background |
--mainsite-primary / --mainsite-dark | Brand fill and ring accent |
--text-inverse | Initials text on the coloured fill |
--bg-surface | The ring, and the outline around the status dot |
--color-success | Online status dot |
--gray-400 | Offline status dot |
--color-error | Busy status dot |
--text-nano / --text-caption / --text-small / --text-h4 / --text-h2 | Initials size across xs–xl |
--font-body / --font-semibold | Initials face and weight |
CSS Classes
| Class | Purpose |
|---|---|
.ws-avatar | Base container |
.ws-avatar--xs / --sm / --md / --lg / --xl | Size modifiers |
.ws-avatar--ring | Separating ring |
.ws-avatar--initials | Emitted 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__image | The <img> |
.ws-avatar__initials | Initials <span> |
.ws-avatar__status | Status dot |
.ws-avatar__status--online / --offline / --busy | Status dot colours |
.ws-avatar-group | Overlapping stack wrapper. Applied by you to a container of avatars — the helper never emits it. |
Files
| File | Purpose |
|---|---|
includes/components/helpers.php | ws_avatar() helper function |
includes/components/avatar.php | Template — initials derivation and markup |
includes/components/components.css | Styles (.ws-avatar rules) |