Technology Blog

Themify your (S)CSS Modules

September 03, 2019 - 11 min read ⏱️⏱️

aurelio_gamero
aurelio gamero

Personalise your application with shiny themes combining CSS Modules and Bootstrap SASS


Building your site styles

The mythical ‘Hello world…’, because no project can’t start without these two magic words

Hello world

But anyone should recognize that any Web page with a bit of CSS, looks really better…

<style>
  * {
    font-family: "Open Sans", sans-serif;
  }
  header {
    padding: 16px;
    background-color: #0063be;
    color: #fff;
    font-size: medium;
  }
  h1 {
    text-align: center;
    color: #3c3c3c;
    font-style: italic;
  }
</style>

Hello world with CSS

But what happens when you let your imagination flies… and you becomes crazy styling your Web page/site/app:

CSS

You’ll end up more than a thousand lines of CSS, with deeply-nested, hard-to-read selectors with a bunch of !important here and there. You will fear to touch anything as a little change in one part can become a huge mess in a totally different section of the site…

CSS_mess

Meeting preprocessors

Fortunately, preprocessors arrived to make designers and developers life easier. And We could finally split and organize our CSS in more manageable pieces, and also use variables!

If you are not familiarized with preprocessors, take a look at this:
Introduction to CSS Preprocessors: SASS, LESS and Stylus

And again… becomes a nightmare. With pre-processors, the modularization of your stylesheets would end up within a heap of files very difficult to organize. Take a look at the next picture so you can feel lost:

SASS files

File patterns to the rescue!

The file patterns are a very useful tool to organize your stylesheets in a common way until you have a more personal structure. Here are the most famous:

Simple Structure:

modules/
    _color.scss
    _typography.scss

partials/
    _base.scss
    _navigation.scss

vendor/
    _ico-moon.scss

main.scss



The 7–1 Pattern:

base/
    _reset.scss
    _typography.scss
    _color.scss

components/
    _buttons.scss
    _navigation.scss
    _gallery.scss

layout/
    _header.scss
    _grid.scss
    _sidebar.scss

pages/
    _home.scss
    _about.scss
    _contact.scss

themes/
    _theme.scss
    _admin.scss

helpers/
    _variables.scss
    _functions.scss
    _mixins.scss

vendors/
    _bootstrap.scss
    _jquery-ui.scss

main.scss



SMACSS/BEM Architecture:

_base.scss
    is for vendor code and styles on base elements (html, body, ul, p, etc.)

_layout.scss
	is for macro layout styles

_modules.scss
	is for micro layout styles

_other.scss
	is for whatever doesn’t fit in first three

_shame.scss
	is for code you feel ashamed of and plan to improve



Others:

pets/
    _dog.scss
    _cat.scss

aliens/
    _predator.scss
    _alf.scss

food/
    _cookies.scss
    _pasta.scss

main.scss



But all this file patterns are just conventions and different developers may not respect them. Actually sometimes is hard to follow them: ever lost time deciding if a selector was a block or an element?

Introducing CSS Modules

Now we have more or less the basics for a medium large Web project (or, at least in my experience, these are the common elements).

But we are living in the time of continuous improvements and we have now a very useful technique which allows us managing the styles for each component much better than any previous pattern: the CSS Modules.

What are CSS Modules?

CSS files in which all class names and animation names are scoped locally by default

CSS Modules, during our build step, search through styles files imported, then look through the JavaScript we’ve written and make the classes accessible. Our build step would then process both these things into new, separate HTML and CSS files, with a new string of characters replacing both the HTML class and the CSS selector class: https://css-tricks.com/css-modules-part-1-need

What he is saying

In other words, we can isolate the styles of each component and keep the CSS and JS code of components together, without take care if we are importing properly the SASS file in our main file or overwriting in another part of the code. And If you remove components, styles will go with theme: no more zombie CSS left in your site!

Let’s tweak our ‘Hello World’ to use CSS Modules

Next steps explain how to transform the first simple example in a themed React project with CSS Modules:

webpack.config.js

module: {
   rules: [
     { test: /\.(js|jsx)$/,
       exclude: /node_modules/,
       loader: 'babel-loader'
     },
     { test: /\.scss$/,
       use: [
         MiniCssExtractPlugin.loader, { // Component
           loader: "css-loader", options: {
             modules: true, // Configuration
             sourceMap: true
           }
         },{
           loader: "sass-loader", options: {
             modules: true, // Configuration
             sourceMap: true
           }
         }]
     }]
},



