NelsonLabs

Styling

Next.js supports several approaches to styling. The right choice depends on your project's complexity and your personal workflow. We'll cover the three most common: Global CSS, CSS Modules, and Tailwind CSS.

1. Global CSS

Global CSS works exactly like normal CSS — you write styles in a .css file and they apply to the whole app. Best for base resets, typography, and CSS variables.

app/globals.css
css
/* These styles apply to every page */
body {
  margin: 0;
  background: #000;
  color: white;
  font-family: -apple-system, sans-serif;
}

h1 { font-size: 2rem; }
a  { color: #0070f3; text-decoration: none; }
a:hover { text-decoration: underline; }
app/layout.jsx — import global CSS once here
jsx
import "./globals.css";  // Import in the root layout only — once is enough

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}

2. CSS Modules — scoped styles per component

ANALOGY

Real-world analogy: Uniform ID badges. Imagine two people are both wearing name badges that say 'Manager'. They're in different departments so there's no confusion. CSS Modules work the same way — you can name a class .button in two different component files without conflict. Next.js automatically makes them unique by adding a hash suffix.

components/Card.module.css
css
.card {
  background: #111;
  border: 1px solid #222;
  border-radius: 12px;
  padding: 1.5rem;
}

.title {
  font-size: 1.2rem;
  font-weight: bold;
  color: white;
}

/* These classes get auto-hashed at build time: .card_a8f9x → no collisions */
components/Card.jsx
jsx
import styles from "./Card.module.css";

export default function Card({ title, children }) {
  return (
    <div className={styles.card}>
      <h2 className={styles.title}>{title}</h2>
      {children}
    </div>
  );
}

3. Tailwind CSS (most popular in Next.js)

Tailwind is a utility-first CSS framework. Instead of writing separate CSS files, you apply small utility classes directly in your JSX. It sounds strange at first — but most developers who try it never go back.

All styling directly in JSX — no CSS file needed
jsx
export default function CourseCard({ title, level, description }) {
  return (
    <div className="bg-zinc-900 border border-zinc-800 rounded-xl p-6
                    hover:border-zinc-600 transition-colors">

      <span className="text-xs text-zinc-500 uppercase tracking-wider">
        {level}
      </span>

      <h3 className="text-xl font-bold text-white mt-2 mb-3">
        {title}
      </h3>

      <p className="text-zinc-400 text-sm leading-relaxed">
        {description}
      </p>

      <button className="mt-5 bg-blue-600 text-white text-sm font-semibold
                         px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors">
        Start Learning →
      </button>
    </div>
  );
}

NOTE

Tailwind quick reference. Spacing: p-4 (padding), m-4 (margin), gap-4 (flex/grid gap). Typography: text-lg, font-bold, text-zinc-400. Layout: flex, grid, items-center, justify-between. Colors: bg-blue-600, text-white, border-zinc-800. Hover: hover:bg-blue-700. Responsive: md:flex (applies at medium screen width and above).

The Image component — always use this for images

Use next/image instead of plain <img>
jsx
import Image from "next/image";

export default function CourseThumbnail() {
  return (
    <Image
      src="/nextjs-thumbnail.jpg"  // From the /public folder
      alt="Next.js course thumbnail"
      width={800}
      height={400}
      priority                      // Load this image eagerly (above the fold)
      className="rounded-xl w-full"
    />
  );
}

// Why use next/image over plain <img>?
// ✅ Automatic conversion to WebP (smaller file size)
// ✅ Lazy loads by default (won't load until the user scrolls to it)
// ✅ Prevents layout shift (reserves space before the image loads)
// ✅ Responsive sizes built in