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.
- 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 autoprefixernpx tailwindcss init -p/* app/globals.css */@tailwind base;@tailwind components;@tailwind utilities;// app/layout.tsximport './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.jsmodule.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.tsxexport 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 clsximport 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> );}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
- 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.