Topic 08 / 13

Styling — CSS Modules, Tailwind & Fonts

~9 min read  //  Next.js Series  //  Coding India

Global CSS

Imported once in the root layout — resets, tokens, base typography:

// app/layout.js
import "./globals.css";
/* app/globals.css */
:root {
  --bg: #0A0A14;
  --accent: #6366F1;
}
body {
  background: var(--bg);
  font-family: var(--font-inter);     /* set by next/font below */
}

CSS Modules — Scoped by Default

Files named *.module.css generate unique class names — styles can’t leak between components:

/* components/Card.module.css */
.card {
  background: #111827;
  border-radius: 16px;
  padding: 2rem;
}
.card:hover { border-color: var(--accent); }
.title { font-weight: 700; }
import styles from "./Card.module.css";

export default function Card({ title, children }) {
  return (
    <div className={styles.card}>          {/* → "Card_card__x7Ab3" */}
      <h3 className={styles.title}>{title}</h3>
      {children}
    </div>
  );
}

Conditional classes: className={`${styles.card} ${featured ? styles.featured : ""}`} (or the tiny clsx package).

Tailwind CSS

The most popular choice in the Next ecosystem — utility classes straight in JSX, no separate files. create-next-app offers it during setup:

export default function Card({ title }) {
  return (
    <div className="rounded-2xl border border-gray-800 bg-gray-900 p-8
                    transition hover:border-indigo-500">
      <h3 className="text-lg font-bold">{title}</h3>
    </div>
  );
}

Trade-off in one line: Tailwind colocates styling with markup and eliminates naming; CSS Modules keep classic CSS with guaranteed scoping. Both are first-class — pick per project, not per file. (The CSS knowledge from the CSS series applies fully either way; Tailwind is CSS with shorter names.)

Fonts with next/font — No Layout Shift

Self-hosts Google fonts at build time — no external requests, no flash of fallback text:

// app/layout.js
import { Inter, JetBrains_Mono } from "next/font/google";

const inter = Inter({
  subsets: ["latin"],
  variable: "--font-inter",
});
const mono = JetBrains_Mono({
  subsets: ["latin"],
  variable: "--font-mono",
});

export default function RootLayout({ children }) {
  return (
    <html lang="en" className={`${inter.variable} ${mono.variable}`}>
      <body>{children}</body>
    </html>
  );
}

Now var(--font-inter) works in any CSS, and the font files ship from your own domain with optimal caching.

Conditional & Dynamic Styling Notes

  • Server components can compute class names from data — zero JS cost: className={post.featured ? "card featured" : "card"}.
  • Avoid styled-components/emotion in the App Router unless you must — runtime CSS-in-JS fights server components. CSS Modules and Tailwind are the supported happy paths.