Tailwind isn't the only option. Next.js supports CSS Modules, global CSS, and CSS-in-JS — usually all three at once.
Imported once from the root layout:
1// app/layout.tsx
2import "./globals.css";You can't import global CSS from any other component. This enforces a single global file.
For component-scoped styles, name the file *.module.css.
1/* Button.module.css */
2.button {
3 border-radius: 6px;
4 padding: 8px 16px;
5 background: black;
6 color: white;
7}
8
9.button:disabled {
10 opacity: 0.5;
11}1import styles from "./Button.module.css";
2
3export function Button({ children, disabled }: Props) {
4 return <button className={styles.button} disabled={disabled}>{children}</button>;
5}Class names are hashed at build (Button_button__a1b2c3) so collisions across files are impossible.
CSS-in-JS libraries that run at build time (vanilla-extract, Pigment CSS, StyleX) work in Server Components. Runtime CSS-in-JS (Emotion, styled-components) requires "use client" and a special setup — not recommended for new code.
1npm i -D sassThen import .scss files anywhere CSS works. CSS Modules with Sass: Button.module.scss.
In a real codebase, a common split:
| Style for | Tool |
|---|---|
| Layout & utilities | Tailwind |
| Component-specific tricky styles | CSS Modules |
| Single global file (resets, fonts, vars) | global.css |
| Marketing landing animations | Framer Motion |
Don't dogmatize one tool. Pick whichever takes the least code at the call site.
CSS variables work the best across all these tools:
1:root {
2 --color-fg: #0a0a0a;
3 --color-bg: #ffffff;
4}
5
6.dark {
7 --color-fg: #f5f5f5;
8 --color-bg: #0a0a0a;
9}1<div style={{ color: "var(--color-fg)", background: "var(--color-bg)" }} />Same approach works whether you're styling with CSS modules, Tailwind (bg-[var(--color-bg)]), or plain CSS.