Joshua Johnson

Front-end Developer @ BT

Theming with Sass

For any large-scale project, maintaining clean, organised CSS is by no means easy. Handling file imports or using variables for example are both crucial pieces to the scalable-style-sheet-puzzle™; both of which vanilla CSS cannot handle well if at all.

Through no fault of their own (the web is a very different place now as it was in the 90s), the creators of CSS have not made front-end developers' lives easy when developing scalable, 'performant' products. Attempting to understand, let alone manipulate scope in CSS for example is frivolous — the control just is not there. Everything is global meaning it is far too easy for a codebase to quickly fill up with overrides of code written further up the cascade. Although this may be tameable for smaller projects, having each and every CSS value in the global namespace is problematic and if you throw theming into the mixture, you will quickly find yourself fighting a losing battle.

Thankfully, we have preprocessors such as Sass that equip us with features and functions to handle some of the issues aforementioned. Structuring for example is made especially easy through the ability to break style sheets into various partials that can be thus imported into one core style sheet (and therefore one HTTP request) affording control over the cascade. The ability to store values in reusable variables also makes maintaining a large codebase far more manageable, allowing developers to quickly change a value from one place, as opposed to trawling through directories to find a value that may have discrepancies – the inconsistent use of 'white', '#fff' ', '#FFF', '#FFFFFF', '#ffffff' springs to mind.

So can Sass help us with theming?

Creating a styling layer

Before diving into whether SASS can help us, it is important to first create the foundation for it to do so, through what I am now going to call the 'style layer'.

If you are familiar with writing object-oriented CSS you will know that separating style-specific CSS from layout-specific CSS separates our concerns, allowing us to make minor superficial changes to an object (or module if you prefer) whilst ensuring the core layout, positioning and sizing remain. Of course you could pile all that code together into one class like the following:

.foo {
    // Styling
    background-color: $bg;
    color: $color;

    // Positioning
    position: fixed;
    bottom: 0;
    left: 0;
    height: 5em;
}

But if we went ahead with the code above, as soon as we need to introduce a modifier (we are adopting the BEM methodology for clarity) to the class .foo, we are forced to write code that overrides a pre-existing value:

.foo--highlight { background-color: $bg-highlight; }

But if we instead abstract the style layer away from the core object code, there is no longer a need to introduce overrides that more often than not lead to more specific CSS – not something we should be aiming for. Instead we can create modifier classes that set the initial value wherever the class is used, not only reducing specificity but also giving us the ability to manipulate style values without touching the object class. This may look something like this:

.foo {
    position: fixed;
    bottom: 0;
    left: 0;
    height: 5em;
}
.foo--primary { background-color: $bg-primary; }
.foo--highlight { background-color: $bg-highlight; }

Problems with theming

So we now have clean CSS with low specificity. We do not however have CSS that can be easily themed. As soon as a theme needs to add its own primary or highlight background colours (following on with the previous example) we are immediately provoking a scrap with our old mate – specificity.

Previously when introducing themes for our internal SaaS product Twine, my perhaps knee-jerk inclination was to create a new style sheet that would be loaded into the page after the main style sheet, containing any theme-specific modifier classes (like the ones above). This is however problematic, as classes lose their proximity to the actual object, meaning it becomes far more difficult to understand the modifiers that are actually available for styling (as they are now in separate directories). I, for example, may have forgotten about the 'highlight' modifier class I wrote for one particular theme, meaning when the time comes to create an entirely new theme, there is a chance I will miss said class entirely, leaving an object without any recognisable styling.

There is also the added task of having to update each theme file with any new objects developed into the browser, slowing down our workflow. What we really need is to maintain the abstracted style layer whilst maintaining the relationship of the object class and its modifier classes.

Creating a theme layer

Fortunately, with Sass, instead of needing a theme file, we can tie the style layer to a broader theme layer attached to the 'html' element whilst maintaining class proximity via the ampersand character. This may look something like this:

.foo { ... }
.foo--highlight { 
    .t-default & { background-color: $t-default-bg-highlight; }
    .t-alt & { background-color: $t-alt-bg-highlight; }
}

Which would compile into the following CSS:

.foo { ... }
.t-default .foo--highlight {
    background-color: #BADA55;
}
.t-alt .foo--highlight {
    background-color: #FAFAFA;
}

Not only does this have the added benefit of eliminating one HTTP request (better for performance), it is now far more manageable as the modifier class clearly has a relationship with the object class – in this case we are simply adding a highlight background colour for each theme. Although prepending a theme class before another class name does increase our CSS's specificity (by one), we now have a consistent layer of non-obtrusive classes that can be easily managed through variables.

Hold your horses, what if I have lots of themes?

A valid observation. It is possible but not particular likely you are going to go to the effort of creating an entire theme layer and only introduce two or three themes. Twine, for example, contains eight themes (and counting!) so adding a style modifier to an object is a cumbersome task as values need to be set for each and every theme available to the user.

Fortunately Sass (v3.3+) comes with a feature called Maps that allow us to loop through variable lists – essentially an array of values. The keen eyed of you will have noticed that in the example above I no longer have a generic $bg-highlight variable that stores the project's highlight colour. Instead I tie a unique variable to each theme.

With Sass maps we can quickly turn such variables into a list that can be looped through eliminating the need for us to explicitly declare each theme when styling an object. This may look something like this:

$bg-highlight: (
    t-default: (bgcolor: $t-default-bg-highlight),
    t-accentuate: (bgcolor: $t-accentuate-bg-highlight),
    t-bluesky: (bgcolor: $t-bluesky-bg-highlight),
    t-dusk: (bgcolor: $t-dusk-bg-highlight),
    t-midnight: (bgcolor: $t-midnight-bg-highlight),
    t-sleek: (bgcolor: $t-sleek-bg-highlight),
    t-solar: (bgcolor: $t-solar-bg-highlight),
    t-sunrise: (bgcolor: $t-sunrise-bg-highlight),
);

Using the above list now enables use to refactor the previous example to be far more manageable and lean:

.foo--highlight {
     @each $theme, $map in $bg-highlight {
        $bg-highlight: map-get($map, bgcolor);
        .#{$theme} & {
            background-color: $bg-highlight;
        }
    };
}

Which, when compiled, produces the exact same CSS for each theme as before but saves us some of the leg work!

Expanding this approach

Although I have used a background colour as an example throughout this post, it should be said that this can be extended to cover a variety of values that relate to each other. There is no need to create a new map and loop for each and every property we need to pull for a theme, we can simply relate two map keys together. In the map below for example we are storing values that will be used to generate a gradient:

$brand-gradient: (
    t-default: (
        startcolor: $t-default-header-gradient-start,
        endcolor: $t-default-header-gradient-end,
    ),
    t-dusk: (
        startcolor: $t-dusk-header-gradient-start,
        endcolor: $t-dusk-header-gradient-end,
    ),
    t-midnight: (
        startcolor: $t-midnight-header-gradient-start,
        endcolor: $t-midnight-header-gradient-end,
    ),
    t-sleek: (
        startcolor: $t-sleek-header-gradient-start,
        endcolor: $t-sleek-header-gradient-end,
    ),
    t-sunrise: (
        startcolor: $t-sunrise-header-gradient-start,
        endcolor: $t-sunrise-header-gradient-end,
    ),
);

As before, to access these values we can use a loop:

.foo--gradient {
    @each $theme, $map in $brand-gradient {
        .#{$theme} & {
            $start-color: map-get($map, startcolor);
            $end-color: map-get($map, endcolor);

            background-image: linear-gradient(to right, $start-color 0%, $end-color 100%);
        }
    }
}

Controlling themes in Sass is by no means perfect – repeating the loop when setting something simple like a colour can become somewhat arduous (using a mixin unfortunately doesn't work as of writing) – however it is a significant step forward in terms of maintaining control of specificity in large projects whilst having fun with themes!

Afterthought (31/03/2015)

Having experimented with this approach over the past month it is worth noting that it is quite easy to unknowingly increase the weight of your site's CSS without diligence. Once compiled and gzipped Twine's CSS weighed in at a modest 33kb, however we did hit the fatuous IE selector limit. If IE7-9 support is a must, I recommend bless.js as a workaround (Grunt/Gulp integration available).