Avatar

The picture of a person or an organization, alone or in a group.

StableSince 1.0.0
On this page

Import

import {
  Avatar,
  AvatarButton,
  AvatarGroup,
  avatarHue,
} from '@envsemble/envui';

Usage

Avatar takes the person's or organization's name, a picture in src, and fallback="person" for the default person picture instead of the initials, which an empty name shows too. Beside a visible name it is decorative; named makes it an image named by name. size is sm, md, the default, lg or xl: 1.5, 2, 3 or 5.5 rem.

<Avatar name="Alex Morgan" src={alex.picture} />
<Avatar name="Northwind Labs" size="lg" named />

Chosen

selected draws the ring and the check at its bottom end, env-avatar-check. It says nothing to assistive technology: the checkbox or option around it does, as in the ChooseReviewers example. An AvatarGroup person takes selected too, and comes in front of its neighbours so its check shows.

<Checkbox
  checked={chosen}
  onCheckedChange={setChosen}
  label={
    <Row align="center" gap={3}>
      <Avatar name="Sam Lee" selected={chosen} />
      <Text render={<span />}>Sam Lee</Text>
    </Row>
  }
/>

Colors

hue sets one of the eight colors by name, in place of the one the name chooses: powder (powder blue), seafoam, sage, butter, blush, periwinkle, orchid or oat (a warm oat). avatarHue(name) returns the one a name chooses, and avatarHues lists the eight in order. Each is a tonal pair, a soft fill with the initials or the figure in a deep shade of the same hue, and in the dark theme a deep fill with the initials and the figure in a light shade. In a table's cell and in a small AvatarGroup the fill takes half its chroma at the same lightness, so the initials keep their contrast; --env-avatar-dense: 1 on a container of your own, such as a dense list, does the same for the avatars and groups in it. In an AvatarGroup, an avatar whose color looks like its neighbour's before it, the two blues, greens, pinks or warm hues, takes the next of the eight that doesn't.

Groups

AvatarGroup takes people, each with a name and an optional src, fallback, id and selected. max sets how many show, and the count chip counts the rest; total counts people beyond a preview list.

  • At rest the avatars pile up, each a little off the line and turned, with the count chip at the bottom of the pile. A group of initials piles looser, so two letters stay whole under the next avatar, and rests upright and in line, as does a small group.
  • A small group, or any group in a table's cell, is dense: pictures overlap by 0.3 of the avatar, and initials only by the ring that parts them, so the letters stay clear.
  • The group always keeps the room its spread takes, so spreading never moves what is around it; align="end" piles a group at the end of that room, for a group that ends a row, such as a list row's action.
  • With reduced motion it spreads at once.
  • formatMore and formatLabel set its words.
<AvatarGroup people={members} max={3} total={12} align="end" />

Pressable groups

onPersonClick makes every avatar a button, which receives the person pressed; a person with href is a link, through the application's router link from LinkProvider.

  • While the group is piled its avatars and count take no pointer events, and a cover over the group, its ::before, takes the pointer, so no avatar can be pressed or hovered then.
  • The cover stays for the medium motion duration after the group starts to spread, until the avatars are in place.
  • A hovering pointer spreads the group with :hover, and keyboard focus inside with :focus-visible; a click's focus, without a focus ring, doesn't hold it spread.
  • Where no pointer hovers, a tap on the pile sets data-spread on the group, until a tap outside it or five seconds without a tap in it.
  • Spread, each pressable avatar is a whole target, 2.75 rem on touch screens.
PressableAvatars.tsx
import { useState } from 'react';
import { AvatarGroup, Column, Text } from '@envsemble/envui';

const reviewers = [
  { id: 'alex', name: 'Alex Morgan' },
  { id: 'sam', name: 'Sam Lee' },
  { id: 'priya', name: 'Priya Shah', href: '/people/priya' },
  { id: 'jordan', name: 'Jordan Ellis' },
  { id: 'taylor', name: 'Taylor Kim' },
];

export default function PressableAvatars() {
  const [opened, setOpened] = useState('');
  return (
    <Column gap={3} align="start">
      <AvatarGroup
        aria-label="Reviewers"
        people={reviewers}
        max={4}
        onPersonClick={(person) => setOpened(`Opened the profile of ${person.name}`)}
      />
      <Text variant="caption" emphasis="muted" role="status">
        {opened}
      </Text>
    </Column>
  );
}

Composition

The group is env-avatar-group, with an env-avatar for each person, an env-avatar-group-item button or link around each pressable one, and the count chip, env-avatar-group-overflow, whose tooltip lists the people it hides. A picture is env-avatar-image, the default person picture env-avatar-person, and AvatarButton env-avatar-button.

Customize

