Tailwind with TypeScript

September 5, 2022

Tailwind is amazing. It gives you the ability to style your components without even writing a single line of CSS.

Without Tailwind

You would need to:

  • Make a CSS file.
  • Write CSS in there with proper selectors.
  • Link/import it into your HTML/React component file.
  • Add those class names from that CSS file to your HTML tags.

In the above approach, there are multiple ways you can make mistakes: mistyping class names, using wrong file references, and so on. All of these things would not flag an error, and you would have to debug them manually.

With Tailwind

Tailwind comes with a one-time setup for your project, where you pick your class names from the documentation and just apply them. DONE! No more fuss.

Working with Tailwind & React

Here is a simple button, and we may run into a couple of issues:

  1. We do not get auto-complete while passing class names as a prop.
  2. We may mix up the props, which could lead to unexpected results.

Let's build a type for these classes.

Z-Index Classname Types

type sizeSpecificVariants = ['auto', 'full', 'screen', 'min', 'max', 'fit']; type zIndexVariants = ['0','10','20','30','40','50'] type integerVariants = ['', '-'] // Making a Union out of Arrays. type zIndexVariantsUnions = zIndexVariants[number]; type integerVariantsUnion = integerVariants[number] type zIndexClasses = | `${integerVariantsUnion}z-${zIndexVariantsUnions}` | `z-${sizeSpecificVariants[0]}` | `${integerVariantsUnion}z-[${number}]`

We are combining two unions using template literal types. This gives us all possible combinations.

Background Color & Text Color Classname Types

type colorsWithNoVariant = ['inherit','current','transparent','black']; type colorsWithVariants = ['white','slate','gray','zinc','neutral', 'stone','red','orange','amber','yellow','lime','green','emerald', 'teal','cyan','sky','blue','indigo','violet','purple','fuchsia', 'pink','rose'] type numberVariants = ['100','200','300','400','500','600','700','800','900']; type opacity = 10|20|30|40|50|60|70|80|90; type colorsWithVariantsUnion = colorsWithVariants[number]; type colorsWithNoVariantsUnion = colorsWithNoVariant[number]; type numberVariantsUnion = numberVariants[number] type bgColor = | `bg-${colorsWithVariantsUnion}-${numberVariantsUnion}/${opacity}` | `bg-${colorsWithVariantsUnion}-${numberVariantsUnion}` | `bg-${colorsWithNoVariantsUnion}` | `bg-[#${string}]`; type textColor = | `text-${colorsWithVariantsUnion}-${numberVariantsUnion}/${opacity}` | `text-${colorsWithVariantsUnion}-${numberVariantsUnion}` | `text-${colorsWithNoVariantsUnion}` | `text-[#${string}]`;

Making a Tailwind Type

We can merge everything to make a Tailwind type that can validate all Tailwind classes.

Type Checking for one class name

For a single class name, we create a conditional type that checks if the string S extends the validTailwindClasses union. If it does, it returns S; otherwise, it returns never.

Type Checking for multiple class names

For multiple class names, we use the infer keyword to split the string at spaces. We recursively check each class against the valid union. If all classes are valid, the full string type is returned. If any class is invalid, never is returned, giving us a compile-time error.

This gives us both TypeScript errors for invalid class names and autocomplete when passing class names as props!

GitHub
LinkedIn