---
title: "18 — Styling Next.js — Global CSS, Modules, Tailwind, Sass, CSS-in-JS"
uid: styling
tags: ["roadmap:nextjs", "css-modules", "css", "sass", "css-in-js", "tailwind", "nextjs"]
excerpt: "All five approaches coexist — global CSS, CSS Modules, Tailwind, Sass, CSS-in-JS — and the choice is which matches the project's conventions, not which is correct."
date: 2026-08-13T03:27:59+0000
source: https://www.aveshina.my.id/en/blog/styling
---

A styling section that lists five valid options used to read as indecision to me. The model that clicked: **all five approaches coexist — global CSS for app-wide rules, CSS Modules for locally-scoped styles, Tailwind for utility-first composition, Sass for preprocessing power, and CSS-in-JS for dynamic theming** [1][2][3][4][5]. The choice is less "which is correct" and more "which matches the project's conventions" — Tailwind is the modern default for new work.

## Global CSS — the app-wide baseline

Global CSS refers to styles applied across the entire application — a single stylesheet whose rules affect every page unless overridden by more specific rules [1]. In the App Router, a globals.css imported into the root layout establishes the baseline: resets, design tokens, base typography. Global CSS is the right place for things that should be truly universal and the wrong place for component-specific styles.

```
/* app/globals.css — imported once in the root layout */
@tailwind base;
@tailwind components;
@tailwind utilities;

:root { --color-bg: #ffffff; }
body { background: var(--color-bg); }
```

## CSS Modules — locally scoped by default

CSS Modules locally scope CSS by generating unique class names, so I can use the same class name in different files without collisions [2]. The scoping is automatic — the build turns .button in Button.module.css into something like .Button_button__a1b2c, guaranteeing it won't leak. This is the conventional choice when a project wants component-scoped styles without a utility framework.

```
// Button.tsx
import styles from './Button.module.css';
export function Button() {
  return <button className={styles.button}>Click</button>;
}
```

## Tailwind CSS — utility-first

Tailwind is a utility-first CSS framework: pre-defined utility classes composed directly in markup, instead of writing custom CSS rules [3]. The payoff is rapid UI development and consistent design without context-switching between markup and stylesheets. Tailwind has become the de facto default for new Next.js projects, and create-next-app offers it as a first-class option.

```
export function Card() {
  return <div className="rounded-lg border p-4 shadow-sm">…</div>;
}
```

The trade-off is the class-name density in markup, which some find noisy. The win is that the design system lives in the config and the utilities, not in ad-hoc CSS files that drift.

## Sass — preprocessing power

Sass extends CSS with variables, nesting, mixins, and functions, compiled down to standard CSS [4]. Next.js has built-in Sass support once the package is installed, accepting both .scss and .sass extensions, and component-level Sass via .module.scss. Sass earns its keep in large stylesheets where mixins and nesting reduce repetition; for most modern work, Tailwind and CSS custom properties have absorbed much of that need.

## CSS-in-JS — dynamic and co-located

CSS-in-JS writes styles in JavaScript files, using libraries that generate CSS at runtime or build time [5]. The benefits are component-level styling, dynamic theming driven by JS values, and easier CSS dependency management. In the App Router, the constraint matters: because Server Components don't run client-side JS, runtime CSS-in-JS doesn't work in them — the supported approaches are the zero-runtime/build-time ones (e.g., the version of styled-components or Panda CSS that extracts at build). CSS-in-JS is now a deliberate choice rather than the default it once was.

