Introduction to Styling in Next.js
Next.js supports multiple styling approaches, giving you flexibility to choose what works best for your project. From CSS Modules and global styles to modern solutions like Tailwind CSS and CSS-in-JS libraries, Next.js provides excellent support for all popular styling methods with built-in optimization.
Note: Next.js automatically optimizes CSS by minifying it in production, splitting code efficiently, and removing unused styles when using certain approaches like Tailwind CSS with its purge feature.
CSS Modules
CSS Modules are the recommended approach for component-level styles in Next.js. They automatically create locally scoped class names, preventing style conflicts:
Basic CSS Module Usage
// styles/Button.module.css
.button {
background-color: #0070f3;
color: white;
padding: 12px 24px;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 16px;
}
.button:hover {
background-color: #0051cc;
}
.primary {
background-color: #0070f3;
}
.secondary {
background-color: #666;
}
// app/components/Button.tsx
import styles from './Button.module.css';
export default function Button({ children, variant = 'primary' }) {
return (
<button className={`${styles.button} ${styles[variant]}`}>
{children}
</button>
);
}
Composing CSS Classes
// styles/Card.module.css
.card {
border: 1px solid #ddd;
border-radius: 8px;
padding: 20px;
}
.highlighted {
composes: card;
border-color: #0070f3;
box-shadow: 0 4px 12px rgba(0, 112, 243, 0.1);
}
.large {
composes: card;
padding: 32px;
font-size: 18px;
}
Dynamic Class Names
// app/components/Alert.tsx
import styles from './Alert.module.css';
export default function Alert({ type, message }) {
return (
<div className={styles[type]}>
{message}
</div>
);
}
Tip: CSS Modules generate unique class names like \"Button_button__abc123\" automatically, eliminating naming conflicts. This is perfect for component libraries and large applications.
Global Styles
Global styles apply to your entire application:
Creating Global Styles
// app/globals.css
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
:root {
--primary-color: #0070f3;
--secondary-color: #666;
--background: #ffffff;
--text-color: #000000;
--border-radius: 8px;
--spacing-sm: 8px;
--spacing-md: 16px;
--spacing-lg: 24px;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
color: var(--text-color);
background: var(--background);
line-height: 1.6;
}
a {
color: var(--primary-color);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
h1, h2, h3, h4, h5, h6 {
margin-bottom: 1rem;
line-height: 1.2;
}
Importing Global Styles
// app/layout.tsx
import './globals.css';
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
Warning: Global CSS can only be imported in the root layout (app/layout.tsx) in the App Router. Importing global styles in other components will cause an error.
Tailwind CSS
Tailwind CSS is a popular utility-first CSS framework with excellent Next.js support:
Installing Tailwind CSS
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
Configuring Tailwind
// tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
'./app/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {
colors: {
primary: '#0070f3',
secondary: '#666',
},
spacing: {
'128': '32rem',
},
fontFamily: {
sans: ['Inter', 'sans-serif'],
},
},
},
plugins: [],
}
Adding Tailwind Directives
// app/globals.css
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.btn-primary {
@apply bg-primary text-white px-6 py-3 rounded-lg hover:bg-blue-700 transition-colors;
}
.card {
@apply border border-gray-200 rounded-xl p-6 shadow-sm hover:shadow-md transition-shadow;
}
}
@layer utilities {
.text-balance {
text-wrap: balance;
}
}
Using Tailwind Classes
// app/components/Hero.tsx
export default function Hero() {
return (
<section className="bg-gradient-to-r from-blue-500 to-purple-600 py-20">
<div className="container mx-auto px-4">
<h1 className="text-4xl md:text-6xl font-bold text-white mb-6">
Welcome to Next.js
</h1>
<p className="text-xl text-white/90 mb-8 max-w-2xl">
Build production-ready applications with React and Next.js
</p>
<button className="btn-primary">
Get Started
</button>
</div>
</section>
);
}
Conditional Classes with clsx
npm install clsx
// app/components/Button.tsx
import clsx from 'clsx';
export default function Button({ variant, size, disabled, children }) {
return (
<button
className={clsx(
'font-semibold rounded-lg transition-colors',
{
'bg-blue-500 text-white hover:bg-blue-600': variant === 'primary',
'bg-gray-200 text-gray-800 hover:bg-gray-300': variant === 'secondary',
'px-4 py-2 text-sm': size === 'small',
'px-6 py-3 text-base': size === 'medium',
'px-8 py-4 text-lg': size === 'large',
'opacity-50 cursor-not-allowed': disabled,
}
)}
disabled={disabled}
>
{children}
</button>
);
}
Sass Support
Next.js has built-in support for Sass:
Installing Sass
npm install -D sass
Using Sass Files
// styles/Button.module.scss
$primary-color: #0070f3;
$border-radius: 8px;
.button {
background-color: $primary-color;
color: white;
padding: 12px 24px;
border: none;
border-radius: $border-radius;
cursor: pointer;
&:hover {
background-color: darken($primary-color, 10%);
}
&.large {
padding: 16px 32px;
font-size: 18px;
}
&.small {
padding: 8px 16px;
font-size: 14px;
}
}
Sass Variables and Mixins
// styles/variables.scss
$colors: (
primary: #0070f3,
secondary: #666,
success: #10b981,
danger: #ef4444,
);
$breakpoints: (
sm: 640px,
md: 768px,
lg: 1024px,
xl: 1280px,
);
@mixin respond-to($breakpoint) {
@media (min-width: map-get($breakpoints, $breakpoint)) {
@content;
}
}
@mixin flex-center {
display: flex;
align-items: center;
justify-content: center;
}
// styles/Card.module.scss
@import './variables';
.card {
padding: 20px;
border-radius: 8px;
background: white;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
@include respond-to(md) {
padding: 32px;
}
.header {
@include flex-center;
margin-bottom: 16px;
}
.title {
color: map-get($colors, primary);
font-size: 24px;
}
}
CSS-in-JS
Next.js supports popular CSS-in-JS libraries:
Styled-Components
npm install styled-components
npm install -D @types/styled-components
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
compiler: {
styledComponents: true,
},
};
module.exports = nextConfig;
// app/components/Button.tsx
'use client';
import styled from 'styled-components';
const StyledButton = styled.button<{ variant?: 'primary' | 'secondary' }>`
padding: 12px 24px;
border: none;
border-radius: 8px;
font-size: 16px;
cursor: pointer;
transition: all 0.2s;
background-color: ${props =>
props.variant === 'primary' ? '#0070f3' : '#666'
};
color: white;
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
`;
export default function Button({ children, variant = 'primary' }) {
return <StyledButton variant={variant}>{children}</StyledButton>;
}
Emotion
npm install @emotion/react @emotion/styled
// app/components/Card.tsx
'use client';
import styled from '@emotion/styled';
import { css } from '@emotion/react';
const CardContainer = styled.div`
padding: 24px;
border-radius: 12px;
background: white;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transition: box-shadow 0.2s;
&:hover {
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
}
`;
const titleStyle = css`
font-size: 24px;
font-weight: bold;
margin-bottom: 16px;
color: #0070f3;
`;
export default function Card({ title, children }) {
return (
<CardContainer>
<h2 css={titleStyle}>{title}</h2>
{children}
</CardContainer>
);
}
Note: CSS-in-JS libraries require 'use client' directive since they need JavaScript runtime. This means they only work in Client Components, not Server Components.
Inline Styles
Use inline styles for dynamic, component-specific styling:
// app/components/ProgressBar.tsx
export default function ProgressBar({ progress }) {
return (
<div style={{
width: '100%',
height: '20px',
backgroundColor: '#e0e0e0',
borderRadius: '10px',
overflow: 'hidden'
}}>
<div style={{
width: `${progress}%`,
height: '100%',
backgroundColor: '#0070f3',
transition: 'width 0.3s ease'
}} />
</div>
);
}
PostCSS Configuration
Customize PostCSS for advanced CSS processing:
// postcss.config.js
module.exports = {
plugins: {
'tailwindcss': {},
'autoprefixer': {},
'postcss-preset-env': {
stage: 3,
features: {
'nesting-rules': true,
},
},
...(process.env.NODE_ENV === 'production' ? { cssnano: {} } : {})
},
};
Modern CSS Features
Next.js supports modern CSS features out of the box:
CSS Variables (Custom Properties)
// app/globals.css
:root {
--primary: #0070f3;
--secondary: #666;
--radius: 8px;
}
.button {
background: var(--primary);
border-radius: var(--radius);
}
CSS Grid and Flexbox
// styles/Layout.module.css
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 24px;
}
.flex {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
}
Container Queries
// styles/Card.module.css
.container {
container-type: inline-size;
}
.card {
padding: 16px;
}
@container (min-width: 400px) {
.card {
padding: 32px;
font-size: 18px;
}
}
Best Practices
1. Choose the Right Styling Approach
- CSS Modules: Component-scoped styles, best for most cases
- Tailwind CSS: Rapid development, utility-first approach
- Sass: Complex design systems, need for variables/mixins
- CSS-in-JS: Dynamic styles, theme switching
2. Organize Styles Consistently
// Good structure
app/
├── components/
│ ├── Button/
│ │ ├── Button.tsx
│ │ └── Button.module.css
│ └── Card/
│ ├── Card.tsx
│ └── Card.module.css
└── styles/
├── globals.css
└── variables.css
3. Use CSS Variables for Theming
// app/globals.css
:root {
--color-primary: #0070f3;
--color-background: #ffffff;
--color-text: #000000;
}
[data-theme="dark"] {
--color-primary: #3291ff;
--color-background: #1a1a1a;
--color-text: #ffffff;
}
4. Optimize for Performance
- Use CSS Modules for automatic code splitting
- Enable Tailwind's purge to remove unused styles
- Minimize use of @import in CSS (use Next.js imports instead)
- Leverage CSS-in-JS only when needed (Client Components)
Exercise: Create a complete styling system for a dashboard with:
- Global styles with CSS variables for theming (light/dark mode)
- CSS Modules for component-specific styles (Sidebar, Navbar, Card)
- Tailwind utility classes for rapid layout prototyping
- Sass variables for a design token system
- Responsive design using mobile-first approach
Implement a theme switcher that toggles between light and dark modes.
Summary
Next.js offers comprehensive styling support for modern web applications. Key takeaways:
- CSS Modules provide scoped, conflict-free component styles
- Global styles define app-wide design tokens and resets
- Tailwind CSS enables rapid, utility-first development
- Sass adds powerful features like variables, mixins, and nesting
- CSS-in-JS (styled-components, Emotion) provides dynamic styling
- Inline styles work for component-specific dynamic values
- Next.js automatically optimizes and minifies all CSS
- Choose styling approach based on project needs and team preferences
- Modern CSS features (variables, Grid, Flexbox) are fully supported