I'm trying to create a standalone type in TypeScript that can be used to represent a single valid HEX color code as a fully type-safe string.
My attempt is below, which falls short due to not actually being a standalone type, which is what I would hope to achieve.
type HexDigit<T extends '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | 'a' | 'b' | 'c' | 'd' | 'e'| 'f' | 'A' | 'B' | 'C' | 'D' | 'E'| 'F'> = T;
type HexColor<T extends string> =
T extends `#${HexDigit<infer D1>}${HexDigit<infer D2>}${HexDigit<infer D3>}${HexDigit<infer D4>}${HexDigit<infer D5>}${HexDigit<infer D5>}`
? T
: (
T extends `#${HexDigit<infer D1>}${HexDigit<infer D2>}${HexDigit<infer D3>}`
? T
: never
);
function hex<T extends string>(s: HexColor<T>): T {
return s;
}
// All valid
hex('#ffffff');
hex('#fff');
hex('#000');
hex('#123456');
hex('#abcdef');
hex('#012def');
hex('#ABCDEF');
TypeScript playground link
Trying to use a type with a generic as a standalone type was bound to fail, so I got stuck at this point.
// Ideal use case - does not compute
const color: HexColor = '#aaa';
const theme: Record<string, HexColor> = {
backgroundColor: '#ff0000',
color: '#0f0',
};
Is this even possible to achieve with TypeScript, and if so, how?
Hex color codes start with a pound sign or hashtag (#) and are followed by six letters and/or numbers. The first two letters/numbers refer to red, the next two refer to green, and the last two refer to blue. The color values are defined in values between 00 and FF (instead of from 0 to 255 in RGB).
A hex color code is a 6-symbol code made of up to three 2-symbol elements. Each of the 2-symbol elements expresses a color value from 0 to 255. The code is written using a formula that turns each value into a unique 2-digit alphanumeric code. For example, the RGB code (224, 105, 16) is E06910 in hexadecimal code.
Hex color generator gives the hex code of selected color. To select a color, we will use <input type=”color”> which creates a color picker. Get the value returned by color picker.
For the visualizations where you are specifying a list of colors (i.e. multiple colors at once), this is where HEX comes into play. As the visualizations and charts universally can use HEX for custom coloring, the goal is to get a list of HEX codes in a palette.
As the visualizations and charts universally can use HEX for custom coloring, the goal is to get a list of HEX codes in a palette. The purpose of this post is two-fold: How you can set up a color palette for use in Displayr (with both RBG and HEX codes)
The visualizations in Displayr require you to use HEX codes when you want to customize the color palette. HEX codes are a six digit string of letters/characters that encode a specific color. RGB can encode colors too - they use a combination of three numbers separated by a comma (red,green,blue).
You probably already tried to do it the naive way, where you make a union of all the possibilites, and it does actually work for a three-digit hex color, but not for a longer one (see code below). I suspect, to answer your question directly, that it's impossible to make a 'standalone' type that works for all six-digit hex colors, because this would have to be a union of millions of elements.
type Digit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | 'a' | 'b' | 'c' | 'd' | 'e'| 'f' | 'A' | 'B' | 'C' | 'D' | 'E' | 'F';
type ShortColor = `#${Digit}${Digit}${Digit}`;
type LongColor = `#${Digit}${Digit}${Digit}${Digit}${Digit}${Digit}`; // Error
type Color = ShortColor | LongColor;
const someColor: ShortColor = '#fc2';
const badColor: ShortColor = '#cg2'; // Error
That said, I think your solution is a fine one itself. I did notice your code doesn't work on newer versions of TypeScript, so I made a slight modification to it so that it does work with the latest version, 4.4.0-beta. The code is a bit simpler and avoids generating too large of a union by doing the conditional check in two steps:
type HexDigit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | 'a' | 'b' | 'c' | 'd' | 'e'| 'f' | 'A' | 'B' | 'C' | 'D' | 'E'| 'F';
type HexColor<T extends string> =
T extends `#${HexDigit}${HexDigit}${HexDigit}${infer Rest1}`
? (Rest1 extends ``
? T // three-digit hex color
: (
Rest1 extends `${HexDigit}${HexDigit}${HexDigit}`
? T // six-digit hex color
: never
)
)
: never;
function hex<T extends string>(s: HexColor<T>): T {
return s;
}
// All valid
hex('#ffffff');
hex('#fff');
hex('#000');
hex('#123456');
hex('#abcdef');
hex('#012def');
hex('#ABCDEF');
Finally, I might suggest that you use type brands to create a type which signifies that a string is a valid hex color, and only use your hex
function to create strings of that type? In the example below, you could simply use Color
throughout your codebase and only need to rely on the hex
helper function when specifically making a Color
from a string literal for the first time. I don't know if this'll meet your exact ideal usecase, because you do still need to use your hex
helper wherever a literal is declared, but it gets you pretty close:
type Color = string & { __type: "HexColor" };
function hex<T extends string>(s: HexColor<T>): Color {
return s;
}
const color: Color = hex('#aaa');
const theme: Record<string, Color> = {
backgroundColor: hex('#ff0000'),
color: hex('#0f0'),
};
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With