What the Hex?

What the Hex?

24 October 2022

If you’re a designer or frontend developer, chances are you’ve happened upon hex color codes (such as `#ff6d91`

). Have you ever wondered what the hex you’re looking at when working with hex color codes? In this post we’re going to break down these hex color codes and how they relate to RGB colors.

## What is “hex”?

“Hex” is short for “hexadecimal”. But what *is* “hexadecimal”?

In our day-to-day life, we generally think and work in a *base-10* number system, where the `n`

th digit in a number represents some scaled amount of `10^n`

. For example, `257`

can be thought of as `2*100 + 5*10 + 7*1`

. Each time you move a digit to the left, your multiplier is getting 10 times larger. And for each digit place, we need 10 options (0 through 9). This is illustrated below.

When it comes to computing, you might be familiar with the notion of “binary” – which is a *base-2* number system. For example, the binary number `0b10110`

(where the `0b`

prefix just indicates that it’s a binary number) is equal to `1*16 + 0*8 + 1*4 + 1*2 + 0*1 = 22`

– notice how each time you move a digit to the left, the “multiplier” becomes 2 times larger, and there are only two options per digit place (`0`

and `1`

). Therefore `0b10110`

is the binary (or base-2) representation of the base-10 number `22`

.

Okay, so how do hexadecimals fit into this? Well, hexadecimal numbers are just **base-16 numbers**. Each digit place in a hexadecimal number needs 16 options… but we only have 0 through 9 for number options. To deal with this issue, we just start dipping into the alphabet and borrow letters “a” through “f”!

Our base-16 digits now run “from 0 to f”, which feels a bit weird to say – but just keep in mind that we’re tacking “a” through “f” onto the end of 0…9.

Let’s take a peak at a sample hexadecimal number. The number `0x38f`

(where the `0x`

prefix just indicates that this is a hexadecimal number) is actually equal to `3*16^2 + 8*16 + 15*1 = 911`

. Remember, `f`

in hex-world is equal to `15`

in base-10-world!

Okay, chances are you’re not going to want to do these computations by hand. Let’s use a little JavaScript to move back and forth between hex-world and base-10-world.

### Converting between base-10 and hexadecimal in JS

Fear not, converting between base-10 and hexadecimal in JS is quite easy! There are already built-in functions to handle this for you.

```
const base10ToHex = (x: number) => x.toString(16);
const hexToBase10 = (h: string) => parseInt(h, 16);
```

Yep, that’s it. The `Number.prototype.toString`

method accepts a base (or a “radix”) as an argument and will convert the given number to that base. The `parseInt`

global function accepts a base (or “radix”) as a second argument and will assume the first argument is a number in *that* specified base.

Remember our `0x38f`

math we did above? I much prefer to open a JS REPL and run `parseInt("38f", 16)`

. It returns `911`

and I don’t have to do any arithmetic. (You can also just enter `0x38f`

into a JS REPL and it’ll give you the base-10 equivalent, but that’s a bit less generalizable.)

Okay, so this is feeling a lot like math. Let’s talk about colors instead.

## Hex color codes and RGB

I’m assuming you have some familiarity with RGB colors, where you can represent a color as `rgb(R, G, B)`

where `R,G,B`

are integer values between 0 and 255 – and each represent how much of the respective red/green/blue color should be “added” to the resulting color.

Each of the red, green, and blue components can be an integer between 0 and 255 – a total of 256 total options. Conveniently, `256 = 16^2`

and therefore you can nicely represent “an integer between 0 and 255” as a 2-digit hexadecimal value! It turns out, that’s pretty much what hex color codes are all about – turning your RGB color representation (using base-10 RGB values) into a similar RGB representation using hexadecimal values! This is best illustrated with a diagram.

For example, `rgb(100, 30, 200)`

can be represented as `#641ec8`

because when we convert our RGB components from base-10 to hexadecimal, we have `100 → 64`

and `30 → 1e`

and `200 → c8`

. Make sure you can match those up in the two different representations of the color!

To really drive this point home, let’s write a little function that will do this conversion for us.

```
// Note the padStart(2, '0') to ensure length of 2
const base10ToHex = (x: number) => x.toString(16).padStart(2, '0');
const rgbToHex = ({ r, g, b }: { r: number; g: number; b: number }) => {
return `#${base10ToHex(r)}${base10ToHex(g)}${base10ToHex(b)}`;
}
```

I’ve omitted additional checks (to ensure `r`

, `g`

, and `b`

are integers between 0 and 255), but hopefully you get the idea. It’s worth pointing out the added `.padStart(2, '0')`

which will ensure that each hex value will have two digits (padding with `0`

if necessary).

Since the RGB and hexadecimal representations of colors are in a sense “isomorphic”, we can just as easily move from hex to RGB!

Let’s write a little code to do this conversion.

```
// For simplicity, assume hexValue of shape #xxxxxx
const hexToRgb = (hexValue: string) => {
const rHex = hexValue.substring(1, 3);
const gHex = hexValue.substring(3, 5);
const bHex = hexValue.substring(5, 7);
const r = hexToBase10(rHex);
const g = hexToBase10(gHex);
const b = hexToBase10(bHex);
return { r, g, b };
}
```

Awesome! Now we’ve seen enough code to successfully move back and forth between RGB and hex representations.

## 3-digit hex codes

You’ll generally see hex color codes as 6-digit hex codes, such as `#ff6d91`

