CSS Modules

Contents

Scoping

Composition

Style sharing

Cool examples - single responsibility modules 

Problem with CSS

JS styles in React:

React Style

jsxstyle

Radium

Introducing CSS Modules

CSS Module is a CSS file in which all class names and animation names are scoped locally by default.

 

CSS Modules compile to a low-level interchange format called ICSS or Interoperable CSS, but are written like normal CSS files

 

When importing the CSS Module from a JS Module, it exports an object with all mappings from local names to global names

Scoping

Classic CSS BEM way

/* components/submit-button.css */
.Button { /* all styles for Normal */ }
.Button--disabled { /* overrides for Disabled */ }
.Button--error { /* overrides for Error */ }
.Button--in-progress { /* overrides for In Progress */
<!-- Apply styles to button -->
<button class="Button Button--in-progress">Processing...</button>

Scoping

Scoping - CSS Modules way

/* components/submit-button.css */
.normal { /* all styles for Normal */ }
.disabled { /* all styles for Disabled */ }
.error { /* all styles for Error */ }
.inProgress { /* all styles for In Progress */
/* components/submit-button.js */
import styles from './submit-button.css';

buttonElem.outerHTML = `<button class=${styles.normal}>Submit</button>`

Scoping

Scoping - The result

<button class="components_submit_button__normal__abc5436">
  Processing...
</button>

Scoping

Scoping - Best practice

/* Don't do this */
`class=${[styles.normal, styles['in-progress']].join(" ")}`

/* Using a single name makes a big difference */
`class=${styles['in-progress']}`

/* camelCase makes it even better */
`class=${styles.inProgress}`

Composition

SASS

.Button--common { /* font-sizes, padding, border-radius */ }
.Button--normal {
  @extends .Button--common;
  /* blue color, light blue background */
}
.Button--error {
  @extends .Button--common;
  /* red color, light red background */
}
.Button--common, .Button--normal, .Button--error {
  /* font-sizes, padding, border-radius */
}
.Button--normal {
  /* blue color, light blue background */
}
.Button--error {
  /* red color, light red background */
}
/* BEM Style */
innerHTML = `<button class="Button Button--in-progress">`

Composition

/* CSS Modules */
innerHTML = `<button class="${styles.inProgress}">`
.common {
  /* all the common styles you want */
}
.normal {
  composes: common;
  /* anything that only applies to Normal */
}
.disabled {
  composes: common;
  /* anything that only applies to Disabled */
}
.error {
  composes: common;
  /* anything that only applies to Error */
}
.inProgress {
  composes: common;
  /* anything that only applies to In Progress */
}

Composition

CSS Modules

.common { /* font-sizes, padding, border-radius */ }
.normal { composes: common; /* blue color, light blue background */ }
.error { composes: common; /* red color, light red background */ }
/* getos compiled to: */
.components_submit_button__common__abc5436 { /* font-sizes, padding, border-radius */ }
.components_submit_button__normal__def6547 { /* blue color, light blue background */ }
.components_submit_button__error__1638bcd { /* red color, light red background */ }
import styles from "./submit-button.css"

// returns:

styles: {
  common: "components_submit_button__common__abc5436",
  normal: "components_submit_button__common__abc5436 components_submit_button__normal__def6547",
  error: "components_submit_button__common__abc5436 components_submit_button__error__1638bcd"
} 
<button class="components_submit_button__common__abc5436 
               components_submit_button__normal__def6547">
  Submit
</button>

Share styles

/* colors.css */
.primary {
  color: #720;
}
.secondary {
  color: #777;
}
/* other helper classes... */
/* submit-button.css */
.common { /* font-sizes, padding, border-radius */ }
.normal {
  composes: common;
  composes: primary from "../shared/colors.css";
}

Share styles

/* colors.css */
.primary {
  color: #720;
}
.secondary {
  color: #777;
}
/* other helper classes... */
/* colors.css - compiled */
.shared_colors__primary__fca929 {
  color: #720;
}
.shared_colors__secondary__acf292 {
  color: #777;
}

Share styles

/* submit-button.css - compiled*/
.components_submit_button__common__abc5436 { /* font-sizes, padding, border-radius */ }
.components_submit_button__normal__def6547 {}
/* submit-button.css */
.common { /* font-sizes, padding, border-radius */ }
.normal {
  composes: common;
  composes: primary from "../shared/colors.css";
}
<button class="shared_colors__primary__fca929
               components_submit_button__common__abc5436 
               components_submit_button__normal__def6547">
  Submit
</button>
/* components/submit-button.js */
import styles from './submit-button.css';

buttonElem.outerHTML = `<button class=${styles.normal}>Submit</button>`

Share styles

.heading {
  font-size: 24px;
  font-family: helvetica, arial, sans-serif;
  font-weight: 600;
}
.text {
  composes: heading from "shared/styles/typography.css";
  font-weight: 200;
  color: green;
}

Overriding:

Single responsibility modules

$large-font-size: 1.5rem;
$dark-text: rgba(0,0,0,0);
$padding-normal: 0.5rem;
@mixin subtle-shadow {
  box-shadow: 0 0 4px -2px;
}

.some_element {
  @include subtle-shadow;
  font-size: $large-font-size;
  color: $dark-text;
  padding: $padding-normal;
}

A SASS example:

Single responsibility modules

.element {
  composes: large from "./typography.css";
  composes: dark-text from "./colors.css";
  composes: padding-all-medium from "./layout.css";
  composes: subtle-shadow from "./effect.css";
}

With CSS Modules

Single responsibility modules

/* this short hand: */
.element {
  composes: padding-large margin-small from "./layout.css";
}

/* is equivalent to: */
.element {
  composes: padding-large from "./layout.css";
  composes: margin-small from "./layout.css";
}

Shortcuts:

.article {
  composes: flex vertical centered from "./layout.css";
}

.masthead {
  composes: serif bold 48pt centered from "./typography.css";
  composes: paragraph-margin-below from "./layout.css";
}

.body {
  composes: max720 paragraph-margin-below from "layout.css";
  composes: sans light paragraph-line-height from "./typography.css";
}

Extreme granularity:

Single responsibility modules

/* style.css */
.ball {
  composes: bounce from "shared/styles/animations.css";
  width: 40px;
  height: 40px;
  border-radius: 20px;
  background: rebeccapurple;
}

Animations - REACT example:

/* shared/styles/animations.css */
@keyframes bounce {
  33% { transform: translateY(-20px); }
  66% { transform: translateY(0px); }
}

.bounce {
  animation: bounce 1s infinite ease-in-out;
}

Single responsibility modules

import styles from './ScopedAnimations.css';

import React, { Component } from 'react';

export default class ScopedAnimations extends Component {

  render() {
    return (
      <div className={styles.ball} />
    );
  }

};

Animations - REACT example:

HQ demo

// webpack.config.js

config.loaders.push(
    ...
    { test: /\.css$/, loader: 'style-loader!css-loader?modules
            &localIdentName=[name]__[local]___[hash:base64:5]' })
    ...);

CSS Modules

By Andrei Antal

CSS Modules

  • 1,534