When creating an icon component, I wanted to receive color-related properties as props and add them to className.
Icons are composed of SVG, and instead of directly specifying fill and stroke values, I wanted to use Tailwind CSS's autocompletion to specify colors in className.
For example, I wanted to write code like this:
interface IconProps {
fill: string;
stroke: string;
}
const Icon = ({fill, stroke}: IconProps) => {
<svg className={`${fill} ${stroke} `} />
}
To leverage Tailwind's autocompletion, I needed to use a type other than string
for the fill
and stroke
types. In other words, I needed to extract the names of color classes configured in Tailwind.
Extracting Types
First, we can get the basic color information from Tailwind.
import defaultColors from "tailwindcss/colors";
export type DefaultColors = keyof typeof defaultColors;
const color: DefaultColors = 'blue'
However, only the color keys are autocompleted, not the properties that follow. (blue
works, but blue-500
etc. are not autocompleted)
Next, let's get the entire Tailwind config.
import resolveConfig from "tailwindcss/resolveConfig";
import tailwindConfig from "web/tailwind.config.ts"; // Tailwind config file
const fullConfig = resolveConfig(tailwindConfig);
Then we can get the following information from fullConfig
:
Let's combine the above information to create a new type called ColorShade
type ColorShade<T extends DefaultColors> = keyof ColorConfig[T] extends
| string
| number
? keyof ColorConfig[T]
: never;
const shade: ColorShade<'blue'> = '500'
Now we can also get the properties after the color.
By combining these, we can extract names that combine color and brightness.
type TailwindColorClass = {
[P in DefaultColors]: ColorShade<P> extends never ? P : `${P}-${ColorShade<P>}`;
}[DefaultColors];
Application
Now let's create className that can go into fill for fill and className that can only go into stroke for stroke, which was the functionality I wanted.
interface IconProps {
fill: `fill-${TailwindColorClass}`;
stroke: `storke-${TailwindColorClass}`;
}
Applying Custom Colors
The problem with the above code is that it only gets Tailwind's default colors and cannot get user-defined colors.
Separating Custom Colors
First, let's separate the custom-applied colors into a single variable.
const color = {
...formalColors,
...keyColors,
...accentColors,
...grayColor,
...theme.colors,
}
And instead of using DefaultColors
used above, let's create the following type
type CustomColors = typeof color;
type DefaultColors = typeof defaultColors;
export type Colors = CustomColors & DefaultColors;
Final Code
tailwin.config.ts
type CustomColors = typeof color;
export type DefaultColors = typeof defaultColors;
export type Colors = CustomColors & DefaultColors;
Component.tsx
const fullConfig = resolveConfig(tailwindConfig);
const colorTheme = fullConfig.theme.colors as Colors;
type ColorConfig = typeof colorTheme;
type ColorKeys = keyof Colors;
type ColorShade<T extends ColorKeys> = keyof ColorConfig[T] extends
| string
| number
? keyof ColorConfig[T]
: never;
type TailwindColorClass = {
[P in ColorKeys]: ColorShade<P> extends never ? P : `${P}-${ColorShade<P>}`;
}[ColorKeys];
interface IconProps {
fill: `fill-${TailwindColorClass}`;
stroke: `storke-${TailwindColorClass}`;
}