How to type hex colors in typescript ?

Our journey to type hex color

As typescript doesn’t accept regex in types, we had to find a way to type our hex colors in our design system. Indeed, it wouldn’t be very efficient to make a string type like that:

export type ColorValueHex = string;

as it will accept all strings. For that, we came up with 2 solutions: a simple but less rigorous and an exhaustive one but more complex. We’ll present those two solutions and explain which one we had to choose.

The simple and straightforward solution:

The idea is to use the string template feature of typescript to shrink the type a little:

export type ColorValueHex = `#${string}`;

With that, we don’t allow all types of string but only the ones beginning with the ‘#’ character. Moreover, it is quite readable and maintainable thanks to its simplicity.

However, we wanted to push it a little further to check if it would be possible to tackle all the cases.

The exhaustive one :

type HexaNumber = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
type HexaLetter = 'A' | 'B' | 'C' | 'D' | 'E' | 'F';
type HexaChar = HexaLetter | HexaNumber;
type Hexa3 = `${HexaChar}${HexaChar}${HexaChar}`;
type Hexa6 = `${Hexa3}${Hexa3}`;
type Hexa8 = `${Hexa6}${HexaChar}${HexaChar}`;
type ColorValueHexComplex = `#${Hexa3 | Hexa6 | Hexa8}`;

We cover all the use cases, but we faced another issue. We found that typescript put in memory all possible the possible values to evaluate a type. It meant to us 16⁸=4,294,967,296 namely more than 4 billions ! So we had the TS2590: Expression produces a union type that is too complex to represent error from our 16Gb RAM macs.

NB: Instead of doing an union type, we could have done a type Range using tail recursion instead of writing the number and letters one by one. Here is an example of Range type:

type Range<N extends number, Acc extends Array<number> = []> = (Acc['length'] extends N ? Acc : Range<N, [...Acc, Acc['length']]>);
type NumberRange = Mapped<9>[number];

However, we kept the previous one mainly for readability purposes (the unions types are quite small).

Conclusion

Finally, we came up with the first one. Pushing it into the last solution allowed us to reach the limit of typescript and learn about how typescript runs under the wheel. So until big changes in typescript, we advice that the best way to type an hex color is to use following type:

export type ColorValueHex = `#${string}`;