Introduction#
TailwindCSS is a utility-first CSS framework that allows you to build complex designs from a composition of small, single-purpose utility classes directly in your markup. TailwindCSS works perfectly with browser-heavy frameworks like React, Vue and server-side frameworks like Next.js thanks to its efficiency and zero runtime.
TailwindCSS proves that it resolves many issues of working with CSS and UI systems. You can have a better grasp of your components by looking at them in your components’ markup. You save time and energy from coming up with names for a simple component.
Download Comparison
Arbitrary values#
TailwindCSS ships with a predefined set of utility classes that you can use immediately. For example, Tailwind provides a set of predefined color system and spacing system. You can use them directly in your markup.
<div className="bg-blue-500 p-4"> <p className="text-white">Hello, world!</p> </div>
However, you can also use arbitrary values in your utility classes with the
syntax property-[value]
. For example:
<div class="bg-[#bada55] text-[22px] before:content-['Festivus'] grid grid-cols-[fit-content(theme(spacing.32))]" > <!-- ... --> </div>
This feature works very well with properties that support arbitrary values like
background-color
, font-size
, color
, etc. However, properties like
display
don’t support arbitrary values.
Arbitrary properties#
TailwindCSS also allows you to use arbitrary properties in your utility classes to use some CSS properties that are not covered by TailwindCSS. For example:
<div class="[mask-type:luminance] hover:[mask-type:alpha] [display:block]"> <!-- ... --> </div>
This syntax is like how you would write CSS in a stylesheet and you wrap around the property with square brackets. You can also use this feature to write custom CSS variables:
<div class="[--my-custom-property:10px]"> <!-- ... --> </div>
Dynamic styles#
A common case in building web components is to apply styles based on some sort of states. For example, we have a button component that changes its background color based on the state of the button.
{/* This won't work */} <button className={`bg-${ state === "danger" ? "red" : ( state === "warning" ? "yellow" : "blue" )}-500`}> </button>
This won’t work because TailwindCSS doesn’t support dynamic styles or any client-side logics. Every class names must be extractable at build time.
{/* This will work */} <button className={`${ state === "danger" ? "bg-red-500" : ( state === "warning" ? "bg-yellow-500" : "bg-blue-500" )}`}> </button>
Client-side styles#
But what if there is a situation where you really need to apply styles from the client-side? For example, you are building a floating modal that needs the current width of the device screen so that you can set the appropriate padding
import { useWindowSize } from "@uidotdev/usehooks"; function App() { const { width } = useWindowSize(); return ( <main className="flex flex-col items-center gap-8 py-16 max-w-[1280px] mx-auto" style={ { "--container-size": `${width}px`, } as CSSProperties } > {/* ... */} <p className="absolute bottom-4 bg-amber-500 p-4 rounded-lg w-[calc(var(--container-size)-48px)] left-[24px] border border-yellow-700 text-amber-50"> Click on the Vite and React logos to learn more </p> </main> ); }
Traditional classnames#
Having a lot of utility classes in your markup can be overwhelming. Imaging looking at a component like this:
<div class="rounded-s w-full h-full [&_img]:scale-100 group-hover:[&_img]:scale-105 group-hover:[&_video]:scale-105 [&_img]:transform-gpu [&_video]:transform-gpu [&_img]:transition-transform [&_img]:ease-curve-d [&_img]:duration-normal [&_video]:transition-transform [&_video]:ease-curve-d [&_video]:duration-normal w-1/2 mx-auto transition-opacity ease-curve-c duration-normal max-w-media relative"> <!-- ... --> </div>
Sometimes with components like this, it’s better to put all the CSS rules in a
traditional CSS class and use that one class in your markup. Luckily, TailwindCSS provides a native way to do this with the @apply
directive.
.card { @apply rounded-s w-full h-full mx-auto relative max-w-media; @apply [&_img]:scale-100 group-hover:[&_img]:scale-105 group-hover:[&_video]:scale-105; @apply [&_img]:transform-gpu [&_img]:transition-transform [&_img]:ease-curve-d [&_img]:duration-normal @apply [&_video]:transform-gpu [&_video]:transition-transform [&_video]:ease-curve-d [&_video]:duration-normal; @apply transition-opacity ease-curve-c duration-normal; }
<div class="card"> <!-- ... --> </div>
This way is very useful particularly when you write blogs or documentation that uses many uniformed, reusable components.
Directives#
However, writing custom CSS classes in CSS files does not give a good
developing experience. The first thing is that it might cause some compiling
issues if you are not careful with the @layers
directive in TailwindCSS1.
Secondly, intellisense on your editor, like Tailwind CSS IntelliSense
does not give any suggestions for your custom CSS classes.
To solve this issue, you can use the Tailwind Plugin System to create reusable, custom styles. For example:
import plugin from "tailwindcss/plugin"; const config = { darkMode: ["class"], content: [ "./pages/**/*.{ts,tsx}", "./components/**/*.{ts,tsx}", "./app/**/*.{ts,tsx}", "./src/**/*.{ts,tsx,mdx,md,html}", ], prefix: "", corePlugins: {}, theme: {}, plugins: [ // Other plugins plugin(function ({ addUtilities, theme }) { addUtilities({ ".content-container": { marginLeft: "var(--article-gutter-size)", marginRight: "var(--article-gutter-size)", marginBottom: theme("spacing.4"), width: "var(--article-container-size)", }, }); }), plugin(function ({ addComponents, theme }) { addComponents({}); }), ], }; export default config;
Modern text editor with TailwindCSS Intellisense will tell you what the class is expanded to.
Using TailwindCSS Intellisense#
Speaking of TailwindCSS Intellisense, it is a must have extension for developers to work with TailwindCSS. It provides
- Autocomplete. Intelligent suggestions for class names, as well as CSS functions and directives.
- Linting. Highlights errors and potential bugs in both your CSS and your markup.
- Hover Previews. See the complete CSS for a Tailwind class name by hovering over it.
- Syntax Highlighting. Provides syntax definitions so that Tailwind features are highlighted correctly.
Unfortunately, TailwindCSS Intellisense does not provide any official support for other editors than Visual Studio Code. However, TailwindCSS Intellisense also provides a Language Server Protocol (LSP) that you can use to build your own TailwindCSS Intellisense for other editors.
Using TailwindCSS Prettier Plugin#
Another must-have extension for working with TailwindCSS in VsCode is the prettier-plugin-tailwindcss. This plugin simply sorts and organizes your utility classes in your markup based on the TailwindCSS recommended order.
Since this is a Prettier plugin, it will work seamlessly wherever Prettier works in your project, popular IDEs or text editors, and even on command line.
<!-- Before --> <button class="text-white px-4 sm:px-8 py-2 sm:py-3 bg-sky-700 hover:bg-sky-800">...</button> <!-- After --> <button class="bg-sky-700 px-4 py-2 text-white hover:bg-sky-800 sm:px-8 sm:py-3">...</button>
Override TailwindCSS classes#
Overriding existing styles in existing components is a common case when working
with TailwindCSS. An easy and direct way to override classes is to use the
important modifier !
. For example:
<div class="bg-blue-500 !bg-red-500"> <!-- ... --> </div>
However, thou shalt not use !important
in CSS. Another way to override is to
use tailwind-merge.
import { twMerge } from 'tailwind-merge' twMerge('px-2 py-1 bg-red hover:bg-dark-red', 'p-3 bg-[#B91C1C]') // → 'hover:bg-dark-red p-3 bg-[#B91C1C]'
Refactor TailwindCSS classes#
As mentioned above, having a lot of utility classes in your markup is a pain. Put all the CSS rules in a traditional CSS class is helpful. But what if the component contains some conditions such as states, variants, etc.? Or even, you just want to break down the utility classes into smaller pieces that fit your 80-character line limit?
Packages like classnames and clsx allow you to combine multiple classes into one classname string. For example:
import * as React from "react"; import clsx from "clsx"; interface ButtonProps { size: "sm" | "md" | "lg"; color: "red" | "green" | "blue"; } export const Button: React.FC<ButtonProps> = ({ variant, size, color }) => { return ( <button className={clsx( "px-4 py-2", size === "sm" && "text-sm", size === "md" && "text-md", size === "lg" && "text-lg", color === "red" && "bg-red-500", color === "green" && "bg-green-500", color === "blue" && "bg-blue-500" )} > Click me </button> ); };
You can also combine clsx
with tailwind-merge
to create a robust utility
function for managing your styles. For example, creating a reusable function
to handle that all together:
import { type ClassValue, clsx } from "clsx"; import { twMerge } from "tailwind-merge"; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); }
And then use it in your reusable components that allow you to specify the class later:
import * as React from "react"; import { cn } from "@/lib/utils"; const Card = React.forwardRef< HTMLDivElement, React.HTMLAttributes<HTMLDivElement> >(({ className, ...props }, ref) => ( <div ref={ref} className={cn( "rounded-xl border bg-card text-card-foreground shadow", className, )} {...props} /> )); Card.displayName = "Card";
Conclusion#
TailwindCSS is a powerful tool that allows you to build complex designs with ease. By following these tips, you can get the most out of TailwindCSS and create beautiful, responsive websites quickly and efficiently. Remember that like any other tools, TailwindCSS has its own strengths and weaknesses. I hope this post has given you some insights into how to get the most out of TailwindCSS.