| | |

Next.js 11 ▲ CSS Styling — Tailwind CSS, CSS Modules & global styles

⏱️ Estimated read: 13 min | |

Next.js supports three distinct approaches to styling, and they are not competing alternatives. Tailwind CSS provides utility classes for rapid component styling. CSS Modules provide locally scoped class names for component-specific CSS that Tailwind utilities cannot express. Global CSS provides application-wide base styles, resets, and the Tailwind import itself. The official recommendation is to use all three for their intended purposes: Tailwind for most styling, CSS Modules for custom scoped CSS when needed, and global styles for truly global concerns .

This chapter covers the setup and usage of each approach in the App Router: installing Tailwind CSS v4 and configuring the PostCSS plugin, creating and importing CSS Modules with automatic class name scoping, and importing global stylesheets in the root layout. It also covers the CSS ordering rules that determine which styles win when multiple files apply to the same element, and the cssChunking option that controls how styles are bundled in production.

Key point: Import global styles and Tailwind in the root layout (app/layout.tsx). Import CSS Modules in the component that uses them. The order of imports determines the order of the generated CSS, and later imports override earlier ones for conflicting properties.


Why these three approaches coexist

The utility-first problem. Writing custom CSS for every component creates a parallel stylesheet that must be maintained alongside the JSX. Tailwind inverts this: styles are applied through utility classes directly in the markup, and the generated CSS contains only the utilities that are actually used. This eliminates the naming problem, the dead CSS problem, and the context-switching between files .

The scoping problem. Tailwind utilities cover common patterns, but some components need styles that are not expressible as utilities: complex selectors, keyframe animations, or third-party library overrides. CSS Modules solve this by generating a unique class name for each locally defined class, so .error in one file cannot collide with .error in another . The component imports the styles object and references styles.error, and Next.js guarantees the class name is unique across the application.

The global problem. Some styles are not component-specific. A CSS reset, font-face declarations, CSS custom properties for theming, and the Tailwind import itself must apply to every route. These belong in a global stylesheet imported in the root layout. Importing them in a component would apply them only when that component renders, which is the wrong scope for truly global CSS .


a. Tailwind CSS v4 setup

Tailwind CSS v4 uses a CSS-first configuration model. There is no tailwind.config.js by default. The framework is imported with a single @import "tailwindcss" in the global stylesheet, and the PostCSS plugin processes it during the build.

Install the packages:

npm install -D tailwindcss @tailwindcss/postcss

Add the PostCSS plugin to postcss.config.mjs:

// postcss.config.mjs
export default {
  plugins: {
    '@tailwindcss/postcss': {},
  },
}

Import Tailwind in the global CSS file:

/* app/globals.css */
@import 'tailwindcss';

Import the global CSS file in the root layout:

// app/layout.tsx
import './globals.css'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}

After this setup, Tailwind utility classes are available in every component. The create-next-app scaffold with the --tailwind flag performs these steps automatically .

A common verification step is to check that the PostCSS plugin is configured and that globals.css contains the import. If Tailwind classes are not applied, the most common causes are a missing postcss.config.mjs entry or a globals.css file that is not imported in the root layout .


b. CSS Modules

A CSS Module is a file with the .module.css extension. Next.js processes it and generates a unique class name for each class defined in the file. The component imports the styles object and references the class as a property.

/* app/blog/blog.module.css */
.blog {
  padding: 24px;
  max-width: 680px;
  margin: 0 auto;
}

.title {
  font-size: 2rem;
  font-weight: 700;
}
// app/blog/page.tsx
import styles from './blog.module.css'

export default function Page() {
  return (
    <main className={styles.blog}>
      <h1 className={styles.title}>Blog</h1>
    </main>
  )
}

The generated class name for .blog might be blog_blog__a1b2c, which is unique to that file. The same .blog class in another module file receives a different generated name, so there is no collision .

CSS Modules are the recommended approach for component-specific styles that Tailwind utilities cannot express. The official guidance is to prefer Tailwind for most styling and reach for CSS Modules when a component needs custom scoped CSS .

A consistent naming convention helps maintainability. The documentation recommends <name>.module.css over <name>.tsx for the module file, and importing the module only in the component that uses it .


