Skip to content

Avatar

Terminal window
$ npx shadcn@latest add https://lacodda.github.io/dowel/r/avatar.json
See it liveFive sizes, initials on a neutral ground, the square shape for a thing that is not a person, a picture that fails to load, and a group with its count.→

Every screen that lists people needs one, and the three things that go wrong with it are always the same.

The picture fails to load, and a broken-image glyph appears where a face was. Profile links expire; the fallback is not a nicety but the state the component spends most of its life in, so it is the default rather than the exception.

The initials are cut from the wrong end. Splitting a name on spaces and taking the first letter of each part is the version everyone writes, and it turns a one-word name into one letter and a hyphenated surname into three. initialsOf takes the first and the last word, keeps punctuation inside a word, gives a single word two letters, and iterates by character rather than by code unit — so a name beginning outside the basic plane does not come out as a replacement glyph.

<Avatar name={person.fullName} src={person.avatarUrl} size="sm" />

It is announced once. The tile is the role="img" and carries the name; the <img> inside it has alt="". A picture labelled with the person’s name beside the person’s name as text reads the name twice.

A name is not a colour. Hashing a string into a hue looks charming on a design page and produces, in a real list, colours that collide with the status vocabulary — a face is not --bad — and that differ between products for the same person. The ground is the neutral --soft, always.

Square is for a thing that is not a person: a project, a repository. It is how every other tool says so, and borrowing that saves a caption.

In a group, the first face is on top. Later siblings paint over earlier ones by default, which would put the last face over the first; the eye reads left to right and expects the leftmost tile whole, so the stack is reversed explicitly. The +3 tile has nobody behind it, so it is text rather than a labelled image.

Props

Read from the types in ui/avatar.tsx, the file a product copies - so the description of each prop is also what an editor shows over it there.

Avatar

PropTypeDefault
sizexs | sm | md | lg | xl'md'
shapecircle | square'circle'
namerequiredstringWho or what this stands for. Always required: it is the name a reader who does not see the picture gets, and the source of the initials.
srcstringThe picture. Absent, or failing to load, falls back to the initials.
fallbackReactNodeAnything to draw instead of the initials when there is no picture - an icon for a thing that is not a person.
imgPropsOmit<ImgHTMLAttributes<HTMLImageElement>, 'onError' | 'src' | 'alt'>Passed to the <img>, for loading or referrerPolicy.

And every prop of React's HTMLAttributes<HTMLSpanElement> except children, passed on.

AvatarGroup

A row of faces, overlapping, with a count for the rest.

PropTypeDefault
maxnumber4How many tiles to show before the rest become a count.
peoplerequired{ name: string; src?: string | undefined; }[]The people. Given as data rather than as children, because the group has to know how many there are to say how many are hidden.
sizesm | md | xs | lg | xl | null'sm'The size of every tile in the row, the count included.
labelstringWhat the whole row is called, for a screen reader - "Assignees", in the product's word.

And every prop of React's HTMLAttributes<HTMLDivElement>, passed on.

Accessibility

The examples on the stand, through axe 4.14.0 in a browser, in both themes, against WCAG 2.2 A and AA and axe's best practices: 16 rules applied and passed, and none failed. The accessibility report has every item.

History

Each release that changed ui/avatar.tsx - the file a product copies - newest first.

  1. v0.41.0
    • docs read every props table from the component's types ee3b159
  2. v0.27.0
    • feat a person in the space of a word, with the fallback as the normal case 0582087