dio.la

Dani Guardiola’s blog

May 2, 2023May 2nd, 2023 · 1 minute read · tweet

Colorful avatars

An interactive walkthrough of an algorithm to pick an avatar's color based on the user's initials.

This article's main image

I've written about tracking the loading state of an avatar image before. However, it's not uncommon to have users without a profile picture.

In that case, the usual approach is to show the user's initials over a colored background.

Three colorful avatars displaying initials

Picking the right color

Avatars with images are recognizable and memorable. Just like you might recognize someone close to you by the sound they make when they walk, avatar images become a shortcut to quickly identify a user.

When there is no image to display, achieving the same result is a bit harder. A person's initials alone are not very useful when doing a quick visual scan. That's where the color comes in, and there are a few important rules to follow:

  1. Deterministic outcome: it's not enough to just pick a random color, it needs to be consistent. If it was different every time, it'd be useless and confusing!
  2. Uniform probability distribution: one color should not show up more often than the others. Note that we're not going for perfection here, but it should be close enough.
  3. Disctinct permutations: different arrangements of the same letters (like "DG" and "GD") should result in different colors. If not, it'd be hard to tell them apart since they share the same letters and colors!

With these in mind, we can now build the algorithm.

Do these rules remind you of something?

If you've ever worked with hash functions, you'll notice that they have a lot in common. In fact, when I first implemented this, I called it a "color hash"!

Thanks to Vladimir Klepov (who reviewed this article) for pointing this out.

The magic number

There are many possible combinations of initials.

We have 702 potential combinations with one or two initials (261+262=70226^1 + 26^2 = 702), and that's just for the English alphabet (A-Z). It increases greatly when you account for other alphabets! Our mission is to turn each of these into a small number so that we can map it to a color.

For that, we can use a bit of simple modular arithmetic, but we need to turn our initials into a number first. I call this number the "magic number" because I am fancy like that.

ts
function getInitialsMagicNumber(initials: string) {
const numbers = initials
.toLowerCase()
.substring(0, 2)
.split("")
.map((char) => char.charCodeAt(0));
return numbers.reduce((acc, n) => acc + n);
}
Try

To achieve this, we:

  1. "Normalize" the initials string by converting it to lowercase and taking only the first two characters.
  2. Split and turn both characters into numbers with String.prototype.charCodeAt.
  3. Finally, add them together to get the magic number.

Here's a little playground to try it out:

203

Mapping to a color

Let's say we have these five colors:

ts
const COLORS = ["purple", "orange", "green", "yellow", "blue"] as const;
Try

We can now map the magic number to one of these colors by using the remainder (%) operator (A.K.A. modulo).

ts
export function getAvatarColorFromInitials(initials: string) {
const magicNumber = getInitialsMagicNumber(initials);
const colorIndex = magicNumber % COLORS.length;
return COLORS[colorIndex];
}
Try

Try it:

DG

With this, we've achieved our first two goals: deterministic outcome and (more or less) uniform probability distribution. We're missing the third one, though.

Disctinct permutations

Here's a demo that shows the color for both permutations of the same initials (the numbers below are the magic numbers):

DG
203
GD
203

As you can see, with the current algorithm, the colors are the same for both permutations. This is because the magic number is the same for both.

We can fix that with a little trick: we can add something I called "spice" (because, again, I am fancy like that) to the magic number. This spice will be a number that is either 0 or 1, depending on the order of the characters (based on their UTF-16 code unit).

For example, if we have "DG" and "GD", only one of them will be affected by the spice, so they will have different magic numbers and, therefore, different colors.

ts
function getInitialsMagicNumber(initials: string) {
const numbers = initials
.toLowerCase()
.substring(0, 2)
.split("")
.map((char) => char.charCodeAt(0));
const spice = numbers[0] < numbers[1] ? 0 : 1;
return numbers.reduce((acc, n) => acc + n) + spice;
}
Try

Now it works as intended:

DG
203
GD
204

And we are done!

Bonus: initials from name and one final demo

Here's another neat utility to extract initials (up to two characters) from a name:

ts
export function getInitialsFromName(name: string) {
const matchResult = name?.matchAll(/(\S)\S*/g);
const matches = [...matchResult].map((r) => r[1]);
return matches ? `${matches[0]}${matches[1] ?? ""}` : undefined;
}
Try

Finally, here's a demo of the Avatar component I built for Guide that displays all permutations of initials using a-z and a few non-English characters:

Demo displaying all permutations of initials and their colors

Keep up with my stuff. Zero spam.