LearnAI ToolsCareerPractice BuildsPlayContact
Lesson 2919 min read

Tailwind with React/Next.js

Set up Tailwind CSS in a Next.js project and learn patterns for conditional and dynamic classes inside JSX components.

Introduction

Everything you have learned so far applies directly to React and Next.js — Tailwind classes work the same whether they sit in a .html file or a JSX className attribute. The only new skill is JSX-specific: how to toggle classes based on component state and props, since JSX does not have a built-in templating syntax for conditional strings.

What You Will Learn
  • How to add Tailwind to a Next.js project.
  • Why the content option in tailwind.config.js matters for JSX files.
  • Two common patterns for conditional classes: template literals and the clsx library.

Setting Up in Next.js

Modern Next.js projects (via create-next-app) offer to install Tailwind automatically. Setting it up manually only takes a few steps: install the packages, generate a config file, and add the three @tailwind directives to your global stylesheet.

npx create-next-app@latest my-app
# When prompted "Would you like to use Tailwind CSS?", choose Yes
# Or, to add it manually to an existing project:
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
/* app/globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
// app/layout.tsx
import './globals.css';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}

Configuring Content Paths

Tailwind only generates CSS for class names it actually finds by scanning your files, so the content array in tailwind.config.js must include every folder containing JSX or TSX with class names — miss a folder, and its classes silently do not compile.

// tailwind.config.js
module.exports = {
content: [
'./app/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: { extend: {} },
plugins: [],
};

Building a Component

Inside a component, Tailwind classes go in the className prop instead of HTML's class attribute — everything else about writing them is identical.

// components/Button.tsx
export default function Button({ children }: { children: React.ReactNode }) {
return (
<button className="px-4 py-2 rounded-md bg-blue-600 text-white font-semibold hover:bg-blue-700 transition-colors">
{children}
</button>
);
}

Conditional Classes with Template Literals

For a small number of conditions, a plain JavaScript template literal works fine — no extra dependency needed.

function Alert({ variant }: { variant: 'success' | 'error' }) {
return (
<div
className={`rounded-md p-4 text-sm ${
variant === 'success'
? 'bg-green-50 text-green-700 border border-green-200'
: 'bg-red-50 text-red-700 border border-red-200'
}`}
>
{variant === 'success' ? 'Saved successfully!' : 'Something went wrong.'}
</div>
);
}

Conditional Classes with clsx

Once you have more than two or three conditions, template literals get hard to read. The clsx (or classnames) package takes an object of class-name-to-boolean pairs and joins the truthy ones into a single string.

npm install clsx
import clsx from 'clsx';
function NavLink({ href, isActive, children }: { href: string; isActive: boolean; children: React.ReactNode }) {
return (
<a
href={href}
className={clsx(
'text-sm font-medium transition-colors',
isActive ? 'text-blue-600 font-semibold' : 'text-gray-700 hover:text-blue-600'
)}
>
{children}
</a>
);
}
Result

Click Run to see what this code prints.

Passing className as a Prop

Reusable components often need to accept extra classes from whoever renders them — for example, letting a parent adjust spacing without editing the component itself. Merge the incoming className with the component's own defaults using clsx.

function Card({ className, children }: { className?: string; children: React.ReactNode }) {
return (
<div className={clsx('p-6 bg-white rounded-lg shadow-sm border border-gray-200', className)}>
{children}
</div>
);
}
// Usage: adds margin-top without touching Card's internals
<Card className="mt-8">Card content</Card>

Common Mistakes

Avoid These Mistakes
  • Forgetting to add a components or app folder to the content array, so classes used only in that folder never generate.
  • Building class names by string concatenation, like `bg-${color}-500`, which Tailwind cannot detect during static content scanning — it needs to see the full class name literally in the source.
  • Using class instead of className in JSX, which React silently ignores.
  • Passing an unmerged className prop that gets rendered separately instead of combined with the component's own classes, causing styles to conflict or not apply.

Best Practices

  • Always write full, static class names Tailwind's scanner can see — avoid dynamically constructing them from variables.
  • Use clsx once a component has more than two or three conditional classes.
  • Let reusable components accept an optional className prop merged with clsx, so consumers can adjust layout without editing the component.
  • Keep the content array in tailwind.config.js in sync with your actual folder structure as the project grows.

Frequently Asked Questions

Tailwind does not execute your JavaScript — it scans the raw text of the listed files for strings that look like class names. If a file is not listed, its classes are invisible to the scanner and get purged.

Template literals are fine for one or two conditions; clsx becomes worth the small dependency once you are juggling several conditional classes or merging a className prop, since it is easier to read and handles falsy values automatically.

Yes, they can coexist, though most teams pick one primary styling approach to avoid maintaining two mental models for the same UI.

Key Takeaways

  • Tailwind classes work identically in JSX — they simply go in className instead of class.
  • The content array must list every folder containing files with Tailwind classes.
  • Template literals handle simple conditional classes; clsx scales better for complex or merged class logic.
  • Never build class names dynamically from variables — Tailwind's static scanner cannot detect them.

Summary

You set up Tailwind in a Next.js project and learned the standard patterns for conditional and merged classes in JSX. Next, you will look under the hood at how Tailwind's content scanning keeps production CSS bundles small by removing every class you never actually used.

Next Lesson →

Performance & Purging Unused CSS