TailwindCSS: React Setup and className Override
TL;DR
In this article. I will talk about tailwind setup in typescript React.
- Keep the Ugliness of Tailwind
- Install Tailwind Prettier Plugin
- Override Styles and Resolve Conflicts with cn utility function
- className Overriding Pattern 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:
- No need to switch between HTML and CSS file while styling a webpage
- No need to create and manage classes in the long run
- 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:
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
npm install -D prettier prettier-plugin-tailwindcssThen, add to your prettier config file .prettierrc
{ "plugins": ["prettier-plugin-tailwindcss"] }Side Note:
- 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
cnfunction I about to introduce. - 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 :
npm i tailwind-merge clsxThen export a cn function from somewhere in your project, src/utils/cn.ts for example:
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
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} /> );}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