Demystifing the HEX

An image of playing cards, boolean symbols, and HEX units to demonstrate the ways data can be represented with different bases.
Illustration from Susie Lu

Computer programs are composed of 1s and 0s, something is either on or off, everything else is a composition on top of this concept. This is analogous to how the universe is made up of atoms. Just as we do not see atoms, we do not see the binary representation of data. However, we as web developers do in many cases come quite close to working directly with binary numbers without even knowing it! For example, binary numbers are pleasantly obscured in the wonderful concept of CSS HEX colors; Let’s dig in!

You may be familiar with a few colors in HEX #FFFFFF is white, #000000 is black,
#FF0000 is red and so on. While yes these are “HEX” colors, what does that even mean? What is HEX? HEX is short for HEXadecimal which is a fancy way to say a a unit that represents 16 possible values; In this case using 0 - 9 on the numeric scale and then A - F on the alphabetical scale to represent 16 possible values from [0 - 15].

Scale units: 0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F
Value units: 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15

In CSS HEX colors are used to represent RGB (red, green, and blue) colors in a somewhat terse and understandable way. For HEX colors each pair of HEX units represents one color channel, and each color channel has a possible value from 0 to 255

As an example let’s look at the color red #FF0000 , which breaks down to
FF for the red channel with a value of 255
00 for the blue channel with a value of 0
00 for the green channel with a value of 0

The question arises though how does FF equal 255? As we said earlier a single HEX value represents a number from 0 - 15 but what happens when you combine multiple of these HEX characters together?

To start let’s look at a more familiar non-HEX number 253 and show how we can break it up into the digits [2,5,3] and turn it back into the number 253.

The algorithm can be described as

  1. take the first $digit (least significant value) 3 [2,5,3],
  2. look at the right based $index of the digit in the sequence which would be 0 for 3
  3. Raise the base / radix to the power of $index and then multiply that by the $digit.
Hold up a moment, what is this base / radix thing?
In mathematics, a base or radix is the number of different digits or combination of digits and letters that a system of counting uses to represent numbers. For example, the most common base used today is the decimal system, base = 10. (source) Looking at our HEX example, we can see how HEXadecimal numbers are base 16 and thus can represent 16 possible values via (0 -> F) within a single digit.
Humans typically work in base 10 (0 -> 9) and Robots 🤖 like to operate in base 2 (0 -> 1).
It makes you wonder if humans had 12 fingers if we would count in base 12?

In the case of 253, we are showing a number with a base of 10. The 3 represents the ones place, the 5 represents the 10s place, and the 2 represents the 100s place. So (3 * 1) + (5 * 10) + (2 * 100) gives us 253.

We can then generalize this math a bit into code like this:

            
function toNumber(digits = [2,5,3], base = 10) {
    let sum = 0;
    for (let i = 0; i < digits.length; i++) { 
        // walk the digits in reverse order
        // 3 … 5 … 2
        // multiple each unit by base to the exponent power
        // digit 2, index of 0 … 10^ 0 === 1
        // digit 5, index of 1 … 10 ^ 1 === 10
        // digit 3, index of 2 … 10 ^ 2 === 100
        sum += digits[digits.length - (i + 1)] * ((Math.pow(base, i)));
    }
    return sum;
}
        
        

We can apply this same working methodology to convert #FD0000 which has a base of 16 to the base 10 number of 253.

[F, D]
(D(13) * 16^0 +
F(15) * 16^1) === 253
            

All of which to say a two-digit HEX encoding of color space gives us 256 possible values for each red, green, and blue channel yielding a color permutation space of 16777216 colors!
Using this new found information we can apply it to generate random colors via a script like this:

// Math.random generates a number from 0 -> 1
const color = ‘#’ + Math.floor((Math.random() * 16777216)).toString(16);
Which would give us a value like: #FFAE12

toString on a number?
The .toString(16) call after the number might be a bit confusing as it is an uncommon API for most folks. The toString API when called on a number takes an optional argument to set the the base explicitly which is quite useful in the case of taking a base 10 number and converting it to base 16 HEX representation.

I hope that the next time you encounter a HEX string when changing the background color of a div, or the text on the page that you now feel comfortable explaining what this series of base-16 digits represents 🤓.


Thanks so much to Fabián Cañas and Susie Lu for reviewing this post.

Sam Saccone @samccone