c. Global CSS

Global CSS applies to every route. In the App Router, it can be imported in any layout, page, or component inside the app directory, but the recommendation is to import it in the root layout so that it applies to the entire application .

/* app/global.css */
body {
  padding: 20px 20px 60px;
  max-width: 680px;
  margin: 0 auto;
}
// app/layout.tsx
import './global.css'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}

The documentation notes a caveat about global styles in the App Router: because Next.js integrates with React’s built-in stylesheet support and Suspense, global stylesheets are not removed when navigating between routes. This can lead to style conflicts if multiple global stylesheets are imported in different segments. The recommendation is to use global styles for truly global CSS only, and to avoid importing different global stylesheets in different parts of the application .


d. CSS ordering and chunking

The order of CSS imports matters. Next.js determines the final CSS order by the order in which stylesheets are imported in the application code. If base-button.module.css is imported before page.module.css in the component tree, the rules from base-button come first, and page can override them .

To keep CSS ordering predictable, the documentation recommends:

  • Import a CSS file in only one JavaScript or TypeScript file.
  • Import global styles and Tailwind in the root of the application.
  • Prefer CSS Modules over global styles for component-level CSS.
  • Use a consistent naming convention for CSS Modules.
  • Turn off linters or formatters that automatically sort imports, because sorting imports can change the CSS order .

The cssChunking option in next.config.js controls how CSS is chunked in production. The default behavior merges CSS into fewer files to reduce network requests, but the option can be set to 'strict' if more granular chunking is needed .


Complete Example Session

// ============================================
// PART 1: TAILWIND SETUP IN GLOBALS.CSS
// ============================================
/* app/globals.css */
@import 'tailwindcss';

/* Tailwind v4 uses CSS-first configuration. */
/* No tailwind.config.js is required by default. */
// ============================================
// PART 2: POSTCSS CONFIG
// ============================================
// postcss.config.mjs
export default {
  plugins: {
    '@tailwindcss/postcss': {},
  },
}

// The plugin processes the @import "tailwindcss" directive.
// ============================================
// PART 3: ROOT LAYOUT IMPORTS GLOBAL CSS
// ============================================
// app/layout.tsx
import './globals.css'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}

// Global CSS and Tailwind apply to every route.
// ============================================
// PART 4: TAILWIND UTILITIES IN A COMPONENT
// ============================================
export default function Home() {
  return (
    <main className="flex min-h-screen flex-col items-center justify-center p-24">
      <h1 className="text-4xl font-bold">Hello, Next.js</h1>
      <p className="mt-4 text-gray-600">Styled with Tailwind utilities.</p>
    </main>
  )
}

// No CSS file is needed for this component.
// ============================================
// PART 5: CSS MODULE FILE
// ============================================
/* app/blog/blog.module.css */
.blog {
  padding: 24px;
  max-width: 680px;
  margin: 0 auto;
}

.title {
  font-size: 2rem;
  font-weight: 700;
}
// ============================================
// PART 6: CSS MODULE IMPORT
// ============================================
// app/blog/page.tsx
import styles from './blog.module.css'

export default function Page() {
  return (
    <main className={styles.blog}>
      <h1 className={styles.title}>Blog</h1>
    </main>
  )
}

// styles.blog and styles.title are unique class names.
// ============================================
// PART 7: COMBINING TAILWIND AND CSS MODULES
// ============================================
import styles from './card.module.css'

export function Card({ children }: { children: React.ReactNode }) {
  return (
    <div className={`${styles.card} rounded-lg shadow-md`}>
      {children}
    </div>
  )
}

// Tailwind utilities and module classes can coexist.
// ============================================
// PART 8: GLOBAL BASE STYLES
// ============================================
/* app/global.css */
body {
  padding: 20px 20px 60px;
  max-width: 680px;
  margin: 0 auto;
}

/* Imported in the root layout, applies to every route. */
// ============================================
// PART 9: CSS ORDERING
// ============================================
// base-button.tsx imports base-button.module.css
// page.tsx imports page.module.css
// base-button is imported before page
// → base-button CSS comes first
// → page CSS can override base-button CSS

