Avatar

The single identity mark: initials and colour derived from the name, so the same person is the same colour on every screen and nothing has to store it. React packages/ui/src/components/Avatar.tsx, primitive .acrm-avatar in apps/web/src/ds.css.

Shape carries the kind: a circle is a person or a user, a square is a company or a typed object (Datamart RULES §3). That is the only thing the shape means.

Since 9 September 2026 the mark has two grounds, not one: initials (the hue case) and an uploaded picture — a person's photo or a company's logo. The picture case is the same primitive with the same box; only the ring changes (see Anatomy).

When to use

  • Wherever a person or a company is named: rail rows, listings, headers, entity references, pickers, mentions.
  • As the lead of a list row, at sm.
  • For a person or a company in the app, reach for the wrapper, not the bare primitive: PersonPhoto (apps/web/src/components/person/PersonPhoto.tsx) and OrgLogo (apps/web/src/components/OrgLogo.tsx). They show the uploaded picture when there is one and degrade to this mark when there is not.

When not to use

  • For a typed object that is not a person or a company (a deal, a meeting, a task) — those take the coloured tile from entity-ref.
  • As a decorative colour swatch.
  • As the whole click target — the avatar is a mark, the name is the label.
  • Never <Avatar photo={…}> straight in a screen. The primitive has no onError, so a broken or access-denied image leaves a torn-page glyph where the identity should be. Go through PersonPhoto / OrgLogo, which fall back to the initials.

Anatomy

  • span.acrm-avatar (plus .square, plus the size class when it is not md).
  • --av-hue is set inline from hashHue(name); the ring (1.5px) and the initials are drawn from that hue, the ground stays --surface-75. Never a solid saturated fill with a white initial.
  • Sizes: inline 16 (in text) · sm 24 (default, list row) · lg 32 (card header) — three rungs, measured exactly. The former md 26 was dropped after measuring 73 call sites: it did the same job as sm, differed by 8 % — which nobody can tell apart — and in 14 of 19 cases it only happened because the caller omitted the prop. A card header now asks for lg explicitly.
  • The smallest square is rounded 6px (37.5 % of the box), not 4px. At 16px a --radius-sm corner would be a full circle, so the rung needs its own value — and 4px made the small square read boxier than its bigger sibling.
  • photo replaces the initials with an <img> that fills the box (object-fit: cover, rule .acrm-avatar img). The box, the radius and the sizes are identical in both cases, so the mark never shifts when the picture loads or fails.
  • Ring around a picture is --border, not the name hue (decision 9 September 2026, over the person-photo prototype). A hue ring fights the colours inside a photograph and reads as decoration on a white ground; the same call the company logo already made when it has no brandColor. The hue ring stays for the initials case, where it is the only colour the mark has.
  • Wrappers add nothing to the geometry: same class, same sizes. They add the picture URL (/api/contacts/:id/photo?v=<fileId> · /api/organizations/:id/logo?v=<fileId> — ?v= is the file id, so swapping the picture busts the cache), loading="lazy", decoding="async", and the fallback to initials keyed by file id.
  • Settled 14 October 2026 (acrm fix/avatar-foto-bez-kruhu, owner noticed the hue ring around contact photos in the chat list): the layer rule .acrm-avatar:not(.generated):has(> img) in apps/web/src/ds/entity-mark.css gives every picture avatar a 1px --border ring and a --bg ground (transparent PNGs), so the primitive itself now matches canon — no inline override needed, and the quick-preview drawer is fixed with it. Colleague avatars (.generated) keep no ring at all.

Best practices

Do

  • Let the hue come from the name; that is what makes the mark stable across screens without storage.
  • Feed the wrapper the bare name — a person without titles (plainPersonName), a company without its legal form (splitLegalForm). "Ing. Marek Vondra, MBA" must read MV, "ČEZ, a. s." must read ČE.
  • Use initials for names the automatic rule reads wrong.
  • Put the name next to the mark, never inside it — a long company name still shows two initials.
  • Pick the size from the ladder; there is nothing in between.
  • When the mark is also the upload trigger (person card top bar), keep the hover and focus shape round — .tbar-mark.round, PersonPhoto.css. A square hover ring around a circular face reads as a foreign element.

Don't

  • Re-implement the mark per screen. This is the one component, and it exists because that kept happening.
  • Use shape for anything but person vs company.
  • Fill the mark with a saturated colour.
  • Put a hue ring around a photograph or a logo.
  • Rely on the hue to distinguish two records — hashes collide, and colour is not announced.
  • Fetch a face from anywhere on the internet by name or e-mail. Gravatar, Clearbit, LinkedIn, image search, scraping: never (docs/ZADANI-fotografie-osob.md §4). A picture is uploaded by a person who has the right to edit the record, or it comes from the user's own Google Contacts behind a per-instance switch.

Content

  • name drives everything; give it the full display name even when the mark shows two letters.
  • A one-word name yields a single initial, never a blank circle.
  • No picture is the normal state, not a gap to fill: most records have none, and the initials mark is a finished answer. Nothing is generated, guessed or downloaded to cover it.
  • A picture is a raster only (JPEG · PNG · WebP), cropped square from the centre and capped at 512 px on upload — no SVG, no animation, no crop editor.
  • entity-mark — the same box for every other type of record; a person and a company are routed back here.
  • user-avatar — our own person, drawn as a generated face so a colleague is not mistaken for a contact.
  • entity-ref — the chip this mark sits in.
  • title-with-entity-tile, item-list, record-rail — the usual hosts.
  • App wrappers: PersonPhoto (person, circle), OrgLogo (company, square, plus brandColor ring and backdrop) — neither is in the registry yet.
  • Stories: components--avatar--default, --variants, --sizes, --states, --composition.

Source: docs/avatar/avatar.usage.md · GitLab