```figure
<svg viewBox="0 0 720 280" xmlns="http://www.w3.org/2000/svg" class="my-6 w-full max-w-2xl" role="img" aria-label="Five styling approaches on two axes: scope (global to component) and dynamism (static to runtime). Global CSS: global, static. CSS Modules: component, static. Tailwind: component (via utilities), static. Sass: either, static (compiled). CSS-in-JS: component, dynamic.">
  <g font-family="ui-sans-serif, system-ui, sans-serif" text-rendering="geometricPrecision">

    <!-- axes -->
    <line x1="60" y1="240" x2="680" y2="240" stroke="#64748b" stroke-width="1.5"/>
    <text x="370" y="266" font-size="10" fill="#64748b" text-anchor="middle">scope: global → component</text>
    <line x1="60" y1="40" x2="60" y2="240" stroke="#64748b" stroke-width="1.5"/>
    <text x="30" y="140" font-size="10" fill="#64748b" text-anchor="middle" transform="rotate(-90 30 140)">dynamism: static → runtime</text>

    <!-- points -->
    <circle cx="120" cy="210" r="10" fill="#e0e7ff" stroke="#6366f1" stroke-width="2"/>
    <text x="120" y="232" font-size="10" font-weight="700" fill="#1e1b4b" text-anchor="middle">Global CSS</text>

    <circle cx="320" cy="180" r="10" fill="#dcfce7" stroke="#16a34a" stroke-width="2"/>
    <text x="320" y="202" font-size="10" font-weight="700" fill="#052e16" text-anchor="middle">CSS Modules</text>

    <circle cx="420" cy="160" r="10" fill="#fef9c3" stroke="#ca8a04" stroke-width="2"/>
    <text x="420" y="182" font-size="10" font-weight="700" fill="#422006" text-anchor="middle">Tailwind</text>

    <circle cx="520" cy="140" r="10" fill="#fce7f3" stroke="#db2777" stroke-width="2"/>
    <text x="520" y="132" font-size="10" font-weight="700" fill="#500724" text-anchor="middle">Sass</text>

    <circle cx="620" cy="90" r="10" fill="#fee2e2" stroke="#dc2626" stroke-width="2"/>
    <text x="620" y="82" font-size="10" font-weight="700" fill="#7f1d1d" text-anchor="middle">CSS-in-JS</text>

    <text x="370" y="58" font-size="11" font-style="italic" fill="#64748b" text-anchor="middle">all five coexist — pick by project convention</text>
  </g>
</svg>
```

## How I use this

Tailwind is the default for every new project — the utility-first model, the build-time extraction, and the first-class Next.js integration make it the path of least resistance. Global CSS holds the reset and design tokens. CSS Modules come in when a piece needs scoped styles that don't fit the utility mold. I largely skip Sass (Tailwind covers most of what Sass once solved) and avoid runtime CSS-in-JS in the App Router (Server Components don't run client JS). The principle: pick one primary approach and stick to it; the others are available when a specific need demands them.

## References

[1] Vercel, "Global CSS," Next.js Docs, 2024. [Online]. Available: [https://nextjs.org/docs/app/getting-started/css#global-css](https://nextjs.org/docs/app/getting-started/css#global-css)

[2] Vercel, "CSS Modules," Next.js Docs, 2024. [Online]. Available: [https://nextjs.org/docs/app/getting-started/css#css-modules](https://nextjs.org/docs/app/getting-started/css#css-modules)

[3] Vercel, "Tailwind CSS in Next.js," Next.js Docs, 2024. [Online]. Available: [https://nextjs.org/docs/app/getting-started/css#tailwind-css](https://nextjs.org/docs/app/getting-started/css#tailwind-css)

[4] Vercel, "How to use Sass in Next.js," Next.js Docs, 2024. [Online]. Available: [https://nextjs.org/docs/app/guides/sass](https://nextjs.org/docs/app/guides/sass)

[5] Vercel, "How to use CSS-in-JS libraries (App Router)," Next.js Docs, 2024. [Online]. Available: [https://nextjs.org/docs/app/guides/css-in-js](https://nextjs.org/docs/app/guides/css-in-js)

[6] Tailwind Labs, "Tailwind CSS," tailwindcss.com, 2024. [Online]. Available: [https://tailwindcss.com/](https://tailwindcss.com/)

```quiz
Q: CSS Modules solve which problem?
- Slow CSS parsing
- Class name collisions — they generate unique class names so the same class can be used in different files without conflict
correct: 1
explain: CSS Modules scope styles locally by generating unique class names at build time, preventing collisions across components.

Q: What's Tailwind CSS's defining approach?
- A component library like Material UI
- Utility-first — pre-defined utility classes composed directly in markup, rather than writing custom CSS
correct: 1
explain: Tailwind provides utility classes (rounded-lg, p-4, shadow-sm) composed in the markup. The design system lives in the config; the developer composes utilities.

Q: Why is runtime CSS-in-JS problematic in the App Router?
- It conflicts with Tailwind
- Server Components don't run client-side JS, so runtime CSS-in-JS doesn't work in them — only build-time/zero-runtime solutions are supported
correct: 1
explain: Because Server Components execute on the server without client JS, runtime stylesheet generation can't happen there. Build-time extraction (zero-runtime) approaches are the supported path.

Q: Global CSS in the App Router is typically imported where?
- In every page component
- Once, in the root layout (app/layout.tsx), via a globals.css file
correct: 1
explain: A single globals.css imported in the root layout establishes the app-wide baseline — resets, design tokens, base typography — applied across every page.

Q: For a brand-new Next.js project, what's the modern default styling choice?
- Runtime CSS-in-JS
- Tailwind CSS (offered as a first-class option by create-next-app)
correct: 1
explain: Tailwind has become the de facto default for new Next.js projects, with first-class create-next-app support and build-time extraction.
```