index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width" />
    <title>Themes with 'styled-components' (PoC)</title>
    <!-- Extract CSS
    <style>
        * {
            font-family: 'Open Sans', sans-serif;
        }
        header {
            padding: 16px;
            background-color:#0063be;
            color: #fff;
            font-size: medium;
        }
        h1 {
            text-align: center;
            color: #3c3c3c;
            font-style: italic;
        }
    </style>
    -->
  </head>
  <body>
    <!-- Extract HTML
    <header>TSC (Poc)</header>
    <h1>Hello world...</h1>
    -->
    <div id="app"></div>
  </body>
</html>



src/mainLayout.scss

* {
  font-family: "Open Sans", sans-serif;
}

header {
  padding: 16px;
  background-color: #0063be;
  color: #fff;
  font-size: medium;
}

h1 {
  text-align: center;
  color: #3c3c3c;
  font-style: italic;
}



src/app.js

import React, { Fragment } from "react";
import ReactDOM from "react-dom";
import styles from "./mainLayout.scss";

class Hello extends React.Component {
  render() {
    return (
      <Fragment>
        <header>TSC (Poc)</header>
        <h1>Hello world...</h1>
      </Fragment>
    );
  }
}

ReactDOM.render(<Hello className={styles} />, document.getElementById("app"));

Building some components

building-blocks

Now we are going to refactor and extract some code to start to create components and start to see the real helpful of this technique:

src/app.js

  import React, { Fragment } from 'react';
  import ReactDOM from 'react-dom';
+ import { Header } from '.header/header';
+ import { Title } from '.title/title';
  import styles from './mainLayout.scss';

  class Hello extends React.Component {
    render() {
      return (
        <Fragment>
-         <header>TSC (Poc)</header>
-         <h1>Hello world...</h1>
+         <Header/>
+         <Title/>
        </Fragment>
      );
    }
  }

  ReactDOM.render(
    <Hello className={styles}/>,
    document.getElementById('app')
  );



src/mainLayout.scss

  * {
    font-family: 'Open Sans', sans-serif;
  }

  header {
-   padding: 16px;
-   background-color: #0063be;
-   color: #fff;
    font-size: medium;
  }

- h1 {
-   text-align: center;
-   color: #3c3c3c;
-   font-style: italic;
- }



src/header/header.jsx

import React from "react";
import styles from "./header.scss";

export const Header = () => {
  return <header className={styles.header}>TSC (Poc)</header>;
};



src/header/header.css

.header {
  padding: 16px;
  background-color: #0063be;
  color: #fff;
}



src/title/title.jsx

import React from "react";
import styles from "./title.scss";

export const Title = () => {
  return <h1 className={styles.title}>Hello world...</h1>;
};



src/title/title.css

.title {
  text-align: center;
  color: #3c3c3c;
  font-style: italic;
}

Building themes

Warhol-Marilyn

But now imagine that you want to have different themes for your site depending on brand, products or just the user choice: you need your Web page to use different colours palette.

Now you need to add in Webpack the sass-loader plugin and import the variables file which you’ll use for all the CSS Modules components:

data: '@import "variables";'

webpack.config.js

loader: "sass-loader", options: {
  modules: true,
  sourceMap: true,
  data: '@import "variables";',
  includePaths: [
    path.join(__dirname, 'src')
  ]
}



src/_variables.scss

$white: #ffffff;
$gray-base: #080808;
$gray-darker: #3c3c3c;
$gray-dark: #808080;
$gray: #cfcfcf;
$gray-light: #e3e3e3;
$gray-lighter: #e9ebee;
$brand-primary: #ec008c; // Theme color
$padding-base: 16px;



src/header/header.scss

.header {
  padding: $padding-base;
  background-color: $brand-primary;
  color: $white;
}



src/title/title.scss

.title {
  text-align: center;
  color: $gray-darker;
  font-style: italic;
}



A gift:
Looking for info for this article, I found this awesome project that helps to build faster all the style sheets you define: parallel-webpack

webpack.config.js

const createVariants = require('parallel-webpack').createVariants;

const variants = {
  themes: ['pink', 'blue', 'green']
};

