Extracting Color Properties from Tailwind CSS

profile image

Let\'s extract color types set in Tailwind CSS!

This post has been translated by DeepL . Please let us know if there are any mistranslations!

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:

typescript
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.

typescript
import defaultColors from "tailwindcss/colors";
export type DefaultColors = keyof typeof defaultColors;

const color: DefaultColors = 'blue'

Image.png

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.

typescript
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:

Image.png

Let's combine the above information to create a new type called ColorShade

typescript
type ColorShade<T extends DefaultColors> = keyof ColorConfig[T] extends
  | string
  | number
  ? keyof ColorConfig[T]
  : never;

const shade: ColorShade<'blue'> = '500'

Image.png

Now we can also get the properties after the color.

By combining these, we can extract names that combine color and brightness.

typescript
type TailwindColorClass = {
  [P in DefaultColors]: ColorShade<P> extends never ? P : `${P}-${ColorShade<P>}`;
}[DefaultColors];

Image.png

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.

typescript
interface IconProps {
  fill: `fill-${TailwindColorClass}`;
  stroke: `storke-${TailwindColorClass}`;
}

Image.png

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.

typescript
const color = {
  ...formalColors,
  ...keyColors,
  ...accentColors,
  ...grayColor,
  ...theme.colors,
}

And instead of using DefaultColors used above, let's create the following type

typescript
type CustomColors = typeof color;
type DefaultColors = typeof defaultColors;
export type Colors = CustomColors & DefaultColors;

Final Code

tailwin.config.ts

typescript
type CustomColors = typeof color;
export type DefaultColors = typeof defaultColors;
export type Colors = CustomColors & DefaultColors;

Component.tsx

typescript
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}`;
}
❤️ 0
🔥 0
😎 0
⭐️ 0
🆒 0