~/TechPurAI
~/tutorials/nextjs-from-scratch/styling-with-css-modules
intermediate·part 19 of 22·2 min read

Styling: CSS Modules and global styles

Updated Aug 16, 2026JavaScript · Next.js

Every component built across this series has rendered with no real styling at all. Next.js supports CSS in a few different ways out of the box — this part covers the two this project actually uses: CSS Modules, scoped per component, and globals.css for the small amount that genuinely needs to apply everywhere.

A CSS Module

css
/* app/components/PostCard.module.css */
.card {
  border: 1px solid #ddd;
  padding: 1.5rem;
  border-radius: 8px;
}

.title {
  font-size: 1.25rem;
  margin: 0 0 0.5rem;
}
tsx
// app/components/PostCard.tsx
import styles from "./PostCard.module.css";

export function PostCard({ title, excerpt }: { title: string; excerpt: string }) {
  return (
    <div className={styles.card}>
      <h2 className={styles.title}>{title}</h2>
      <p>{excerpt}</p>
    </div>
  );
}

The .module.css suffix is what tells Next.js's build process to treat this file as a CSS Module rather than a plain global stylesheet — every class name inside it gets compiled to something unique to this file (PostCard_card__a1b2c, roughly), which is what styles.card actually resolves to at runtime. Two different components can both define a class named .card in their own .module.css files with zero risk of one silently overriding the other's styles — the scoping is real, enforced by the build, not just a naming convention to remember.

globals.css: for what genuinely needs to be global

css
/* app/globals.css */
* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: system-ui, sans-serif;
  color: #1a1a1a;
}
tsx
// app/layout.tsx
import "./globals.css";

Imported exactly once, in the root layout — this is the one file in the project where an un-scoped body selector or a CSS reset genuinely belongs, since those specifically need to apply everywhere, not to one component. Reaching for globals.css for a component-specific style defeats the entire point of CSS Modules' scoping; the reverse mistake — trying to reset box-sizing globally from inside a .module.css file — technically works but is the wrong file to own something meant to apply everywhere.

Combining classes conditionally

tsx
import styles from "./PostCard.module.css";

<div className={`${styles.card} ${featured ? styles.featured : ""}`}>

Template-literal string concatenation is the plain-CSS-Modules way to combine a base class with a conditional one — no special API needed, since styles.card and styles.featured are both just plain strings once compiled.

Common mistake

Defining the same class name — .card, .title — directly in globals.css instead of a CSS Module, then being surprised when a completely unrelated component elsewhere in the app picks up styles never intended for it. That kind of accidental collision is exactly what CSS Modules exist to make structurally impossible.

Next: fonts — system-ui above works, but a custom typeface needs next/font to load without becoming a layout-shift or performance liability.

VK

Vijay Kumar

Founder of TechPurAI — writing hands-on tutorials and honest tool breakdowns.

LinkedIn ↗
← previous18. Middleware: redirects and edge logicnext →20. next/font: self-hosted, zero-layout-shift fonts