function createConfig(options) {
  return {
    plugins: [
    ...
    new MiniCssExtractPlugin({
      filename: `${options.themes}-theme.css`})

    module: {
      loader: "sass-loader", options: {
      ...
      data: `@import "${options.themes}";`
    }
  }
}

module.exports = createVariants(variants, createConfig);

In this example, we are building three themes and we have to use the names of the main variables files we have in the project to build each version of the CSS file.

But if you want to forget about modify the configuration for every new style sheets you’ll add, just add a bit of code to automate it:

webpack.config.js

function getThemes() {
  const files = fs.readdirSync("./src/themes/");

  return files.map(fileName => fileName.replace(/(^_|.scss$)/g, ""));
}

const variants = {
  themes: getThemes()
};

Boost your style(s)!

Now it’s time to push your Web to the next level. Adding one of the most common CSS frameworks, you’ll build easily pre-styled components that, magically, will adapt his primary, secondary, etc. colours to the selected template.

But not everything it’s pink…, at the moment of writing this article, the glyphicons bootstrap import causes the failure of the build, so you should at least comment this line.

src/vendors/custom-bootstrap.scss

//Core variables and mixins
@import "~bootstrap-sass/assets/stylesheets/bootstrap/variables";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/mixins";

//Reset and dependencies
@import "~bootstrap-sass/assets/stylesheets/bootstrap/normalize";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/print";
//@import "~bootstrap-sass/assets/stylesheets/bootstrap/glyphicons";

//Core CSS
@import "~bootstrap-sass/assets/stylesheets/bootstrap/scaffolding";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/type";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/code";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/grid";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/tables";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/forms";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/buttons";

//Components
@import "~bootstrap-sass/assets/stylesheets/bootstrap/component-animations";
//@import "~bootstrap-sass/assets/stylesheets/bootstrap/dropdowns";
//@import "~bootstrap-sass/assets/stylesheets/bootstrap/button-groups";
//@import "~bootstrap-sass/assets/stylesheets/bootstrap/input-groups";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/navs";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/navbar";
//@import "~bootstrap-sass/assets/stylesheets/bootstrap/breadcrumbs";
//@import "~bootstrap-sass/assets/stylesheets/bootstrap/pagination";
//@import "~bootstrap-sass/assets/stylesheets/bootstrap/pager";
//@import "~bootstrap-sass/assets/stylesheets/bootstrap/labels";
...



src/mainLayout.scss

html {
  :global {
    @import "./vendors/custom-bootstrap.scss";
  }
}

* {
  font-family: "Open Sans", sans-serif;
}

header {
  font-size: medium;
}



index.html

- <button onclick="changeCss('pink')">Pink</button>
- <button onclick="changeCss('blue')">Blue</button>
- <button onclick="changeCss('green')">Green</button>

+ <div class="btn-group" role="group">
+     <button type="button" class="btn btn-default" onclick="changeCss('pink')">Pink</button>
+     <button type="button" class="btn btn-default" onclick="changeCss('blue')">Blue</button>
+     <button type="button" class="btn btn-default" onclick="changeCss('green')">Green</button>
+ </div>



title.jsx

- return (
-   <h1 className={styles.title}>Hello world...</h1>
- );
+ return (
+   <div className="panel panel-primary">
+     <div className="panel-heading">
+       <h3 className={`panel-title ${styles.title}`}>Hello world...</h3>
+     </div>
+     <div className="panel-body">Lorem ipsum dolor sit amet...</div>
+   </div>
+ );

Conclusion

Your perfect cocktail, shaken, not stirred…

shaken, not stirred

Pros Cons
- Isolate components (JS & CSS) - Needs a specific Webpack conf to build
- Don’t need specific Sass imports - Needs same configuration in different projects to have really shareable components
- Obfuscated CSS class names - Needs a JS template builder
- Works with any JS template builder

Thanks

To all the people who shared their knowledge and experiments to help and inspire the whole community.

My helpful references have been:
Keep a Changelog
Semantic Versioning
Muffin Man
webpack.js.org
Styled Components
CSS modules React App
Add SASS in React App
Webpack sass loader global variables file
Sass loader for webpack
Trivago parallel-webpack


https://github.com/lastminutedotcom/themed-css-modules


lastminute.com folks
Written by lastminute.com folks who are busy living. You should follow us on Twitter

The postings on this site are authors' opinions and experiences and do not necessarily represent the postings, strategies or opinions of lastminute.com group.