busy-office-ui
Menu

Avatar

Who a thing belongs to, at a glance — initials or a photo in a disc: assignee columns, approval chains, bylines. Always decoration (aria-hidden) with the name as real text nearby; it scales with the text it sits beside. Not as the only way to name someone — a disc of initials in an Assignee column, with no name beside it, asks every reader to decode "JK" and gives a screen-reader user nothing at all, since the avatar is aria-hidden by contract. Pair it with the name as real text — byline does exactly that — or, in a dense table, drop the disc and keep the name: the column is scanned and sorted on the text, never on the picture.

Initials

View code
<span class="bo-avatar" aria-hidden="true">RV</span>
<span class="bo-avatar" aria-hidden="true">JK</span>

A span with two letters. The disc is em-sized, so it follows the font size of its context — put it in a compact table and it shrinks with the rows, no modifier needed.

Photo

View code
<span class="bo-avatar" aria-hidden="true">
  <img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' fill='%230f766e'/%3E%3Ccircle cx='16' cy='12' r='6' fill='%23ccfbf1'/%3E%3Cellipse cx='16' cy='27' rx='10' ry='8' fill='%23ccfbf1'/%3E%3C/svg%3E" alt="">
</span>

An img fills the disc (object-fit: cover). Keep alt="" — the identity belongs in the adjacent text, not in the picture.

Approval-chain stack

View code
<!-- approval chain: DOM order = approval order -->
<div class="bo-avatar-stack" role="img" aria-label="Approvers: R. Vasquez, J. Kim, then M. Osei">
  <span class="bo-avatar" aria-hidden="true">RV</span>
  <span class="bo-avatar" aria-hidden="true">JK</span>
  <span class="bo-avatar" aria-hidden="true">MO</span>
</div>

The "who's next" strip: discs overlap with a surface-colored ring, DOM order is approval order. The stack itself carries the accessible name (role="img" + aria-label listing the chain) — one announcement, not three fragments.

In context — assignee column

InvoiceAssignee
INV-10234
INV-10235
View code
<table class="bo-data-table" data-density="compact">
  <thead><tr><th scope="col">Invoice</th><th scope="col">Assignee</th></tr></thead>
  <tbody>
    <tr><td>INV-10234</td><td><span class="bo-byline"><span class="bo-avatar bo-byline__avatar" aria-hidden="true">RV</span>R. Vasquez</span></td></tr>
    <tr><td>INV-10235</td><td><span class="bo-byline"><span class="bo-avatar bo-byline__avatar" aria-hidden="true">JK</span>J. Kim</span></td></tr>
  </tbody>
</table>

Composed with Byline: the avatar takes both classes — .bo-avatar paints the disc, .bo-byline__avatar tells the byline to lay out as a flex row.

Markup

<!-- Decoration, not a caption: aria-hidden always, the NAME is real
     text nearby (a byline, a cell, a stack's aria-label). em-sized —
     it scales with the surrounding text, no size modifiers. -->
<span class="bo-avatar" aria-hidden="true">RV</span>

<!-- photo variant: img fills the disc; alt stays empty -->
<span class="bo-avatar" aria-hidden="true"><img src="…" alt=""></span>

<!-- inside a byline, compose both classes (byline's :has() keys on the
     second one to switch to a flex row) -->
<span class="bo-byline">
  <span class="bo-avatar bo-byline__avatar" aria-hidden="true">RV</span>
  R. Vasquez · <time datetime="2026-08-16">today</time>
</span>

Quick reference generated

ClassRole
.bo-avatar block (root)
.bo-avatar-stack block (root)

API generated from CSS

Blocks bo-avatarbo-avatar-stack
JS required None — CSS-only.
  • Always aria-hidden="true" — the avatar is decoration; the name is real text beside it (byline, cell) or on the stack (role="img" + aria-label).
  • No size modifiers by design: the disc is em-sized and inherits scale from its context (density, byline --compact, headings).
  • Stack order is DOM order — put the current/next approver first and say so in the aria-label.
  • Forced colors: the disc keeps a real border, so stacked avatars stay separable when backgrounds flatten.
  • Promoted from Byline (owner call 2026-08-16): markup that carried only .bo-byline__avatar must add .bo-avatar — listed as Breaking in the CHANGELOG.
Contrast — 21 AA pairs this component uses (generated, CI-gated)
TextonLightDark
text-primary bg-surface 17.74 16.15
text-secondary bg-surface 10.31 11.46
text-secondary bg-muted 9.37 9.75
text-muted bg-surface 7.56 6.65
text-muted bg-muted 6.87 5.66
bg-surface text-primary 17.74 16.15
danger-text bg-surface 6.47 8.89
success-text bg-surface 5.02 12.02
accent bg-surface 5.47 9.07
text-secondary bg-selected 9.88 8.41
text-secondary bg-canvas 9.86 12.82
border-control bg-surface 4.83 6.65
--bo-state-error-color bg-surface 4.83 6.1
border-control bg-muted 4.39 5.66
accent bg-muted 4.97 7.72
warning-strong bg-muted 4.56 6.69
warning-strong bg-surface 5.02 7.86
danger bg-muted 4.39 5.19
text-primary bg-muted 16.12 13.75
success-text bg-muted 4.56 10.23
danger-text bg-muted 5.88 7.57

Maturity generated

Introduced
v0.3.0
CSS floor
Chrome/Edge 105 · Firefox 121 · Safari 15.4
Alignment scored
2026-08-23
Assistive-tech evidence
None recorded

Pin this component's first release with npm i @busy-office/ui@0.3.0. The floor is derived from this component's stylesheet alone — set by :has() — so it sits at or below the whole framework's Chrome/Edge 119 · Firefox 129 · Safari 17.5. No assistive-technology runtime result is recorded for any component: it needs a human listening to VoiceOver and NVDA on owner hardware; no gate in this repo can simulate announcement behaviour. What automation covers instead is test:axe across every page at 1440 and 390, and check:forced-colors — see accessibility.

Design-system alignment generated

100% (15/15) — how closely this component follows the design language. Each rule is scored 0-3 against a checkable citation; rules that don't apply leave the denominator. Scored 2026-08-23.

Rule Score Evidence
Typography 3 / 3 0.7em is geometry, not typography — two initials fitting a 1.8em disc, sized from the same host the disc is; stated in place (94.13)
Colour 3 / 3 zero raw colour; forced-colors adds the disc boundary that separates stacked avatars once backgrounds flatten
Spacing 3 / 3 the 2px stack ring states that it is a cut-out in the surface colour, not an outline; the -0.5em overlap is covered by the stack comment above it (94.6b)
Interaction n/a decoration; aria-hidden by contract
Content 3 / 3 opener carries the wrong-choice clause: "Not as the only way to name someone" — an initials disc with no adjacent name asks every reader to decode it and gives a screen-reader user nothing, since the avatar is aria-hidden by contract; points at byline, or dropping the disc in a dense table. Blind re-scored 2026-08-23 (Polish round 2), judged genuine: avatar-only assignee columns are a mainstream real pattern.
Fit 3 / 3 assignee columns, approval chains, bylines; em-sized so it inherits scale instead of shipping size modifiers
↑↓ move Enter open Esc close