// Import order in code determines CSS order.
// ============================================
// PART 10: CSS CHUNKING CONFIGURATION
// ============================================
// next.config.ts
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  experimental: {
    cssChunking: 'strict', // or 'loose' (default)
  },
}

export default nextConfig

// Controls how CSS is split in production.

The ten parts covered Tailwind setup, PostCSS configuration, root layout imports, Tailwind utilities in a component, a CSS Module file, CSS Module import, combining Tailwind and modules, global base styles, CSS ordering, and chunking configuration.


Quick Reference

Styling approaches

ApproachFile extensionScopeImport location
Tailwind CSS.css (with @import "tailwindcss")Global utilitiesRoot layout
CSS Modules.module.cssLocal (unique class names)Component
Global CSS.cssGlobalRoot layout

Tailwind setup commands

StepCommand or file
Installnpm install -D tailwindcss @tailwindcss/postcss
PostCSS configpostcss.config.mjs with @tailwindcss/postcss
Global import@import 'tailwindcss'; in app/globals.css
Root layoutimport './globals.css' in app/layout.tsx

CSS Modules syntax

ElementExample
FileComponent.module.css
Importimport styles from './Component.module.css'
UsageclassName={styles.className}
Generated nameUnique hash appended automatically

Ordering rules

RuleReason
Import CSS in one filePredictable order
Global styles in root layoutApply to all routes
Prefer modules over global for componentsAvoid conflicts
Disable import sortingSorting changes CSS order

Best Practices

✅ Do This:

// Import global CSS in the root layout
import './globals.css'                                    // ✅
// Use Tailwind for most styling
<div className="flex items-center p-4">                   // ✅
// Use CSS Modules for custom scoped styles
import styles from './card.module.css'                    // ✅
// Import CSS files in one entry file per component
// to keep order predictable                                 // ✅

❌ Don’t Do This:

// Import global CSS in a component
import './globals.css'  // in a page component             // ❌ (applies globally anyway)
// Use global class names in CSS Modules
// .error { } // without modules                             // ❌ (collision risk)
// Auto-sort imports with ESLint
// sort-imports may reorder CSS imports                     // ❌ (breaks CSS order)
// Use Tailwind utilities for everything
// Some patterns need custom CSS                             // ❌

Common Pitfalls

PitfallWhy It HappensFix
Tailwind classes not appliedPostCSS plugin or global import missingCheck postcss.config.mjs and globals.css
CSS Module class is undefinedWrong import path or typo in class nameVerify styles.className matches the file
Styles apply globally unexpectedlyRegular .css imported instead of .module.cssRename to .module.css
CSS order is wrongImports sorted or split across filesImport in one file; disable import sorting
Global styles conflictMultiple global stylesheets in different segmentsUse one global stylesheet in root layout

Real-World Examples

1. Tailwind utility layout

<main className="flex min-h-screen flex-col items-center justify-center p-24">

2. CSS Module file

.card { border-radius: 8px; padding: 16px; }

3. CSS Module import

import styles from './card.module.css'
<div className={styles.card} />

4. Combine Tailwind and module

<div className={`${styles.card} shadow-lg`} />

5. Global reset

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

6. Root layout import

import './globals.css'

7. Tailwind PostCSS config

export default { plugins: { '@tailwindcss/postcss': {} } }

8. Tailwind global import

@import 'tailwindcss';

9. Strict chunking

experimental: { cssChunking: 'strict' }

10. Verify CSS order

next build && inspect the generated CSS files

Visual

The three styling layers

┌─────────────────────────────────────────────────────────────┐
│  GLOBAL CSS (app/globals.css)                                │
│    - Tailwind import                                         │
│    - CSS reset                                               │
│    - Custom properties                                       │
│    - Base element styles                                     │
│    Imported in root layout                                   │
│    ↓                                                         │
│  TAILWIND UTILITIES (in JSX)                                 │
│    - flex, p-4, text-lg, bg-blue-500                         │
│    - Applied via className                                   │
│    ↓                                                         │
│  CSS MODULES (Component.module.css)                          │
│    - Custom scoped styles                                    │
│    - Complex selectors                                       │
│    - Animations                                              │
│    Imported in the component                                 │
└─────────────────────────────────────────────────────────────┘