. However, you might also run into 3-digit hex color codes out in the wild!

Recall that 6-digit hex color are just hexadecimal representations of RGB colors, where the first two hex digits (say `r1`

and `r2`

) represent “how much red”, the second two hex digits (say `g1`

and `g2`

) represent “how much green”, and the last two hex digits (say `b1`

and `b2`

) represent “how much blue”.

There’s a special scenario where `r1 === r2`

and `g1 === g2`

and `b1 === b2`

(such as for `#ff0033`

). In this scenario, we can cheekily condense our representation from `#[r1r2][g1g2][b1b2]`

to just `#[r1][g1][b1]`

– and instead of writing the duplicate digits for each color dimension, just use a single digit!

Here’s an example. Since `#ff0033`

has duplicate digits in all three RGB dimensions (e.g. we have `ff`

for red, `00`

for green, and `33`

for blue), we can “fold” the dimensions down into single digits, representing this color as just `#f03`

. This is represented below in the diagram below.

In this special scenario, we can “fold” our 6-digit representation down into a 3-digit representation. This also means that if you see a 3-digit hex color code, it’s just a folded-down version of a 6-digit color code and you can expand it back out by duplicating each digit! For example, `#a3f`

is really just `#aa33ff`

in disguise! Here’s a little code to showcase this expansion.

```
const expandHexColor = (hexCode: string): string => {
const hexValue = hexCode.substring(1);
// If 3-digits, duplicate each digit.
if (hexValue.length === 3) {
const expandedHexValue = [...hexValue].map(x => `${x}${x}`).join('')
return `#${expandedHexValue}`;
}
// Otherwise, we'll assume it's a 6-digit code and return the original.
return hexCode;
}
```

## Alpha channel: 8-digit hex codes

Browsers support a “fourth dimension” for RGB colors, namely the “alpha channel” – which specifies how transparent/opaque the color is (or, how “see-through” it is). In CSS, you’ll generally see this specified via the `rgba`

function (the trailing `a`

standing for “alpha”), where you can specify how much opacity the color should have (where `a`

varies from 0 to 1).

For example, `rgb(21, 188, 168)`

is a blueish color. If we wanted to make this “partially opaque”, we could specify an opacity of 0.6 by writing `rgba(21, 188, 168, 0.6)`

.

Hex color codes can *also* support alpha channel specification! If you take a 6-digit hex color code, such as `#ff0033`

, you can specify the opacity by adding two more hex digits to the end to specify the alpha channel amount – where `00`

(`0`

in base-10) is no opacity and `ff`

(`255`

in base-10) is full opacity. As an example `#ff003380`

is just `#ff0033`

with partial opacity.

With `rgba`

, the alpha dimension ranges from 0 to 1. However, with 8-digit hex color codes, the alpha dimension ranges from 0 to 255 – and therefore when converting back and forth between `rgba`

and 8-digit hex color codes, you’ll have to scale up or down by 255 accordingly.

We can now update our hex ↔ RGB conversions to handle alpha channel!

```
const rgbaToHex = ({ r, g, b, a = 1 }: { r: number; g: number; b: number; a?: number }) => {
const hex = `#${base10ToHex(r)}${base10ToHex(g)}${base10ToHex(b)}`;
// If a (opacity) is 1, use 6-digit hex code
if (a === 1) return hex;
// Otherwise, scale a by 255 and convert to hex for the alpha-channel digits.
return `${hex}${base10ToHex(Math.round(255 * a))}`
}
const hexToRgba = (hexValue: string) => {
const rHex = hexValue.substring(1, 3);
const gHex = hexValue.substring(3, 5);
const bHex = hexValue.substring(5, 7);
// alpha-channel, default to 'ff' (full opacity)
const aHex = hexValue.substring(7, 9) || 'ff';
const r = hexToBase10(rHex);
const g = hexToBase10(gHex);
const b = hexToBase10(bHex);
// Remember to scale from [0, 255] to [0, 1]
const a = hexToBase10(aHex) / 255;
return { r, g, b, a };
}
```

Notice how we have to scale the alpha channel up/down by 255 and convert between base-10 and hex.

### 4-digit hex codes

Just like how we can, by convention, “fold” some special 6-digit hex color codes into 3-digit ones, we can do a similar thing with 8-digit hex color codes! We can do this whenever the RGB components of the hex code (the first 6 digits) are “foldable” *and* the last 2 digits (that specify the alpha channel amount) are also “foldable”.

As an example, `#ff003388`

can be “folded” all the way down to `#f038`

. However, `#ff003380`

*cannot* be folded to a 4-digit representation because the alpha-channel digits `80`

are not the same.

## Wrap up

So many options! When using hex color codes, we can have 3-digit, 4-digit, 6-digit, *or* 8-digit hex color codes! This is a lot of edge cases to handle if you’re writing conversion code, but luckily most design and development tools handle this sort of thing for you. If in doubt, just stick to 6-digit hex codes – or use 8-digit hex codes if you need to specify an opacity.

Color theory is complex. This post will not make you a color expert. However, if you’re a designer or frontend developer, chances are you’re working with hex color codes regularly. Having a baseline understanding of what this color representation *actually means* might help you in understanding the hex color codes you’re using on a daily basis!