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) andOrgLogo(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 noonError, so a broken or access-denied image leaves a torn-page glyph where the identity should be. Go throughPersonPhoto/OrgLogo, which fall back to the initials.
Anatomy
span.acrm-avatar(plus.square, plus the size class when it is notmd).--av-hueis set inline fromhashHue(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:
inline16 (in text) ·sm24 (default, list row) ·lg32 (card header) — three rungs, measured exactly. The formermd26 was dropped after measuring 73 call sites: it did the same job assm, 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 forlgexplicitly. - The smallest square is rounded 6px (37.5 % of the box), not 4px. At 16px a
--radius-smcorner would be a full circle, so the rung needs its own value — and 4px made the small square read boxier than its bigger sibling. photoreplaces 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 nobrandColor. 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)inapps/web/src/ds/entity-mark.cssgives every picture avatar a 1px--borderring and a--bgground (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 readMV, "ČEZ, a. s." must readČE. - Use
initialsfor 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
namedrives 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.
Related
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, plusbrandColorring and backdrop) — neither is in the registry yet. - Stories:
components--avatar--default,--variants,--sizes,--states,--composition.
Source: docs/avatar/avatar.usage.md · GitLab