Next.js CSS Modules
Introduction
CSS Modules are a powerful styling solution available out of the box in Next.js that allows you to write component-scoped CSS. Unlike traditional CSS where styles are global, CSS Modules automatically create unique class names, ensuring your styles don't clash with other components. This makes CSS Modules an excellent approach for component-based architecture in modern web applications.
In this tutorial, you'll learn how to implement CSS Modules in your Next.js project, understand their advantages, and see real-world examples of how they can streamline your styling workflow.
What are CSS Modules?
CSS Modules are regular CSS files that are locally scoped to components by default. When you import a CSS Module into a component, it creates unique class names, preventing style conflicts across your application.
Key Features of CSS Modules
- Local Scoping: Styles are scoped to specific components
- Name Uniqueness: Class names are automatically made unique
- No Runtime Overhead: Compiled at build time, not runtime
- Works with existing CSS: You can use all standard CSS features
- Built-in Support: No additional configuration needed in Next.js
Getting Started with CSS Modules in Next.js
Creating a CSS Module
To create a CSS Module in Next.js, simply name your CSS file with the .module.css
suffix.
For example, let's create a button component with its own styles:
- Create a CSS Module file named
Button.module.css
:
.button {
padding: 10px 15px;
background-color: #0070f3;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
transition: background-color 0.3s;
}
.button:hover {
background-color: #0051a2;
}
.buttonLarge {
font-size: 20px;
padding: 12px 20px;
}
- Use the CSS Module in your React component:
import styles from './Button.module.css';
export default function Button({ children, size }) {
return (
<button
className={size === 'large' ? `${styles.button} ${styles.buttonLarge}` : styles.button}
>
{children}
</button>
);
}
When Next.js builds your application, it will transform the class names to be unique, something like:
<button class="Button_button__2Xguw Button_buttonLarge__1A3Cv">
Click Me
</button>
Using CSS Modules with Multiple Classes
There are several ways to apply multiple CSS classes when using CSS Modules:
String Concatenation
<div className={`${styles.card} ${styles.highlighted}`}>
Content here
</div>
Conditional Classes
<div className={`${styles.card} ${isActive ? styles.active : ''}`}>
Content here
</div>
Using the classnames
Library
For more complex conditional class logic, the popular classnames
library works well with CSS Modules:
First, install the library:
npm install classnames
Then use it in your component:
import classNames from 'classnames';
import styles from './Card.module.css';
export default function Card({ isActive, isHighlighted, children }) {
return (
<div
className={classNames({
[styles.card]: true,
[styles.active]: isActive,
[styles.highlighted]: isHighlighted
})}
>
{children}
</div>
);
}
Global Styles with CSS Modules
While CSS Modules are primarily for local, component-scoped styles, you can also define global styles within them using the :global
syntax:
/* Button.module.css */
.button {
/* Local styles as before */
}
:global(.btn-wrapper) {
margin: 20px 0;
display: flex;
justify-content: center;
}
The .btn-wrapper
class will be available globally in your application.
Composing CSS Modules
CSS Modules support composition, allowing you to reuse styles across different modules:
/* colors.module.css */
.primary {
color: #0070f3;
}
.secondary {
color: #6c757d;
}
/* Button.module.css */
@value primary, secondary from './colors.module.css';
.buttonPrimary {
composes: button;
background-color: primary;
}
.buttonSecondary {
composes: button;
background-color: secondary;
}
Real-world Example: Building a Card Component
Let's build a reusable card component using CSS Modules:
- Create
Card.module.css
:
.card {
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
transition: transform 0.3s, box-shadow 0.3s;
}
.card:hover {
transform: translateY(-5px);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
}
.cardTitle {
font-size: 1.5rem;
margin-top: 0;
margin-bottom: 10px;
color: #333;
}
.cardContent {
color: #666;
line-height: 1.6;
}
.featured {
border-left: 5px solid #0070f3;
background-color: #f8f9fa;
}
- Create a Card component:
import styles from './Card.module.css';
import classNames from 'classnames';
export default function Card({ title, children, featured = false }) {
return (
<div className={classNames(styles.card, { [styles.featured]: featured })}>
<h2 className={styles.cardTitle}>{title}</h2>
<div className={styles.cardContent}>
{children}
</div>
</div>
);
}
- Use the Card component in a page:
import Card from '../components/Card';
export default function HomePage() {
return (
<div>
<h1>Welcome to My Next.js Site</h1>
<Card title="Getting Started with Next.js">
Next.js provides a great developer experience with features like
server-side rendering and static site generation.
</Card>
<Card title="Featured Tutorial" featured={true}>
Learn how to build scalable React applications with Next.js and
style them using CSS Modules.
</Card>
</div>
);
}
Debugging CSS Modules
When debugging CSS Modules in development mode, Next.js preserves the original class names by prefixing them with the component and file name. For example, .button
might become .Button_button__2Xguw
, making it easier to identify the source of styles in your browser's developer tools.
CSS Modules vs. Other Styling Solutions
Feature | CSS Modules | Inline Styles | Global CSS | Styled Components |
---|---|---|---|---|
Scoped styles | ✅ | ✅ | ❌ | ✅ |
No runtime overhead | ✅ | ✅ | ✅ | ❌ |
Pre-processor support | ✅ | ❌ | ✅ | ✅ |
Dynamic styling | ❌ | ✅ | ❌ | ✅ |
Built into Next.js | ✅ | ✅ | ✅ | ❌ |
Media queries | ✅ | ❌ | ✅ | ✅ |
Best Practices for CSS Modules in Next.js
- Keep modules small: Focus on component-specific styles
- Organize by component: Store CSS Modules alongside their components
- Use descriptive class names: While they'll be transformed, clear names help with development
- Avoid global styles: Use the global scope sparingly, if at all
- Leverage composition: Reuse styles through composition when possible
- Use utility libraries: Consider
classnames
for complex conditional styling logic
Summary
CSS Modules in Next.js provide an excellent solution for component-based styling with automatic scoping. They allow you to write regular CSS while avoiding the common pitfalls of global styles, such as class name collisions and specificity issues. With built-in support in Next.js, you can start using CSS Modules without any additional configuration.
Key takeaways:
- CSS Modules create unique class names automatically
- They work natively in Next.js with the
.module.css
naming convention - They're perfect for component-based architectures
- They offer a good balance between isolation and familiarity
Additional Resources and Exercises
Resources
- Official Next.js Documentation on CSS Modules
- CSS Modules GitHub Repository
- Using SASS with CSS Modules in Next.js
Exercises
-
Basic Implementation: Create a navbar component using CSS Modules with styles for both desktop and mobile views.
-
Theme Switching: Build a component that uses CSS Modules and allows switching between light and dark themes.
-
Component Library: Create a small component library (Button, Card, Alert) using CSS Modules with consistent styling.
-
Responsive Design: Implement a responsive grid system using CSS Modules with media queries.
-
Animation Practice: Add transitions and animations to components styled with CSS Modules.
By completing these exercises, you'll gain practical experience with CSS Modules in Next.js and develop a solid foundation for styling your applications.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)