CSS Module scoping

┌─────────────────────────────────────────────────────────────┐
│  WITHOUT CSS MODULES                                         │
│                                                              │
│  a.css: .error { color: red; }                               │
│  b.css: .error { color: blue; }                              │
│  Result: unpredictable, depends on load order                │
│                                                              │
│  WITH CSS MODULES                                            │
│                                                              │
│  a.module.css: .error { color: red; }                        │
│    → .error_a1b2c { color: red; }                            │
│                                                              │
│  b.module.css: .error { color: blue; }                       │
│    → .error_d4e5f { color: blue; }                           │
│                                                              │
│  No collision. Each class is unique.                         │
└─────────────────────────────────────────────────────────────┘

CSS import order

┌─────────────────────────────────────────────────────────────┐
│  IMPORT ORDER DETERMINES CSS ORDER                           │
│                                                              │
│  layout.tsx: import './globals.css'                          │
│  page.tsx: import styles from './page.module.css'            │
│                                                              │
│  Generated CSS order:                                        │
│    1. globals.css (Tailwind base)                            │
│    2. page.module.css (page-specific)                        │
│                                                              │
│  page.module.css can override globals.                       │
│  Later imports win for conflicting properties.               │
└─────────────────────────────────────────────────────────────┘

Tailwind v4 setup flow

┌─────────────────────────────────────────────────────────────┐
│  TAILWIND V4 SETUP                                           │
│                                                              │
│  npm install -D tailwindcss @tailwindcss/postcss             │
│       │                                                      │
│       ▼                                                      │
│  postcss.config.mjs:                                         │
│    plugins: { '@tailwindcss/postcss': {} }                   │
│       │                                                      │
│       ▼                                                      │
│  app/globals.css:                                            │
│    @import 'tailwindcss';                                    │
│       │                                                      │
│       ▼                                                      │
│  app/layout.tsx:                                             │
│    import './globals.css'                                    │
│       │                                                      │
│       ▼                                                      │
│  Tailwind utilities available everywhere                     │
└─────────────────────────────────────────────────────────────┘

Summary

ItemValue
Tailwind import@import 'tailwindcss' in global CSS
Tailwind PostCSS plugin@tailwindcss/postcss
CSS Modules extension.module.css
CSS Modules scopingAutomatic unique class names
Global CSS locationRoot layout (app/layout.tsx)
CSS orderingDetermined by import order
Chunking controlexperimental.cssChunking
Recommended mixTailwind for most, Modules for custom, Global for base

Key takeaways:

  • Tailwind CSS v4 uses CSS-first configuration. There is no tailwind.config.js by default. The framework is imported with @import 'tailwindcss' in the global stylesheet, and the PostCSS plugin processes it during the build .
  • CSS Modules provide local scoping for component-specific styles. The .module.css extension triggers unique class name generation. The component imports the styles object and references classes as properties, eliminating collisions .
  • Global CSS belongs in the root layout. Importing it in a component would apply it globally anyway, because global stylesheets are not removed on navigation. Importing it in the root layout makes the intent explicit and avoids duplication .
  • CSS order is import order. The final CSS is ordered by the sequence of imports in the application code. Auto-sorting imports with ESLint or a formatter can change the order and break style overrides. Import CSS in one file per component and disable import sorting .
  • The three approaches are complementary. Use Tailwind for utility-first component styling, CSS Modules for custom scoped CSS that Tailwind cannot express, and global CSS for resets, theming, and the Tailwind import itself. The documentation explicitly recommends this combination .
  • The cssChunking option controls production CSS bundling. The default merges CSS to reduce requests, while 'strict' provides more granular chunks. This is an optimization decision, not a correctness one.

Remember: Styling in Next.js is a layering problem, not a single-tool decision. Tailwind handles the majority of component styling through utility classes. CSS Modules handle the cases where custom scoped CSS is needed. Global CSS handles the base styles and the Tailwind import. The three coexist, and the import order determines which rules override which. Get the root layout import right for global styles, use .module.css for scoped styles, and disable import sorting so that the CSS order matches the intended cascade.

Was this helpful?


Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!