Maintainable CSS based on SCSS

I’ve been working with SCSS for quite some time now and can’t code without it anymore. The main reason I’m using it is because of the ability to have seperate files, each with its own purpose. Besides that mixins and placeholders make it easy to extend code so you can come up with a structure which keep your code clear and maintainable. At least… when you do it the right way.

I’m not writing this article to convince you to use SCSS but instead I want to give you a good impression on how I think people should set up their project structure. When writing SCSS you have to deal with a lot of stuff. Think of reusable buttons, typography, external assets, responsiveness, cross-browser compatiblity and so on. All these aspects need a clever way of developing to keep your code clean and more important; to keep it readable for your fellow front-enders.

Structure

At Blocklevel we have a default folder structure to keep everything ordered. The screen.css contains a table of contents, it looks something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Utility
@import 'utility/normalize';
@import 'utility/mixins';
@import 'utility/functions';
@import 'utility/variables';
@import 'utility/breakpoints';
@import 'utility/extends';
// Font including
@import 'type/font';
@import 'type/type';
// Assets
@import 'asset/asset';
// Layout
@import 'layout/layout';
// Module
@import 'module/module';
// Pages
@import 'page/page';
// Vendor
@import 'vendor/slick';

Normalize/Reset

Normalize.css makes browsers render all elements more consistently and in line with modern standards. It precisely targets only the styles that need normalizing.

Mixins

This file contains all global mixins. A mixin lets you make groups of CSS declarations that you want to reuse throughout your site.

Functions

This file contains all global functions. Instead of outputting lines of Sass the way mixins do, functions return a value. This can be useful when you have to do calculations.

We’ve set up some mixins and functions which are used in almost every project, more about those later.

Variables

This file contains all global variables used in the project. The default variables.scss would look something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Source Paths
$pathFont: '../font/';
$pathImage: '../image/';
// Fonts
$fontHeading: Arial, sans-serif;
$fontCopy: Verdana, sans-serif;
// Sizes
$sizeSiteWidth: 1240px;
// Colors
$colorBlack: #000;
$colorWhite: #fff;
// Z-indexes
$layout: header, content, sidebar, popup;

Source paths

Source paths are used to load assets (for example fonts and images). Having these paths in a variable is really useful because these paths are used everywhere in your project. Imagine you have to move your assets to a different folder or even a different server (CDN). Now you can easily change just one variable and all your assets will load from the correct location. You can use a variable like this:

1
2
3
.selector {
background-image: url(#{$pathImage}filename.png);
}

Fonts, sizes and colors

Font variables are very useful too. Not only to avoid repetitive font-family styles but also for clearer namings. $fontHeading makes more sense than just a font-family like ‘Verdana’. More about typography later.

This also goes for sizes (and colors). Imagine having a 2 column layout. When you want the content section to be 980px and the sidebar should fill up the remaining space in our wrapper you can do something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// variables.scss
$sizeSiteWidth: 1440px;
$sizeContentWidth: 980px;
// layout.scss
.wrapper {
width: $sizeSiteWidth;
}
.content {
width: $sizeContentWidth;
}
.sidebar {
width: ($sizeSiteWidth - $sizeContentWidth);
}

Z-indexes

Managing z-indexes through a function is definitely needed. Since the z-index is accepting a unitless value between (-infinity) and infinity it gives you plenty of occasions to screw things up. Often you see things like z-index: 9999. Never do this. In fact: never use z-index without the awesome z-index function.

In variables.scss we defined $layout. This is a list of elements. Now when we use the z-index function it returns the index of the array item. Adding an item in the middle of this array will change all z-indexes of the arrays items automatically. Now we can do something like this and never have to change a dozen of z-indexes anymore when we add a new element:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.header {
z-index: zindex($layout, header); // returns 1
}
.content {
z-index: zindex($layout, content); // returns 2
}
.sidebar {
z-index: zindex($layout, sidebar); // returns 3
}
.popup {
z-index: zindex($layout, popup); // returns 4
}

Breakpoints

Since most of our websites are responsive and target desktops, tablets and mobile devices we use a media query manager. In our default project there are 4 default sizes:

1
2
3
4
5
6
$breakpoints: (
'x-small': '(max-width: 480px)',
'small': '(max-width: 768px)',
'medium': '(max-width: 1024px)',
'large': '(min-width: 1025px)',
);

Now with the respond-to mixin you can do the following:

1
2
3
4
5
6
7
.selector {
width: 500px;
@include respond-to('x-small') {
width: 200px;
}
}

This way you have all the breakpoints at one spot and they can easily be changed for the whole project. You might wonder why the breakpoints are not set up to be “mobile first”. This is because most of our clients focus on desktop. This means development for desktop already starts while mobile designs are not delivered yet. I know this is not ideal but changing these breakpoints to “mobile first” is quite easy and depends on the project you are working on.

Extends

This file contains some extendable %placeholders and .classes. A good example is the %reset-input which resets the browsers default input styles. It can be used like this:

1
2
3
4
.input {
@extend %reset-input;
// Input styles here...
}

Typography

Managing typography can often be a tricky issue since we have to deal with different fonts, different sizes and exceptions for smaller screens. Having a good approach to modular typography will provide a solid foundation for all projects. In font.scss all font assets are loaded through the @font-face mixin. This mixin takes care of loading all needed assets, this way you don’t have to write repetitive code to load a font:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Mixin
@mixin font-face($fontName, $folderName, $fileName, $fontWeight: normal, $fontStyle: normal) {
@font-face {
font-family: $fontName;
src: url('#{$pathFont}#{$folderName}/#{$fileName}.eot');
src: url('#{$pathFont}#{$folderName}/#{$fileName}.eot?#iefix') format('embedded-opentype'),
url('#{$pathFont}#{$folderName}/#{$fileName}.woff') format('woff'),
url('#{$pathFont}#{$folderName}/#{$fileName}.ttf') format('truetype'),
url('#{$pathFont}#{$folderName}/#{$fileName}.svg##{$fontName}') format('svg');
font-weight: $fontWeight;
font-style: $fontStyle;
}
}
// Usage
@include font-face('booster-next', 'booster-next', 'boosternextfy-regular');

Once you’ve loaded all needed fonts and defined the font variables in variables.scss we can start setting up a base for all type styles:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Headings
.heading {
// Global heading styles here...
&.h1 {
font: 2.8rem $fontHeading;
letter-spacing: -.5rem;
}
// etc.
}
// Copy
.copy {
// Global copy styles here...
&.c1 {
font: 2.4rem/1.5em $fontCopy;
}
// etc.
}

Notice the use of classes instead of elements. The main reason for this is because you should avoid styling HTML elements directly. Imagine a website with content provided by a Content Management System. Most of the time clients are able to fill in their sites content through a Rich Text Editor. Most of these editors output raw HTML so titles are rendered like this:

1
<h1>I am a title</h1>

Now when we styled the <h1> element the clients content will adapt the h1 styles. You don’t necessarily want this so stick to classes if you’re awesome:

1
2
3
4
5
<h1 class="heading h1">My awesome styled text</h1>
<div class="rich-text-editor">
<h1>Clients text, which is not affected by our global heading styles! Woohoo</h1>
</div>

Asset

The asset file contains all assets needed across your project. In here we define our global icons, buttons, form elements and list elements. Keep in mind that these are global styles which only correspond to the individual assets so these styles should be as clean as possible. Let’s start with a bad practise:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.button {
margin: 0;
padding: 0;
border: 0;
background: transparent;
&.primary {
background-color: #f00;
.sidebar & {
width: 100px;
}
}
}

A few things go wrong here. You don’t want exceptions for the sidebar to be in your button styles since the button.scss should only represent the buttons global styles. A better way would be:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// button.scss
.button {
@extend %reset-button; // Resets default browser behaviour.
&.primary {
background-color: $colorRed; // Always use color variables.
}
}
// sidebar.scss
.sidebar {
.button.primary {
width: 100px;
}
}

Of course the same goes for icons, form elements and lists.

Layout

Once all assets are there it’s time to setup the global layout styles. Think of the header, content, sidebar, footer etcetera. Don’t go too crazy on defining layout. Think of it as the shell of the website. After finishing layout it’s time to fill in the sections with modules. A good layout.scss would look something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
html {
font-size: 62.5%; // Sets 1rem to 10px
}
body {
height: 100%;
}
.wrapper {
width: 100%;
max-width: $sizeSiteWidth;
margin: 0 auto;
}
.header {
margin: 0 0 20px;
}
.main {
display: flex;
}
.content {
flex: 1;
width: $sizeContentWidth;
}
.sidebar {
flex: 1;
width: $sizeSiteWidth - $sizeContentWidth;
}
.footer {
width: 100%;
margin: 20px 0;
}

HTML:

1
2
3
4
5
6
7
8
9
10
<div class="wrapper">
<header class="header"></header>
<div class="main">
<div class="content"></div>
<div class="sidebar"></div>
</div>
<footer class="footer"></div>
</div>

This represents a really simple website but I think you understand the purpose of layout now.

Module

Once all layout styles are defined it’s time to start styling modules. Modules represent independent blocks which can be implemented anywhere in the project. A good example for a module is a carousel:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
.module-carousel {
position: relative;
overflow: hidden;
.slide {
float: left;
width: 200px;
}
.button.next {
position: absolute;
right: 0;
}
.button.previous {
position: absolute;
left: 0;
}
}

Depending on the project you will have several modules. Let’s say you have two carousels on your website. One on the homepage which has a 100% width and one on a subpage with a 50% width. You can accomplish this by making a variant class in your module:

1
2
3
4
5
6
7
8
9
// module/_carousel.scss
.module-carousel {
width: 100%;
// Variants
&.half-width {
width: 50%;
}
}

And the HTML:

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- Full width -->
<div class="module-carousel">
<div class="slide"></div>
<div class="slide"></div>
<div class="slide"></div>
</div>
<!-- Half width -->
<div class="module-carousel half-width">
<div class="slide"></div>
<div class="slide"></div>
<div class="slide"></div>
</div>

Page

In our project structure each page gets its own unique class. For example: The homepage will have a wrapping class called .view-home and the about page will have a class .view-about. This is useful when you have page specific exceptions. These files should be as empty as possible since this is for really odd exceptions. Variant classes in modules should cover most of this.

This is a good example:

1
2
3
4
5
6
7
.view-about {
.module-carousel {
margin-top: 30px;
}
}

Vendor

Vendor is responsible for all external styles that are used, for example: open source plugins.

That’s it! Congratulations! You’ve managed to get through the whole post. I hope this article covers the basics of writing maintainable CSS. Good luck writing some awesome styles!

Share Comments