Building your site styles
The mythical Hello world…, because no project can’t start without these two magic words
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>
But what happens when you let your imagination flies… and you becomes crazy styling your Web page/site/app:
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…
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:
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:
modules/ _color.scss _typography.scss partials/ _base.scss _navigation.scss
vendor/ _ico-moon.scss main.scss
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
_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
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
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
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
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…
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