Its color properties take the place of its hue. The hues are tokens, --env-color-bg-avatar-{hue}, --env-color-fg-avatar-{hue} and --env-color-fg-avatar-figure-{hue}, and the powder hue is also the unsuffixed --env-color-bg-avatar, --env-color-fg-avatar and --env-color-fg-avatar-figure. A lone avatar casts a faint shadow, 1 px down and 2 px soft, which the ring replaces in a group; a small avatar and one in a table cast none; under forced colors there is none, and box-shadow: none on .env-avatar removes it.

Accessibility

You provide

  • The name beside a decorative avatar, or named.
  • An aria-label on an AvatarButton that says what it opens.

envUI provides

  • A decorative avatar hidden, or with named an image named by name.
  • A group named with its count and every name, such as 4 people: Alex Morgan, Sam Lee…
  • Pressable avatars as native buttons and links, named after each person.
  • A piled group as one pointer target, which spreads; its avatars take presses only once spread, apart and whole, so no target lies under another (WCAG 2.5.8).
  • The count as a tab stop, so the hidden names can be reached.

Props

Avatar

A round picture of a person or organization, with their initials or the default person picture as fallback.

PropTypeDescription
namerequired
string
Person or organization name: the accessible name of a named avatar, and the initials fallback.
src
stringnull
Picture URL. The fallback shows while it loads and when it is missing or fails.
fallbackDefault 'initials'
'person''initials'
Shown without a picture: the initials of `name`, so people tell avatars apart, or the default person picture, which an empty `name` shows too.
hue
AvatarHue
The fill behind the initials or the person picture. Without it, the name chooses one, the same for the same name everywhere; set it for an organization with a color of its own.
sizeDefault 'md'
'sm''md''lg''xl'
sm 1.5 rem, md 2 rem, lg 3 rem or xl 5.5 rem, such as for a profile page.
namedDefault false
boolean
Names the avatar as an image by `name`. Without it, the avatar is hidden from assistive technology, for when text beside it already names the person.
selectedDefault false
boolean
Shows the avatar as chosen, such as in a list of people to pick from: a ring around it and a small check at its bottom end, so the choice never rests on color alone. It is a look only: the control around the avatar, such as a checkbox or an option, says it is chosen.

Also accepts the native props of <span>, such as ref, className and aria-* attributes.

AvatarButton

A person's avatar as a button, typically the account menu trigger in a topbar. A halo shows hover, press and an open menu, and the tooltip names the account.

PropTypeDescription
namerequired
string
The person's name: shown in the tooltip, and the accessible name unless aria-label is set.
description
string
A second tooltip line, such as the e-mail address of the signed-in account.
src
stringnull
Picture URL; the fallback shows without one.
fallback
AvatarProps['fallback']
Shown without a picture: the initials of `name`, by default, or the default person picture.
sizeDefault 'md'
'sm''md''lg'
The picture: sm 1.5 rem, md 2 rem or lg 3 rem, inside a slightly larger hit area, which grows to the touch height under a coarse pointer.

Also accepts the native props of <button>, such as ref, className and aria-* attributes.

AvatarGroup

A pile of avatars for a set of people, such as the members of a project, with a chip that counts the rest and lists them in its tooltip. Hover, a tap or keyboard focus spreads it.

PropTypeDescription
peoplerequired
readonly AvatarGroupPerson[]
The people, in order. Pass a preview with total when there are many more.
max
number
The most avatars to show; the overflow chip after them counts the rest. All by default.
sizeDefault 'md'
'sm''md''lg''xl'
As Avatar: sm 1.5 rem, md 2 rem, lg 3 rem or xl 5.5 rem.
alignDefault 'start'
'start''end'
The group keeps the room its spread takes, so spreading never moves what is around it, and the pile rests at one end of that room, along the inline axis: start, or end for a group at the end of a row, such as at a list row's end.
total
number
How many people there are in all, when people is only a preview of them.
onPersonClick
(person: AvatarGroupPerson, event: MouseEvent<HTMLElement>) => void
Makes every avatar a button with a name tooltip; receives the person pressed. The avatars are targets only while the group is spread: at rest the pile is one target, which hover, a tap or keyboard focus spreads.
formatMoreDefault '{count} more'
(count: number) => string
Text for people beyond the list, in the overflow tooltip and the group's name. It overrides EnvUIProvider's `messages.avatarGroup.more` for this group.
formatLabelDefault '{count} people: {names}'
(count: number, names: readonly string[]) => string
The group's name from the number of people and their names, which end with formatMore's text for people counted in total but not listed, such as "3 people: Alex Morgan, Sam Lee, 1 more". It overrides EnvUIProvider's `messages.avatarGroup.label` for this group; aria-label replaces it.

Also accepts the native props of <div>, such as ref, className and aria-* attributes.

avatarHue

The hue a name gives an avatar: the same name always gives the same hue, on every page and in every session, and different names spread across the hues. Letter case and surrounding spaces do not count.

