CSS Modules have emerged as a powerful tool for managing styles in component-based framework like Next.js. By scoping styles locally to components, they solve common problems like global namespace conflicts and style leakage, making them a favorite for developers building scalable applications.
In this blog, we’ll explore what CSS Modules are, why they’re effective, and how to use them efficiently.
What are CSS Modules?
CSS Modules are CSS files where all class names and animation names are scoped locally by default. When you import a CSS Module into your JavaScript or TypeScript file, the class names are automatically transformed into unique identifiers.
Why Use CSS Modules?
- Encapsulation: Styles are tied to components, ensuring no unintentional impact on other parts of the app.
- Scalability: CSS Modules work well in large projects with many developers.
- Flexibility: Compatible with CSS preprocessors like Sass for advanced features.
- Easy Adoption: CSS Modules integrate seamlessly with tools like Webpack and frameworks like Next.js.
Getting Started with CSS Modules
1. Setting Up CSS Modules in Next.js
Next.js supports CSS Modules out of the box. Create a .module.css
file, and you’re ready to go.
1. Example Directory Structure:
components/
├── Button/
│ ├── Button.module.css
│ ├── Button.js
2. Writing Styles
In the Button.module.css
file:
/* Button.module.css */ .button { padding: 10px 20px; font-size: 16px; border: none; border-radius: 5px; cursor: pointer; } .primary { background-color: #0070f3; color: white; } .secondary { background-color: #eaeaea; color: #333; }
Button.js
import React from 'react' import styles from './Button.module.css'; const Button = ({ label, variant = 'primary' }) => { return ( <button className={`${styles.button} ${styles[variant]}`}>{label}</button> ) } export default Button
This approach dynamically applies the primary
or secondary
class based on the variant
prop.
page.js
import Button from "./Button/Button"; export default function Home() { return ( <> <Button label="Click Me"/> </> ); }
Here are examples demonstrating best practices and how to use CSS Modules effectively:
1. Basic Usage of CSS Modules
Create a CSS Module file named Button.module.css
:
/* Button.module.css */ .button { background-color: #0070f3; color: white; padding: 10px 20px; border-radius: 5px; border: none; cursor: pointer; transition: background-color 0.3s ease; } .button:hover { background-color: #005bb5; }
Then, import and use the styles in a component:
Button.js
import React from "react"; import styles from "./Button.module.css"; const Button = () => { return ( <> <button type="button" className={styles.button}> Click Me </button> </> ); }; export default Button;
In this example, .button
is scoped only to the Button component, so you don’t have to worry about style conflicts with other buttons.
2. Scoped Styling for Nested Elements
CSS Modules can also style nested elements within a component:
Card.module.css
/* Card.module.css */ .card { border: 1px solid #ddd; padding: 20px; border-radius: 8px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); } .cardHeader { font-weight: bold; font-size: 1.2em; } .cardContent { margin-top: 10px; }
Card.js
import React from 'react' import styles from "./Card.module.css"; const Card = ({ title, content }) => { return ( <div className={styles.card}> <div className={styles.cardHeader}>{title}</div> <div className={styles.cardContent}>{content}</div> </div> ); }; export default Card
page.js
import Card from "./components/Card"; export default function Home() { return ( <> <Card title="Card title" content="Card content" /> </> ); }
Here, cardHeader
and cardContent
are scoped to Card
, making it easy to maintain without affecting other components.
3. Using Global and Scoped Styles Together
For styles that should apply globally (like resets or fonts), you can use a global CSS file.
globals.css(Next.js):
/* globals.css */ * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: Arial, sans-serif; }
In pages.js, import the global styles:
import "./globals.css"; export default function Home() { return ( <> <h1>Hello World</h1> </> ); }
You can now use globals.css
alongside your CSS Modules, combining global resets with scoped styling in your components.
4. Passing and Using Props
In this example we will pass props to display a message and style a button.
Button.js
import React from 'react'; export default function Button({ label, color }) { return <button style={{ backgroundColor: color }}>{label}</button>; }
Here:
- The Button component takes two props: label (text displayed on the button) and color (background color).
- The values of label and color are set by the parent component.
In the parent component:
page.js
import React from 'react'; import Button from './Button'; export default function Home() { return ( <div> <Button label="Click Me" color="blue" /> <Button label="Submit" color="green" /> </div> ); }
Here, we render two Button components with different label and color values. This makes Button reusable and customizable.
5. Dynamic Styling with Conditional Classes
You can use classnames to apply multiple classes conditionally. Install it first:
npm install classnames
Then, use it like this:
Status.module.css
.success { color: green; } .error { color: red; }
StatusMessage.js
import React from 'react' import styles from "./Status.module.css"; import classNames from "classnames"; const StatusMessage = ({ status }) => { const statusClass = classNames({ [styles.success]: status === "success", [styles.error]: status === "error", }); return <p className={statusClass}>This is a {status} message</p>; }; export default StatusMessage
This applies either the success or error class based on the status prop.
6. Animations and Transitions
You can add animations and transitions within CSS Modules too.
Notification.js
import React from "react"; import styles from "./Notification.module.css"; const Notification = ({ message }) => { return <div className={styles.notification}>{message}</div>; }; export default Notification;
Notification.module.css
.notification { padding: 10px; border: 1px solid #ccc; border-radius: 5px; background-color: #f0f0f0; opacity: 0; animation: fadeIn 0.5s forwards; } @keyframes fadeIn { to { opacity: 1; } }
The fadeIn animation will only apply to the Notification component’s .notification class, providing scoped animations.
When to Use CSS Modules
CSS Modules are ideal for:
- Component-specific styles in React or Next.js projects.
- Applications where maintainability and modularity are essential.
- Teams where multiple developers are working on the same codebase.
However, if your project requires extensive dynamic styling or theming, you might consider a CSS-in-JS library like Styled Components or Emotion.
Best Practices for CSS Modules
- Name Your Classes Meaningfully: Use clear, descriptive class names to make your styles easier to understand.
- Combine Global and Local Styles: Use CSS Modules for component-specific styles and a global CSS file for resets and shared styles like typography.
- Organize Your Files: Store the CSS Module file alongside the component it styles for better discoverability.
- Leverage Preprocessors: Combine CSS Modules with Sass or PostCSS for variables, nesting, and other advanced features.
Writing Better CSS with Extensions
- CSS Modules (by Clinyong): Provides autocomplete and syntax highlighting specifically for CSS Modules, which is great for quickly writing and referencing classes.
- CSS Peek: This extension lets you “peek” and “go to definition” for class names in CSS Modules, improving navigation.
Conclusion
CSS Modules offer a straightforward, scalable way to style your applications, making them a great choice for developers seeking a balance between simplicity and modularity.
By embracing this approach, you can create well-organized, conflict-free styles that align perfectly with modern component-based development.
Are you already using CSS Modules in your projects? Share your experiences or questions in the comments below!
Add comment