Exploring CSS Modules

Exploring CSS Modules

CSS Modules are a modern approach to styling web applications, offering a way to keep styles organized and conflict-free. By scoping styles locally, they address common issues faced with traditional CSS.

What are CSS Modules?

CSS Modules are specialized CSS files where each class name and animation name is scoped locally by default. Unlike traditional global CSS, CSS Modules keep styles contained within their respective components, preventing global conflicts and optimizing the final output.

In traditional CSS, a class name is applied across the entire document:


<h1 class="title">Sample Heading</h1>


.title {
color: blue;
}

CSS Modules take a different approach by scoping styles to their respective modules:


/* style.module.css */
.foo {
color: red;
}
.bar {
background-color: blue;
}


import styles from "./style.module.css";

function Component() {
return (
<div className={`${styles.foo} ${styles.bar}`}>Hello, world!</div>
);
}

During the build process, tools like Webpack transform the class names into unique identifiers:


<div class="_style__foo_3hRtp _style__bar_1aBCD">Hello, world!</div>


._style__foo_3hRtp {
color: red;
}
._style__bar_1aBCD {
background-color: blue;
}

CSS Modules also support composition, allowing one class to include styles from another:


.bg-blue {
background-color: blue;
}
.text-large {
composes: bg-blue;
font-size: 20px;
}


import styles from "./styles.module.css";

function AnotherComponent() {
return (
<div className={styles.text-large}>Component with Composed Styles</div>
);
}

Output:


<div class="_styles__text-large_YdCzx _styles__bg-blue_m3w8y">Component with Composed Styles</div>

Why Use CSS Modules?

CSS Modules offer several benefits for managing styles in modern web applications:

  1. Avoiding global scope issues: CSS Modules scope styles locally, reducing the risk of style conflicts.
  2. Organized and clean styles: Each component has its own associated styles, making it clear what styles are applied and where.
  3. Component-specific styles: Styles can be easily reused across different parts of the application without concern for style clashes.
  4. Alignment with component-based architecture: CSS Modules work well with modern JavaScript frameworks that emphasize component-based design.
  5. Easier maintenance: During component updates or bug fixes, you only need to address styles within the specific module.
  6. Optimized output: Tools like Webpack include only the necessary styles, reducing the overall size of the CSS output.
  7. Code reusability: The composes keyword allows for defining common styles in one module and reusing them across different components.

For example, in a project with various components like buttons, forms, and cards, CSS Modules ensure that styles defined for one component do not impact others:


/* button.module.css */
.button {
composes: reset from "./reset.module.css";
color: white;
background-color: blue;
}

/* card.module.css */
.card {
composes: reset from "./reset.module.css";
padding: 20px;
border: 1px solid #ccc;
}

These styles will remain unique to their respective components, facilitating a clean and maintainable codebase.

How to Implement CSS Modules

To implement CSS Modules using Webpack:

  1. Install Webpack:


npm install webpack webpack-cli --save-dev

  1. Create a webpack.config.js file:


module.exports = {
entry: './src/index.js',
output: {
path: `${__dirname}/dist`,
filename: 'bundle.js',
},
module: {
rules: [
{
test: /.module.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: true,
},
},
],
},
{
test: /.css$/,
exclude: /.module.css$/,
use: ['style-loader', 'css-loader'],
},
],
},
mode: 'development',
};

  1. Create a CSS module file:


/* src/styles.module.css */
.foo {
color: red;
}
.bar {
background-color: blue;
}

  1. Import and use the CSS module in a component:


// src/Component.jsx
import React from 'react';
import styles from './styles.module.css';

function Component() {
return (
<div className={`${styles.foo} ${styles.bar}`}>
Hello, world!
</div>
);
}

export default Component;

  1. Update package.json scripts:


"scripts": {
"build": "webpack"
}

  1. Run the build command:


npm run build

The output will have unique class names:


<div class="_styles__foo_3hRtp _styles__bar_1aBCD">
Hello, world!
</div>


._styles__foo_3hRtp {
color: red;
}
._styles__bar_1aBCD {
background-color: blue;
}

Additional features:

  1. Class Composition:


/* base.module.css */
.reset {
margin: 0;
padding: 0;
box-sizing: border-box;
}

/* button.module.css */
.primary {
composes: reset from './base.module.css';
background-color: blue;
color: white;
}

  1. Framework Compatibility: CSS Modules work with various frameworks like React, Angular, Vue, or plain JavaScript.
  2. Class Name Hashing: Ensures unique class names, preventing style conflicts across different CSS files.

A step-by-step visual guide showing the process of implementing CSS Modules in a web project

The Composes Keyword

The composes keyword in CSS Modules enables the composition of styles from different classes and CSS files. This feature allows for building complex styles by combining simpler, reusable ones, maintaining modularity within your CSS.

Consider a base CSS module file, base.module.css:

/* base.module.css */
.reset {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

We can then create another CSS module file, button.module.css, that composes styles from the reset class in base.module.css:

/* button.module.css */
.primary {
  composes: reset from './base.module.css';
  background-color: blue;
  color: white;
  padding: 10px 15px;
  border: none;
  border-radius: 4px;
}

In your JavaScript file, you can import and use these styles:

// src/Button.jsx
import React from 'react';
import styles from './button.module.css';

function Button() {
  return ;
}

export default Button;

The primary class in button.module.css composes the reset class from base.module.css, applying both the reset styles and the primary button styles to the button component.

Comparison with Sass

In Sass, similar functionality can be achieved using the @extend directive:

/* base.scss */
%reset {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

/* button.scss */
.primary {
  @extend %reset;
  background-color: blue;
  color: white;
  padding: 10px 15px;
  border: none;
  border-radius: 4px;
}

The main differences between CSS Modules' composes and Sass's @extend are:

  1. Scoping: CSS Modules ensure local scoping by default, while Sass's @extend operates within the global scope.
  2. Specificity: CSS Modules use hashed class names to avoid specificity conflicts, while Sass's @extend may increase specificity depending on the selectors used.
  3. Ease of Use: CSS Modules make it straightforward to import and compose styles from different files, maintaining clear boundaries between different parts of the application.

Further Composition Use Cases

The composes keyword can also import multiple styles:

/* typography.module.css */
.serif {
  font-family: Georgia, serif;
}
.large {
  font-size: 24px;
}

/* heading.module.css */
.heading {
  composes: serif from './typography.module.css';
  composes: large from './typography.module.css';
  font-weight: bold;
}

This approach keeps CSS modular and maintainable, allowing for the creation of complex, reusable styles without the pitfalls of global scope and specificity issues common in traditional CSS or some preprocessor features.

An illustration demonstrating the use of the composes keyword in CSS Modules, showing how styles can be combined and reused

CSS Modules in React

CSS Modules offer an effective way to manage and scope component styles in React applications. To use CSS Modules in a React project, create CSS files with the .module.css extension.

Here's how to import and use styles in a React component:

/* Button.module.css */
.button {
  background-color: blue;
  color: white;
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.button.primary {
  background-color: darkblue;
}
// Button.jsx
import React from 'react';
import styles from './Button.module.css';

function Button({ label, primary }) {
  return (
    
  );
}

export default Button;

Best Practices for CSS Modules in React

  1. File Naming and Structure: Name CSS files after their corresponding component and store them in the same directory.
  2. Component-Specific Classes: Use descriptive class names within your CSS Modules.
  3. Avoid Direct Tag Selectors: Use class selectors instead of tag selectors to maintain modularity.
  4. Composition Over Inheritance: Use the composes keyword to reuse styles across different components.
  5. Dynamic Class Names: Assign class names based on component state or props for enhanced interactivity.
  6. Consistency and Readability: Follow common naming patterns and avoid overly complex names.

By following these practices, you can create maintainable and conflict-free styles in React applications, enhancing the overall scalability of your project.

Comparing CSS Modules with BEM

CSS Modules and the Block Element Modifier (BEM) methodology are both techniques for organizing CSS code, but they approach the task differently.

BEM is a naming convention:

  • Block: A standalone component, like button.
  • Element: A component within the block, like button__icon.
  • Modifier: A specific state or variation, like button--primary.

Example with BEM:


<button class="button button--primary">
<span class="button__icon button__icon--large">Icon</span>
Click Me
</button>


.button {
/* Base styles for button */
}

.button--primary {
background-color: blue;
color: white;
}

.button__icon {
/* Base styles for icon */
}

.button__icon--large {
font-size: 20px;
}

CSS Modules, on the other hand, offer automatic class name generation and local scoping to prevent CSS conflicts.

Example using CSS Modules:

Button.module.css:


.button {
background-color: blue;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
}

.icon {
font-size: 20px;
margin-right: 5px;
}

Button.jsx:


import React from 'react';
import styles from './Button.module.css';

function Button({ label }) {
return (
<button className={styles.button}>
<span className={styles.icon}>Icon</span>
{label}
</button>
);
}

export default Button;

Advantages of CSS Modules
  1. Local Scoping: Styles are automatically scoped to their components, preventing style conflicts.
  2. Automatic Class Name Generation: Unique class names are generated during the build process, reducing naming conflicts.
  3. Code Cleanliness: CSS Modules result in cleaner, more readable code compared to BEM's verbose naming conventions.
  4. No Global Namespace Issues: Developers can use common class names across different components without conflicts.
  5. Dynamic Class Composition: CSS Modules allow for style composition using the composes keyword, enhancing code reusability.

While BEM provides a structured approach to CSS organization, CSS Modules offer a modern solution that simplifies CSS management in complex projects. The choice between BEM and CSS Modules depends on project requirements and development preferences.

Writio: Your AI content writer for articles.