avatarHue(name: string): AvatarHue

Part names

Each part's class and state attributes are public and versioned, for an application's CSS to style.

Avatar

env-avatar
The avatar: an image, initials or a figure in a circle.
env-avatar-check
The check at the bottom end of a chosen avatar.
env-avatar-fallback
What shows without an image: initials or a figure.
env-avatar-fallback-initials
The fallback that shows initials.
env-avatar-fallback-person
The fallback that shows a person's figure.
env-avatar-image
The image.
env-avatar-person
The figure of a person, drawn when there are no initials.
env-avatar-person-background
The figure's background.
env-avatar-person-figure
The figure itself.
data-hue
blush · butter · oat · orchid · periwinkle · powder · sage · seafoam
data-selected
Present
data-size
lg · md · sm · xl

AvatarButton

env-avatar-button
The button that shows an avatar.
env-avatar-button-tooltip
The tooltip that names the account.

AvatarGroup

env-avatar-group
The overlapping row of avatars.
env-avatar-group-item
One avatar in the group.
env-avatar-group-overflow
The count of avatars that don't fit.
env-avatar-group-tooltip
The tooltip that names an avatar or the hidden ones.
data-align
end · start
data-interactive
Present
data-size
lg · md · sm · xl
data-spread
Present

Color properties

Each sets a color of one instance, through style or CSS; its hover, focus and press follow it. They are public and versioned.

--env-avatar-container
The fill behind the initials or figure, in place of the hue.
--env-avatar-content
The initials and the figure.

Strings

EnvUIProvider's messages overrides each, and a component's own label prop wins over it.

messages.avatarGroup.more
({ countText }) => string People beyond the list, such as 3 more; `countText` is the count in the locale's digits.
messages.avatarGroup.label
({ count, countText, names }) => string Names the group, such as 4 people: Alex Morgan, Sam Lee; `countText` is the count in the locale's digits, and `names` the names joined by commas.

Changelog

1.0.0

An avatar without a picture takes a solid color that its name chooses, one of eight, with white initials or a white figure; hue sets the color, and avatarHue returns the one a name chooses. Initials are semibold. Sizes are sm, md, lg and xl (were small, standard, large and display), set as data-size rather than the env-avatar-{size} classes. AvatarButton's detail is description, and it takes the touch height on touch screens. AvatarGroup's strings come from messages.avatarGroup, with the counts in the locale's digits. getInitials is internal; Avatar shows initials with fallback="initials". Avatar's label, which named it as an image, is named.

1.0.0-prerelease.6

Colors under the pointer and while pressed change with --env-motion-ease-enter, rather than easing in slowly, so hover and press show from the first frame.

In the dark theme an avatar takes a deep fill of its hue with light initials, rather than the light theme's pastel, so avatars are no longer the brightest thing on a dark screen. The butter, sage and oat fills sit a little lighter, with paler initials, so they read as gold, green and oat rather than muddy brown and olive.

Avatars in a table's cell and in a small group take quieter fills, and a small group or one in a table overlaps less, initials only by the ring. In a group, an avatar whose color looks like the one before it takes another.

An avatar without a picture shows its name's initials by default, in place of the default person picture, which fallback="person" keeps. Avatars in a group take a ring in the color of the surface below, so their initials never run together.

A small avatar and one in a table cast no shadow, so a column of them stays quiet.

Initials stay legible at small sizes, at least the caption size; below sm, an avatar with initials is a disc of its color alone.

1.0.0-prerelease.5

AvatarGroup's gather is align, as Row and Popover name the inline axis, and its data-gather attribute is data-align.

An avatar and AvatarGroup's count cast a faint, small shadow in a deeper shade of the surface below, so the avatars of a group part from one another.

1.0.0-prerelease.3

AvatarGroup rests as a compact pile, like stacked cards, which is one pointer target: hover, a tap or keyboard focus spreads it, and its avatars take presses only once spread, in place of a looser rest that kept 1.5 rem of each pressable avatar uncovered. A tap spreads a group on touch screens too, marked with data-spread. The count is a rounded square the size of the avatars, at the bottom of the pile.

1.0.0-prerelease.2

The eight colors are a new pastel palette of tonal pairs: a soft fill with initials and a figure in a deep shade of the same hue, rather than white. The hues are named for their colors, in hue, data-hue and the tokens: blue is powder, teal seafoam, green sage, amber butter, red blush, violet periwinkle, purple orchid and slate oat. selected shows an avatar as chosen, with a ring and a check, env-avatar-check. AvatarGroup rests bunched where a pointer can hover, with the count of the rest under the last avatar, and spreads along an arch under the pointer or with focus inside, within the room it keeps for the spread; gather sets where the avatars gather at rest. Avatars in a group and the count take no ring; --env-avatar-ring is gone, and --env-avatar-group-ring sets only the gap in a chosen avatar's ring.