react-tailwind-css

How to Use Tailwind CSS in React

  • 6 min

In the modern development world, Tailwind CSS has established itself as one of the most widely used styling tools.

Unlike pre-built component libraries (like Material UI or Bootstrap) or traditional CSS methodologies (BEM, CSS Modules), Tailwind operates under the Utility-First concept.

Tailwind provides us with an extensive set of low-level classes that map directly to CSS properties, allowing us to build custom designs directly from JSX.

In this article, we will see how to integrate Tailwind CSS into a React project using Vite, how to configure it correctly, and the advantages (and disadvantages) of using it.

  1. No File Switching: You don’t have to jump between Component.jsx and Component.css. Everything is in the same file.
  2. Design System: Avoids the chaos of “magic numbers”. You can’t put margin: 13px. You have to stick to the scale (m-3, m-4), which guarantees visual consistency.
  3. Goodbye “Dead Code”: Thanks to its JIT (Just-In-Time) engine, only the CSS for the classes you actually use is generated.
  1. HTML Readability: The JSX gets filled with classes, making it difficult to see the document structure at a glance.
  2. Standard CSS: You learn a proprietary syntax (py-2) instead of standard properties (padding-top/bottom). If Tailwind disappears tomorrow, your knowledge of its classes isn’t very useful.
  3. Lack of Originality: All applications start to look the same.

Installation in Vite

Current integration in Vite is done through a native plugin, eliminating the need for complex JavaScript configuration files (which were previously required).

In newer versions, we only need to install the core package and the Vite-specific plugin.

npm install tailwindcss @tailwindcss/vite

Copied!

Plugin Configuration

Now, in the vite.config.js (or .ts) file, we import and register the plugin. This allows Vite to process Tailwind classes during development and build.

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite' // 1. Import

export default defineConfig({
  plugins: [
    react(),
    tailwindcss(), // 2. Add to the array
  ],
})
Copied!

Tailwind uses a JIT (Just-In-Time) engine. This means it scans our code files for class names and generates a CSS file only with the classes we have used.

Building a Component

Let’s remake our famous button, but this time with utilities.

export default function BotonTailwind() {
  return (
    <button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
      Click me
    </button>
  );
}
Copied!

Let’s analyze what’s happening:

  • bg-blue-500: Blue background (medium shade).
  • hover:bg-blue-700: Built-in pseudo-classes. On hover, darken the background.
  • py-2 px-4: Vertical and horizontal padding.
  • rounded: Rounded borders.

Notice the development speed. I haven’t written a single line of CSS, I haven’t created new files, and I have my styles, including state management (hover).

Managing Dynamic Classes

The problem with Tailwind is that the className attribute often becomes huge and hard to read, especially when we include conditional logic with Template Literals.

// ❌ Hard to read
<div className={`p-4 border ${isActive ? 'bg-blue-100 border-blue-500' : 'bg-gray-100 border-gray-300'}`}>
Copied!

This is why it’s common to use two almost mandatory tools:

  1. clsx: For building conditional class strings.
  2. tailwind-merge: To resolve class conflicts (e.g., if we try to override a p-4 with a p-8, it ensures the last one wins).

Install the utilities:

npm install clsx tailwind-merge

Copied!

Create a helper function (usually in src/lib/utils.js):

import { clsx } from "clsx";
import { twMerge } from "tailwind-merge";

export function cn(...inputs) {
  return twMerge(clsx(inputs));
}

Copied!

Now we can create components that accept external classes and variants cleanly:

import { cn } from "../lib/utils";

export default function Button({ className, variant = "primary", children, ...props }) {
  return (
    <button
      className={cn(
        // 1. Base classes (always applied)
        "py-2 px-4 rounded font-semibold transition-colors focus:outline-none",
        
        // 2. Conditional variants
        variant === "primary" && "bg-blue-600 text-white hover:bg-blue-700",
        variant === "secondary" && "bg-gray-200 text-gray-800 hover:bg-gray-300",
        variant === "danger" && "bg-red-500 text-white hover:bg-red-600",
        
        // 3. External classes (allows parent to add margins or widths)
        className
      )}
      {...props}
    >
      {children}
    </button>
  );
}

Copied!

This pattern allows us to maintain Tailwind’s flexibility encapsulated within React components.

Reuse and Extraction

Another common question when using utility classes is how to avoid code repetition, especially with extremely long classes that barely fit on the screen.

In the context of React and Tailwind, the usual solution is to create a component for the element. That is, instead of repeating classes:

// ❌ Repetitive and hard to maintain
<img className="rounded-full w-16 h-16 border-2 border-white" src="..." />
<img className="rounded-full w-16 h-16 border-2 border-white" src="..." />

Copied!

You create a component,

// ✅ Abstraction via React
function Avatar({ src }) {
  return <img className="rounded-full w-16 h-16 border-2 border-white" src={src} />;
}

Copied!

The class is still unbearably long, but at least you only see it once 🤭.

Tailwind or CSS Modules?

We shouldn’t marry tools, but choose the right one for each context. Personally I prefer the standard, but both are useful in their own way.

Use Tailwind if
  • You need to iterate very fast (prototyping, MVPs).
  • You work in a team where everyone uses a different grey and you need to enforce consistency.
  • You don’t mind having “dirty” HTML in exchange for development speed.
Stick with CSS Modules if
  • You like CSS: You want full control over your selectors, complex animations, and the cascade.
  • You value code cleanliness: You want your JSX to be semantic and readable.
  • You want to keep your knowledge aligned with web standards (W3C) and not with a specific library.