CSS Modules
Contents
Scoping
Composition
Style sharing
Cool examples - single responsibility modules
Problem with CSS
Introducing CSS Modules
A 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