DEV Community

Steve Alves-Blyt
Steve Alves-Blyt

Posted on

How to type hex colors in typescript ?

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;
Enter fullscreen mode Exit fullscreen mode

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}`;
Enter fullscreen mode Exit fullscreen mode

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}`;
Enter fullscreen mode Exit fullscreen mode

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^8=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];
Enter fullscreen mode Exit fullscreen mode

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}`;
Enter fullscreen mode Exit fullscreen mode

Top comments (0)