How to write Atomic (design) CSS with Sass in Angular2

The main goal of this post is to go over all the steps and some of the gotchas involved with writing atomic css. We’re going to use sass and angular2.

What is Atomic CSS?

This is technically not atomic-css, but rather css written in atomic web design. The former is just easier to write.

There are many different styles and design patterns for writing CSS, each with their own merits and pitfalls. Earlier this year I was introduced to the concept of Atomic CSS by one of the best lead developers I’ve had the pleasure of working with and I absolutely fell in love with it. Unfortunately he’s no longer with us. He’s in a better place… Google.

One of the main benefits of writing css atomically is that eventually you won’t have to write any more css (that’s the goal anyway). This is a bit of an over-exaggeration, but the idea is that you will get to a point where only very specific and highly customized elements will need any new css.

The basic concept of Atomic CSS is that you have one class that belongs to one property and value pair, these are called atoms.

.height20 {
height: 20px;
}

When you need a new height, you just add a new class for it.

.height30 {
height: 30px;
}

Boom. That’s it. You can even go a step further and abbreviate those classes to `h20` and `h30`.

Some people would argue against being terse in favor of being more semantic. This is a pretty opinionated topic, it’s up to you to make a choice here, but once you make a choice, stick to it! consistency is key.

This seems a bit too tedious. I don’t want to sit here and write out every single possible height that I’m going to use. That’ll be tens to hundreds of lines. I became a developer because I’m lazy, let’s see what we can do about this.

SASS/SCSS

It’s very simple to automate the generation of these classes with Sass.

// heights.scss
// This is our iteratable list of key/value pairs
$heights: (
6: 0.375rem,
10: 0.625rem,
11: 0.688rem,
18: 1.125rem
);
// We loop through our list and use the key as the name
// and the value is used as the val.
@each $name, $val in $heights {
.h#{$name} {
height: #{$val};
}
  .min-h#{$name} {
min-height: #{$val};
}
}
// heights.compiled.css
.h6 {
height: 0.375rem;
}
.h10 {
height: 0.625rem;
}
.h11 {
height: 0.688rem;
}
.h18 {
height: 1.125rem;
}
.min-h6 {
min-height: 0.375rem;
}
.min-h10 {
min-height: 0.625rem;
}
.min-h11 {
min-height: 0.688rem;
}
.min-h18 {
min-height: 1.125rem;
}

A massive benefit of Atomic CSS is that it shifts maintenance from the stylesheet and into the template. If you need to change some spacing issues on a page then you edit that pages html, not the css.

Eventually you’ll have created enough atoms where you won’t need to write any more css.

Molecules

A commonly used group of atoms is called a molecule. A molecule isn’t actually any new code, it’s just a pattern of repeated classnames. A card molecule can be these classes:

<div class=”width-250 height-100 font-size-16 padding-5"></div>
<!- or if you want to be terse ->
<div class=”w250 h100 f16 p5"></div>

Which translates into:

{
width: 250px;
height: 100px;
font-size: 16px;
padding: 5px;
}

This seems verbose at first and can be hard to get used to but it takes away the problem of having to create another custom class for `.card` if you have a need for just one `.card` to be slightly different than the rest.

Angular 2

If you’ve never played around with angular2 then here’s some quick info regarding css.

In angular2 you have something called components. The app starts with one main/root component and then all other components are descendants of that root component.

Image taken from Victor Svakin’s article: CHANGE DETECTION IN ANGULAR 2

Each component can have a stylesheet attached to it and it will be bound to that component’s scope. If FiltersCmp has a css class called .hide then TodosCmp will have no knowledge of that class. So you might be asking yourself how can we have global styles when all our css is bound to some component? We want our styles to be global and not bound to a specific components scope.

There’s 2 methods of getting our atomic styles to be global. The simplest method is to just have angular take care of it. First, we include the scss in the AppCmp and then we set the encapsulation property of the @Component() decorator to ViewEcapsulation.None. This will tell angular2 that we don’t want this component to be greedy and possessive with these styles. We want it to share it with all of it’s descendants/siblings/ancestors, making it essentially global.

