9. Styling
9.1 CSS Modules
CSS Modules offer a powerful solution for encapsulating styles within individual components, effectively preventing style collisions and promoting maintainable, scalable stylesheets. Next.js provides first-class support for CSS Modules out of the box, requiring zero configuration to integrate them into your project.
9.1.1 Scoped Styling
The core concept of CSS Modules revolves around automatically scoping class names. By convention, any CSS file named with the .module.css extension is treated as a CSS Module. When you define classes within such a file, the build system automatically transforms them into unique, hashed class names. This ensures that styles defined in one module do not inadvertently affect elements in other parts of your application, solving common issues related to global CSS specificity and naming conventions.
To use a CSS Module, you import it into your component file as a JavaScript object. This object then exposes your defined class names as properties, which you can apply to your JSX elements.
Usage Example:
Consider a Button.module.css file:
/* components/Button.module.css */
.primary {
background-color: #3b82f6;
color: white;
padding: 8px 16px;
border-radius: 4px;
border: none;
cursor: pointer;
}
.secondary {
background-color: #f3f4f6;
color: #1f2937;
padding: 8px 16px;
border-radius: 4px;
border: 1px solid #d1d5db;
cursor: pointer;
}
And its corresponding React component (e.g., Button.tsx):
// components/Button.tsx
import styles from './Button.module.css';
interface ButtonProps {
variant: 'primary' | 'secondary';
children: React.ReactNode;
onClick?: () => void;
}
export default function Button({ variant, children, onClick }: ButtonProps) {
return (
<button className={styles[variant]} onClick={onClick}>
{children}
</button>
);
}
// In another component (e.g., page.tsx)
import Button from '../components/Button';
export default function MyPage() {
return (
<div>
<Button variant="primary">Click Me</Button>
<Button variant="secondary">Learn More</Button>
</div>
);
}
When rendered, the output HTML will feature unique, hashed class names, such as:
<button class="Button_primary__a1b2c">Click Me</button>
<button class="Button_secondary__d3e4f">Learn More</button>
This automatic hashing prevents any potential naming collisions, ensuring that your component's styles remain isolated and predictable.
9.1.2 Global Styles
While CSS Modules excel at scoped styling, applications often require global styles for base typography, CSS resets, or defining global CSS variables (design tokens). In Next.js, global stylesheets are typically imported in your root layout file, usually app/layout.tsx (for the App Router) or pages/_app.tsx (for the Pages Router). Styles imported in this manner apply across all routes and components in your application.
Common Use Cases for Global Styles:
- CSS Resets: To normalize browser inconsistencies (e.g., Eric Meyer's reset or Normalize.css).
- Base Typography: Setting default font families, sizes, and line heights for headings and paragraphs.
- CSS Variables: Defining design tokens for colors, spacing, and shadows that can be reused throughout your application.
- Framework Directives: Integrating utility-first frameworks like Tailwind CSS by importing its base, components, and utilities layers.
Example: Defining Global Styles
In app/globals.css:
/* app/globals.css */
/* Tailwind CSS directives */
@tailwind base;
@tailwind components;
@tailwind utilities;
/* Custom global CSS variables (design tokens) */
:root {
--primary-color: #3b82f6;
--secondary-color: #6b7280;
--font-sans: 'Inter', sans-serif;
--spacing-md: 1rem;
}
/* Basic reset/base styles */
html, body {
padding: 0;
margin: 0;
font-family: var(--font-sans);
color: var(--secondary-color);
}
h1, h2, h3 {
color: var(--primary-color);
}
This globals.css file would then be imported into your app/layout.tsx:
// app/layout.tsx
import './globals.css'; // Import global styles here
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
This setup ensures that all pages and components inherit these foundational styles, providing a consistent visual base for your application.
9.2 Tailwind CSS
Tailwind CSS is a highly popular utility-first CSS framework that provides a vast collection of low-level utility classes. Instead of writing custom CSS, you compose UIs directly in your HTML (JSX) by applying these classes, which drastically speeds up development and helps maintain design consistency.
9.2.1 Installation
Setting up Tailwind CSS in a Next.js project is straightforward and involves a few simple steps. Next.js's built-in PostCSS support means no extra configuration is needed for the CSS processing itself.
Installation Steps:
- Install Tailwind CSS and its peer dependencies:
- Generate your
tailwind.config.tsandpostcss.config.jsfiles: - Configure your
tailwind.config.tsfile: - Add the Tailwind directives to your global CSS file:
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
The -p flag automatically creates a postcss.config.js file with tailwindcss and autoprefixer already configured.
You need to tell Tailwind CSS which files to scan for utility classes. This is crucial for its JIT (Just-In-Time) engine to generate only the CSS you actually use.
// tailwind.config.ts
import type { Config } from 'tailwindcss';
const config: Config = {
content: [
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
'./app/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {},
},
plugins: [],
};
export default config;
Open your app/globals.css file and add the @tailwind directives at the very top. These directives inject Tailwind's base styles, components, and utility classes into your project.
/* app/globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
/* Your other global styles go here */
With these steps completed, you can start using Tailwind CSS classes in your Next.js components.
9.2.2 Utility Classes
Tailwind's core philosophy is to provide single-purpose utility classes that you compose directly in your HTML (JSX). Each class typically corresponds to a single CSS property or a small group of related properties. This approach eliminates the need to invent class names, reduces the size of your CSS bundle by only including what's used, and makes styling highly consistent and predictable.
Examples of Utility Classes:
- Layout:
flex,grid,block,hidden,container - Flexbox/Grid Properties:
items-center,justify-between,gap-4,grid-cols-3 - Spacing:
p-4(padding),m-8(margin),px-2(horizontal padding),my-3(vertical margin) - Sizing:
w-full,h-16,max-w-md - Typography:
text-xl,font-bold,text-blue-500,text-center - Backgrounds:
bg-blue-500,bg-gray-100 - Borders:
border,border-gray-300,rounded-lg(border-radius) - Shadows:
shadow-md,shadow-xl
Interactive States and Responsiveness:
Tailwind also provides prefixes for applying styles based on different states or screen sizes:
- Responsive Prefixes:
sm:,md:,lg:,xl:,2xl:(e.g.,md:flexwill applydisplay: flex;only on medium screens and up). - State Prefixes:
hover:,focus:,active:,disabled:(e.g.,hover:bg-blue-600changes background on hover). - Dark Mode Prefixes:
dark:(e.g.,dark:bg-blue-900applies background in dark mode).
Example Component with Utility Classes:
<div className="flex items-center justify-between p-4 bg-blue-500 text-white rounded-lg shadow-lg hover:bg-blue-600 dark:bg-blue-900 dark:hover:bg-blue-800 transition-colors duration-200">
<h1 className="text-xl font-semibold">Welcome!</h1>
<button className="px-4 py-2 bg-white text-blue-500 rounded-md hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-white focus:ring-opacity-50">
Sign Out
</button>
</div>
This single HTML element demonstrates how multiple utility classes combine to create a visually rich and interactive component without writing any custom CSS.
9.2.3 Responsive Design
Tailwind CSS is built with a mobile-first philosophy, making responsive design incredibly intuitive. This means that by default, styles you apply without a prefix apply to all screen sizes. To apply styles specifically for larger screens, you use responsive prefixes like sm:, md:, lg:, etc.
Default Breakpoints:
sm:(Small screens, min-width: 640px)md:(Medium screens, min-width: 768px)lg:(Large screens, min-width: 1024px)xl:(Extra-large screens, min-width: 1280px)2xl:(2 Extra-large screens, min-width: 1536px)
These breakpoints are fully customizable in your tailwind.config.ts file if you need different values.
Example of Responsive Layout:
Consider a grid layout that changes based on screen size:
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
<div className="bg-gray-200 p-4 rounded-lg">Item 1</div>
<div className="bg-gray-200 p-4 rounded-lg">Item 2</div>
<div className="bg-gray-200 p-4 rounded-lg">Item 3</div>
<div className="bg-gray-200 p-4 rounded-lg">Item 4</div>
</div>
Explanation:
- On mobile (
base), the grid will have1column (grid-cols-1). - On medium screens (
md) and up, it will switch to2columns (md:grid-cols-2). - On large screens (
lg) and up, it will use3columns (lg:grid-cols-3). - On extra-large screens (
xl) and up, it will use4columns (xl:grid-cols-4).
This declarative approach makes it very clear how your layout will adapt across different devices.
9.2.4 Dark Mode
Tailwind CSS provides robust support for implementing dark mode, allowing you to define different styles based on the user's preferred color scheme or a manual toggle. There are two primary strategies for dark mode:
- Class Strategy (Default): This is the recommended approach. You add a
darkclass to the root HTML element (e.g.,<html class="dark">) to activate dark mode styles. Tailwind then applies styles prefixed withdark:only when this class is present. - Media Strategy: Tailwind can also detect the user's system preference for dark mode using the
@media (prefers-color-scheme: dark)CSS media query. This is configured intailwind.config.tsby settingdarkMode: 'media'.
The class strategy offers more control, as it allows users to manually toggle dark mode even if their system preference is different. It's also easier to persist this choice (e.g., in localStorage).
Enabling Class Strategy:
In tailwind.config.ts:
// tailwind.config.ts
const config: Config = {
// ...
darkMode: 'class', // Enable dark mode via class
// ...
};
Usage with dark: Prefix:
<div className="bg-white text-gray-900 dark:bg-gray-900 dark:text-white p-6 rounded-lg shadow-md">
<h2 className="text-xl font-bold mb-2">My Card</h2>
<p>This content adapts to light and dark mode.</p>
</div>
When the <html> element has the dark class, the background will be bg-gray-900 and text text-white. Otherwise, it will use bg-white and text-gray-900.
Toggling Dark Mode with JavaScript:
You can create a simple toggle button using JavaScript to add or remove the dark class from the <html> element. For a better user experience, this preference should be persisted, typically using localStorage.
// Example React component for a dark mode toggle
import { useState, useEffect } from 'react';
export default function DarkModeToggle() {
const [isDarkMode, setIsDarkMode] = useState(false);
useEffect(() => {
// Check localStorage for saved theme or system preference
const savedTheme = localStorage.getItem('theme');
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
if (savedTheme