Styling: CSS Modules & Tailwind
Next.js does not force one way of styling. You can write ordinary CSS, scope it to one component with CSS Modules, use Tailwind's utility classes, or combine them. The default project uses Tailwind CSS plus a small global stylesheet, and that combination works well for most teams.
In this lesson you will use global CSS, write CSS Modules, set up and use Tailwind CSS v4, and learn when Sass and CSS-in-JS make sense with Server Components.
Global CSS
A global stylesheet applies to every page. Import it once in the root layout:
/* app/globals.css */
:root {
--brand: #111827;
--accent: #f59e0b;
}
body {
margin: 0;
font-family: system-ui, sans-serif;
color: var(--brand);
}
@media (prefers-color-scheme: dark) {
:root { --brand: #f3f4f6; }
body { background: #0b0f19; }
}// app/layout.jsx
import "./globals.css";Keep global CSS for things that really are global: CSS variables, base styles for body and headings, and Tailwind's import. Next.js does not remove a stylesheet when you navigate away from the page that imported it, so page-specific rules in global files can clash later. For component styles, use CSS Modules or Tailwind.
CSS Modules
A file ending in .module.css is a CSS Module. When you import it, you get an object whose keys are your class names, and Next.js rewrites each class to a unique name. Two components can both use .title without affecting each other.
/* app/courses/CourseCard.module.css */
.card {
border: 1px solid #e5e7eb;
border-radius: 8px;
padding: 16px;
}
.title {
margin: 0 0 8px;
font-size: 1.25rem;
}
.free {
color: #15803d;
}// app/courses/CourseCard.jsx
import styles from "./CourseCard.module.css";
export default function CourseCard({ title, fee }) {
return (
<article className={styles.card}>
<h2 className={styles.title}>{title}</h2>
<p className={fee === 0 ? styles.free : undefined}>{fee === 0 ? "Free" : `₹${fee}`}</p>
</article>
);
}In the browser the class might become something like CourseCard_title__x1y2z in development; production builds use even shorter names. CSS Modules work in Server and Client Components.
The runnable example imitates what CSS Modules do: it gives two components the same class name, title, and makes each unique by adding a component prefix, so the styles never collide:
import { createRoot } from "react-dom/client";
function cssModule(componentName, css) {
const map = {};
const scoped = css.replace(/\.([a-zA-Z][\w-]*)/g, (_, cls) => {
map[cls] = `${componentName}_${cls}__${componentName.length}${cls.length}x`;
return "." + map[cls];
});
const style = document.createElement("style");
style.textContent = scoped;
document.head.appendChild(style);
return map;
}
const cardStyles = cssModule("CourseCard", ".title { color: #1d4ed8; font-size: 22px; }");
const noticeStyles = cssModule("Notice", ".title { color: #b91c1c; font-size: 16px; text-transform: uppercase; }");
console.log("CourseCard title class:", cardStyles.title);
console.log("Notice title class:", noticeStyles.title);
createRoot(document.getElementById("root")).render(
<>
<h2 className={cardStyles.title}>React Basics</h2>
<p className={noticeStyles.title}>Admissions close on 30 November</p>
</>
);CourseCard title class: CourseCard_title__105x Notice title class: Notice_title__65x
Next.js uses a real hash instead of these simple numbers, but the result is the same: one blue title and one red title, from two classes both written as .title.
Tailwind CSS v4
Tailwind gives you small utility classes such as p-4, rounded-lg and text-amber-600 that you combine in className. It is chosen by default in create-next-app. To add it to an existing project, the Next.js docs describe this setup:
npm install -D tailwindcss @tailwindcss/postcss// postcss.config.mjs
export default {
plugins: {
"@tailwindcss/postcss": {},
},
};/* app/globals.css */
@import "tailwindcss";Projects generated by create-next-app 16.4 may instead connect Tailwind through a Turbopack rule in next.config.ts (using the @tailwindcss/turbopack package); either way, globals.css starts with @import "tailwindcss" and you use the same classes. There is no tailwind.config.js in v4 by default: you customise the theme in CSS with @theme. The Tailwind tutorial covers the classes in depth.
This runnable page uses Tailwind's browser build, which is meant only for learning and quick prototypes; real projects use the setup above:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Tailwind course card</title>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4.3.3"></script>
</head>
<body class="bg-gray-50 p-6">
<article class="max-w-sm rounded-xl border border-gray-200 bg-white p-5 shadow-sm">
<p class="text-xs font-semibold uppercase tracking-wide text-amber-600">Weekend batch</p>
<h2 class="mt-1 text-xl font-bold text-gray-900">Next.js Basics</h2>
<p class="mt-2 text-gray-600">App Router, Server Components and Server Actions in 6 weeks.</p>
<div class="mt-4 flex items-center justify-between">
<span class="text-lg font-semibold">₹1,499</span>
<button class="rounded-lg bg-gray-900 px-4 py-2 text-white hover:bg-gray-700">Enrol</button>
</div>
</article>
</body>
</html>In Next.js you write the same classes on JSX elements with className instead of class. For conditional classes, build the string in JavaScript: className={`rounded px-3 ${active ? "bg-gray-900 text-white" : "bg-white"}`}.
Sass and CSS-in-JS
- Sass: install it with
npm install -D sassand use.scssfiles, including.module.scss. Next.js compiles them automatically. - CSS-in-JS libraries (which build styles from JavaScript at runtime) only work in Client Components, and some need extra setup to work with streaming. Because Server Components cannot use them, most new App Router projects prefer CSS Modules or Tailwind, which produce plain CSS files.
How CSS order works
In production, Next.js merges and splits your CSS into small, minified files per route, and CSS loads even without JavaScript. The order of rules follows the order of your imports. Keep global and Tailwind imports in the root layout, and import component CSS inside the components that use it. If something looks different after next build, check import order, because development can order CSS slightly differently.
Common mistakes
- Writing
classinstead ofclassName. JSX usesclassName. - Global class names for components. A
.cardrule inglobals.cssaffects every.cardin the app. Use a CSS Module. - Using a CSS-in-JS library in a Server Component. It needs
"use client", which spreads client code. Prefer CSS Modules or Tailwind. - Building Tailwind class names from pieces.
bg-${colour}-500cannot be detected by Tailwind's scanner. Write full class names such asbg-red-500.
Key points
- Import global CSS once in the root layout and keep it truly global.
.module.cssfiles give locally scoped class names through an importedstylesobject.- Tailwind CSS v4 is set up with
@import "tailwindcss"inglobals.cssplus the PostCSS (or Turbopack) integration. - Sass works after installing
sass; CSS-in-JS needs Client Components. - CSS order follows import order; check the production build.
Exercise
Create a Notice component for a school website with a CSS Module: a yellow box with a left border, a bold title and a smaller date line. Then create the same box using Tailwind classes only. Show both versions in one page.
Show solution
/* app/_components/Notice.module.css */
.box { background: #fef9c3; border-left: 4px solid #ca8a04; padding: 12px 16px; }
.title { font-weight: 700; margin: 0; }
.date { font-size: 0.85rem; color: #6b7280; margin: 4px 0 0; }// app/_components/Notice.jsx
import styles from "./Notice.module.css";
export function Notice({ title, date }) {
return (
<div className={styles.box}>
<p className={styles.title}>{title}</p>
<p className={styles.date}>{date}</p>
</div>
);
}
export function NoticeTw({ title, date }) {
return (
<div className="border-l-4 border-yellow-600 bg-yellow-100 px-4 py-3">
<p className="m-0 font-bold">{title}</p>
<p className="mt-1 text-sm text-gray-500">{date}</p>
</div>
);
}
// app/notices/page.jsx
import { Notice, NoticeTw } from "@/app/_components/Notice";
export default function NoticesPage() {
return (
<>
<Notice title="PTM on Saturday" date="15 November" />
<NoticeTw title="Sports day" date="22 November" />
</>
);
}Both boxes look alike. The CSS Module keeps styles in a separate file with scoped names, while the Tailwind version keeps them in the markup.
Quick Quiz
1. What makes a CSS file a CSS Module?
Files named *.module.css get locally scoped class names.
2. How do you use the class .title from Card.module.css in JSX?
The import gives an object whose properties are the generated class names.
3. What line goes at the top of globals.css for Tailwind CSS v4?
Tailwind v4 is loaded with a single CSS import. @tailwind base; is the older v3 syntax.
4. Why do many App Router projects avoid runtime CSS-in-JS libraries?
Runtime CSS-in-JS needs client-side JavaScript, which conflicts with keeping most components on the server.
Learn Next.js with a mentor
Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