import { Component, ViewEncapsulation } from ‘@angular/core’;
@Component({
selector: ‘app’,
templateUrl: require(‘./app.component.html’),
styles: [
require(‘./app.main.scss’)
],
encapsulation: ViewEncapsulation.None
})

Now, here lies the rub. Angular2 injects <style> tags directly into the head at run-time. Which doesn’t seem so bad at first, but once you start getting into server-side rendering and SEO optimizations then you’ll start to realize that 120,000 lines of css in your index.html is a bad idea, although that may or may not be true. I’m not an SEO expert.

Luckily, there’s another method. We can compile our scss and extract it into a separate main.css file and then include that in our html.

Compiling and extracting

There are a few different build tools out there like Gulp and Grunt. Webpack is probably the simplest, in my opinion, and has a pretty low barrier of entry. I’m going to use webpack2 for this, but this can just as easily be done in webpack1.

Webpack runs off of a webpack.config.js file. In that config file we tell it to look for certain files, in our case scss files, and then process them with some loaders. Loaders are just a fancy term for something that transforms or does an operation on a file, essentially plugins, but they’re the most important part of the webpack ecosystem.

Create an app.main.scss as a sibling to your app.component.ts and then @import all your other scss files into this one. Make sure to remove the styles property from all of your component decorators. Then just add a require(‘./path/to/app.main.scss’) in your client.ts, this will ensure that webpack detects this scss file and process the file the way we expect webpack to.

One gotcha is that since you’re importing these other scss files into the main.app.scss then the relative paths inside the other scss files have to be relative to the main.app.scss.

The scss needs to get turned into css first, so we’re going to pipe it through the sass-loader. We’ll only be testing/looking for our app.main.scss file. All the other scss files should be imported into app.main.scss and the loader doesn’t need to be directly aware of them.

module.exports = {
module: {
rules: [
{ test: /app\.main\.scss/, loader: ‘sass-loader’ }
]
}
}

Now our scss is css, but at this point webpack still doesn’t know what to do with css. We’re going to have to add a css-loader. This loader also resolves all your url sources and places them in your ./dist folder, if you have the proper loader for those files or just use the file-loader.

module.exports = {
module: {
rules: [
{
test: /app\.main\.scss/,
loaders: [
{
loader: ‘css-loader’,
options: {
importLoaders: 1
}
},
‘sass-loader’
]
}
]
}
}

While we’re at it, let’s add postcss as well, since we want to add browser specific prefixes to our css.

Place this file in the root of your project. All it does is tell postcss that we want to use an autoprefixer and that we’re targeting the last 6 versions of browsers. It will auto-magically apply `-moz-`, `-webkit-` and `-ms-` prefixes where they’re needed.

// postcss.config.js
const autoprefixer = require(‘autoprefixer’);
module.exports = {
plugins: [
autoprefixer({
browsers: [‘last 6 versions’]
})
]
}

With the current setup webpack will process all our scss and turn it into a string of css. But that string is useless to us unless it’s in a file. The last thing we need to do is to extract the compiled css into our ./dist folder as it’s own file.

const ExtractTextPlugin = require(“extract-text-webpack-plugin”);
module.exports = {
module: {
rules: [
{
test: /app\.main\.scss/,
loaders: [
ExtractTextPlugin.extract({
loader: ‘exports-loader?module.exports.toString()’
}),
{
loader: ‘css-loader’,
options: {
importLoaders: 2
}
},
‘postcss-loader’,
‘sass-loader’
]
}
]
},
plugins: [
new ExtractTextPlugin({
filename: ‘assets/css/main.css’,
allChunks: true
})
],
output: {
path: ‘./dist/client’,
filename: ‘assets/[ext]/[name].[ext]’
}
}

And there you have it. The (almost) complete webpack.config for extracting fully compiled css from your atomic scss files. There is of course the loaders needed to transpile your angular2 typescript source files, but that’s outside the scope of this post.