TailwindCSS: React Setup and className Override

TL;DR

In this article. I will talk about tailwind setup in typescript React.

Prerequisite

Since everybody’s React project setup is different nowadays.

To get started, follow the official tailwind installation guide and install for your project accordingly.

Keep the Ugliness of Tailwind (๑•̀ㅂ•́)و✧

It looks awful. But it works!

With tailwind, inevitably, class names grow longer and our template gets uglier.

To make it look better, we might be tempted to use CSS modules with @apply syntax to group and label tailwind classes with new classes. But doing so, in my humble opinion, we immediately lost 3 productivity boosts that tailwind has offered us:

  1. No need to switch between HTML and CSS file while styling a webpage
  2. No need to create and manage classes in the long run
  3. No need to look up class names while styling a webpage
╮(╯▽╰)╭

Tailwind is a strong opinion on CSS that deliberately trade this ugliness for the above, let’s keep the ugliness and find a way to make peace with it.

Consistently sorting tailwind classes is a great option, it can help us locate styles faster in the long run. It requires zero learning. As it grows on us, the long classes string no longer looks intimidating. Our eyes will naturally know where to look.

Install Tailwind Prettier Plugin

1: Install and Enable Prettier

Follow the prettier official guide and install prettier to your project.

Then, enable prettier on text editor, format on save or on reformat shortcut using prettier:

JetBrains prettier plugin

VScode prettier plugin

Personally, I find Prettier default printWidth = 80 too short, so I often add printWidth: 100 to .prettierrc.

2: Add prettier tailwind plugin

Tailwind offers this prettier-plugin-tailwindcss to auto-sort tailwind classes:

First, install with

Terminal window
npm install -D prettier prettier-plugin-tailwindcss

Then, add to your prettier config file .prettierrc

.prettierrc
{ "plugins": ["prettier-plugin-tailwindcss"] }

Side Note:

  1. Notice that we can also hint the plugin where to look by configuring non-standard attributes or tailwind classes in function call, when you find it not working in the cn function I about to introduce.
  2. Not Recommend to configure the sorting order yourself, the auto-sort brings more value as a standard protocol in team and in community over as a personal flavor expression tool.

Override Styles and Resolve Conflicts with cn utility function

The cn() function is simply a composition of clsx and tailwind-merge. It is used and popularized by shadcn-ui component library.

Install clsx and tailwind-merge :

Terminal window
npm i tailwind-merge clsx

Then export a cn function from somewhere in your project, src/utils/cn.ts for example:

src/utils/cn.ts
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}

className Overriding Pattern in TypeScript React

A rule of thumb for cn function is:

the latter inputs override the former inputs

We can encapsulate a component with className props as an outlet, offering a chance to assign more or override classes from props.

In Typescript, to keep the type hint, we can grab the default prop types using React.ComponentProps, which includes className

Inside the component, we intersect the className from props, tweak a little bit, then throw it back into props, so it is still available

Button.tsx
import React, { type ComponentProps } from "react";
import { cn } from "@/lib/utils";
type Props = ComponentProps<"button"> & {
size?: "sm" | "md" | "lg";
};
export function Button({ className, size = "md", ...rest }: Props) {
return (
<button
className={cn("bg-amber-50 font-semibold text-amber-600 shadow-sm hover:bg-amber-100", {
"rounded px-2 py-1 text-xs": size === "sm",
"rounded-md px-2.5 py-1.5 text-sm": size === "md",
"rounded-md px-3.5 py-2.5 text-sm": size === "lg",
})}
{...rest}
/>
);
}
Demo.tsx
import React, { useState } from "react";
import { Button } from "./Button.tsx";
import "@mantine/core/styles.css";
import { DemoPanel } from "@/components/react/DemoPanel.tsx";
const SIZE = ["sm", "md", "lg"] as const;
export function Demo() {
const [sizeIndex, setSizeIndex] = useState(0);
return (
<DemoPanel className="h-24">
<Button size={SIZE[sizeIndex]} onClick={() => setSizeIndex((sizeIndex + 1) % 3)}>
Click me: {SIZE[sizeIndex]}
</Button>
</DemoPanel>
);
}

In the above example, we can group classes and make it available as custom prop. We can also still override pre-defined classes in its parent component by passing className prop.

Side Note

When using tailwind with non-tailwind-based component libraries like MUI:

  • Better to only apply dimensional or positional classes than to override too hard and break into vis-bug
  • Better to use important tailwind syntax like className="!px-4" to ensure the override