Tailwind with Typescript.

Tailwind with Typescript.

Typesafe Tailwind Classnames.

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

In Short ->

Without Tailwind

You would need to do the following to style the elements.

  • make a CSS file.
  • write CSS in there with proper selectors.
  • link/import to your HTML/ React Component file.
  • add those classnames from that CSS file created earlier to your HTML Tags.

In the above approach, there are multiple ways where you can make mistakes like

  • you may name the class something other than what you wrote on classname HTML attribute.
  • you may have created a file with a different filename than what you wrote in the href link attribute in head tag.
  • There are chances of you mistyping the classnames.

All these things would not flag an error but you will have to debug it manually.

With Tailwind

Tailwind comes with a one-time setup for your project, where you pick your classnames from documentation and just apply it.

DONE !!!

No more fuss.

Working with Tailwind & React

Button Styled with Tailwind.

image.png

Here is a simple button and we may have a couple of issues now.

  1. We do not get auto-complete while passing classnames as a prop.

  2. We may interchange the props that have been passed from the parent which could lead to unexpected results as we have default classes provided if no classes were passed for the specific property.

Lets build a type for these classes.

Going over tailwind documentation, found variants for different classes and added them to variant arrays.

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}]`

Checkout Typescript Playground for Examples.

Here we have made a union of all possible classes of z-index, even the Arbitrary values using Template Literal Types.

In short: 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|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}]`;

const className:bgColor = 'bg-rose-100';
const className1:bgColor = 'z-10';
const className2:bgColor = 'bg-[#eeeeee]';
const className3:bgColor = 'bg-black';


const text:textColor = 'text-rose-100';
const text1:textColor = 'z-10';
const text2:textColor = 'text-[#eeeeee]';
const text3:textColor = 'text-black';

Typescript Playground

Using these types in the Button Component.

Previously -> No Typescript. image.png

Now -> After using the above made Types.

We get TS Errors.

image.png

We get Autocomplete.

when passed as props, we get Autocomplete for classnames.
(To get autocomplete when specifically writing classname on HTML elements use Tailwind CSS IntelliSense) image.png

Making a Tailwind Type

Merging all to make a Tailwind type that can validate all tailwind classes that we have made so far.

Type Checking for one classname

Screen Recording 2022-09-06 at 1.36.38 AM.gif

Explanation: type Tailwind here is a generic type that requires an argument.
if S equals to a valid tailwind class (one of the validTailwindClasses union) then it returns S otherwise type never.

Now, checking for multiple classes fail !

Screen Recording 2022-09-06 at 1.42.52 AM.gif

With the above approach it only checks for one classname. Now, expanding the check for multiple classnames.

Type Checking for multiple classnames

export type Tailwind<S> = S extends validTailwindClasses
  ? S
  : S extends `${infer Class} ${infer Rest}`
  ? Class extends validTailwindClasses
    ? `${Class} ${Tailwind<Rest>}`
    : never
  : never;

Explanation: Theinfer keyword allows types to be extracted from conditions in conditional types. Here, we check if it is a single classname and if it is valid then we return the passed string, otherwise check for multiple classnames then recursively check for a valid class.

If it does not fit any valid classes then typenever is returned.

Implementing Tailwind Type in Button Component

image.png

Link

Playground

Did you find this article valuable?

Support Vishwajeet Raj by becoming a sponsor. Any amount is appreciated!