Barbarian Meets Coding
barbarianmeetscoding

WebDev, UX & a Pinch of Fantasy

8 minutes readcss

CSS modules

CSS modules logo

CSS modules are a CSS framework that allows you to write CSS classes and animations that are locally scoped by default and thus avoid the problems caused by global scoping in CSS.

Each CSS module is a regular CSS file that defines some styles using CSS classes:

/* colors.css */
.forest {
  color: green;
}

When writing a web application one consumes these CSS modules from a JavaScript file:

import colors from "./colors.css";
// import { forest } from "./colors.css";

element.innerHTML = '<p class="' + colors.forest + '">In the forest</p>';

This fits perfectly in React applications which are component-centric and naturally JS/JSX first:

import colors from "./colors.css";
// import { forest } from "./colors.css";

export theForest = () => {
  <p className={colors.forest}>
    In the forest
  </p>
}

A build step transforms the name of the class and ensures that it is unique across your web application.

<p class="colors__forest__32fds12">
In the forest
</p>

As a result one can write CSS classes as if they are locally scoped and not care about the possibility of overwritting CSS styles or other unintended consequences.

Why do we need CSS modules?

CSS has a series of design principles (global scope and the cascade) which have some fundamental problems in large web applications. As applications grow so does the amount of CSS needed to style a web application, over time it becomes harder and harder to maintain as CSS changes for a new feature may have unintended consequences. Overtime the web development community has come up with techniques and conventions to scale CSS like OOCSS, BEM or SMACSS which amongst other things focus on creating component based CSS (tied to a portion of the DOM, a component). All of the above are based on following naming conventions that are verbose and hard to enforce consistently.

CSS modules are another approach that aims to address these CSS limitations and are designed with component-based styles in mind:

  • They are locally scoped (so they ensure there’s no problems with style collisions or overrides)
    • no uncertainty about which components get affected when changing some CSS
    • no uncertainty about whether some CSS is unused or not
    • since CSS defined in a module is only used within a small scope we can use short CSS names and take advantage of the context provided by the CSS module file itself. e.g. button__warning is no longer necessary to prevent collisions between warning-like components since we already are in a button.module.css module with local scope we can rely on a .warning CSS class as a name.
  • The explicitly define dependencies. So you can very quickly see which styles are applied to a given component or template.
  • They provide a mechanism for composition that allows to:
    • reuse styles
    • define a higher level semantic language we can rely on to promote consistency and reuse of styles within an application. E.g. instead of everyone defining specific font styles using basic CSS rules we can composes: large from './typography.module.css' to have all components with a large font use the exact same CSS rules.

“CSS Modules why do we need them?” does a further break down on the necessity of CSS modules and “CSS modules: welcome to the future” goes even deeper comparing CSS modules with BEM and SASS.

This video “The case for CSS modules” describes the completely journey to CSS modules in the words of his co-creator Mark Dangleish:

CSS module features

  • Local scope by default: CSS in CSS modules is locally scoped. This is achieved because CSS modules are a form of CSS-in-JS that uses a build step to generate uniquely identified classes.
  • Composition: CSS classes in CSS modules can be composed to increase reuse and modularity
  • Values: Values can be defined in CSS modules and exported in a similar fashion to LESS and SASS variables
  • Global scope escape hatch: You can refer to CSS classes in global scope using :global
  • Theming: CSS modules can be used for theming by having a component obtain a set of styles (the theme) via prop instead of importing the CSS module from the component itself.

Composition

CSS modules support style composition through the use of the composes keyword. To have one style compose with another style you use composes like so:

/* button.module.css */

.normal {
  color: black;
  padding: 12px;
  background-color: white;
  /* other common button styles */
}

.warning {
  composes: normal;
  color: white;
  background-color: yellow;
  font-weight: bold;
}

We can then apply the normal and warning classes in our JavaScript components:

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

export deleteButton = () => {
  <button className={button.warning}>
    Delete
  </button>
}

As a result of the CSS module composition, the generated component includes both normal and warning classes:

<button class="button__normal button__warning">Delete</button>

It is also possible to compose classes from other CSS modules. This enables the possibility of writing a series of common higher level styles that can be reused within your application for higher consistency and reuse:

.heroButton {
  composes: large from './typography.module.css';
  composes: padding-medium from './layout.module.css';
  composes: subtle-shadow from './effects.module.css';
}

Values

CSS modules support values in a similar fashion to LESS and SASS variables:

/* color.module.css */
@value blue: #0c77f8;
@value red: #ff0000;
@value green: #aaf200;
/* buttons.module.css */
@value colors: "./colors.module.css";
@value blue, red, green from colors;

.button {
  color: blue;
  display: inline-block;
}

Values don’t need to be single CSS properties. For example, these are valid values too:

/* media.module.css */
@value laptop: only screen and (min-device-width : 768px) and (max-device-width : 1024px)
/* layout.module.css */
import { laptop } from './media.module.css';

@media laptop {
  /* laptop specific styles here. */
}

Theming

The natural way to work with CSS modules is that one designs self-containes components where the styles are scoped and contained within the component itself. This gives us all the benefits that we’ve described above on simplicity and maintainability. There’s one case where it makes sense to style a component from the outside, and that’s theming, i.e. the ability to have a component be able to have multiple styles or themes. CSS modules are really good at this because at the end of the day they are a collection of classes and rules that can be passed to a component via props making it a really good fit for theming in React applications:

/* button/light.module.css */
.button { 
  background: white;
  color: black;
}
/* button/dark.module.css */
.button { 
  background: black;
  color: white;
}
/* button.js */
export Button = ({theme}) => {
  <button className={theme.button}>{children}</button>
}
/* button-example.js */
import light from "button/light.css";
import dark from "button/dark.css";
import {Button} from "button/button.js";

export MyApp = () => {
  <Button theme={light} />
}

Global styles in CSS modules

To create global styles in CSS modules use the :global switch:

:global(.my-global-class) { }
@keyframes :global(my-global-animation) {}

To refer to global styles in CSS modules use :global like so:

.localA :global .global-b .global-c :local(.localD.localE) .global-d {}

To use a global class name when composing styles:

.myClass {
  composes: global-class-name from global;
}

Using CSS modules

All of the React-based projects below have built-in support for CSS modules:

If you use a different React framework you can enable CSS modules by relying on Webpack and configuring the css-loader to use CSS modules. For server-side rendering you can rely on PostCSS and the postcss-modules plugin.

The get started guide from the CSS modules documentation provides additional guidance to enable CSS modules in different toolchains.

Resources

For more information about CSS modules refer to the CSS modules documentation. For a really nice discussion on the advantages of using CSS modules with a lots of interesting use cases, comparisons with BEM and SASS and writing component based semantic CSS read CSS modules: welcome to the future by Gled Maddern.

After that you can dive deeper by reading these additional resources:


Jaime González García

Written by Jaime González García , dad, husband, software engineer, ux designer, amateur pixel artist, tinkerer and master of the arcane arts. You can also find him on Twitter jabbering about random stuff.Jaime González García