Themed Styles With Sass

Writing all the markup and styles from the ground up for multiple brands wastes your time upfront and makes future changes even more cumbersome. Ethan has a Sassy solution to save your time and sanity.

I don't care if you read this post now. It's more of a "bookmark it and know it exists" kind of post. I'm going to be talking about leveraging Sass' features to write themed styles.

Let's say you're creating a few different websites for a few different brands in one fell swoop. Our hypothetical parent company consists of four brands:

  • Bob's Burgers
  • Paddy's Pub
  • Stu's Stews
  • Jurrasic Fork

It'd be a massive undertaking if we were to write all the markup and styles from the ground up for each brand. That's essentially quadrupling the work that needs to be done. Ain't nobody got time for that.

Each brand's website needs to be uniquely styled, but we can still use one set of markup across all brands. Brands will also share layouts and content structure, so we can reuse all these styles, too. We can get uniqueness between themes by applying a layer of branded styling on top of all the shared stuff. Theme Rules in SMACSS describes how this can be accomplished in vanilla CSS. You make a distinction between "shared stuff" and "themed stuff." The shared stuff goes into a shared stylesheet. The themed stuff goes into a theme-specific stylesheet. Then the theme-specific stylesheets are loaded on top of the shared stylesheet, and they can be swapped out for a different theme.

This gets the job done, but like the rest of SMACSS, it's an example that isn't really meant to be followed verbatim. We're allowed to pluck out the principles (i.e. the separation of "shared stuff" and "themed stuff") and rethink the implementation to fit our project. So what's that look like?

The Implementation

First thing's first: our implementation should be great for users. We don't want users to download any styles they might not use. Also, users should make only one request for styles. If it can be avoided, we should avoid making one request for base styles, then a second request for branded styles on top of the base.

And second thing's second: this should be easy on developers. Developers shouldn't have to manage similar-looking chunks of code that are spread across files and directories. That's easy to break. Things should be contained in modules. This cuts down on cognitive load for developers. This also helps with messy interdependence between CSS files. Easier for newcomers, easier for future you.

Sass can pull this off pretty elegantly. We can use a sprinkle of preprocessor fairy dust to organize styles for multiple themes in a way that's easy to read and maintain.

Start by creating a base SCSS file for each brand.

  • bobs-burgers.scss
  • paddys-pub.scss
  • stus-stews.scss
  • jurassic-fork.scss

Note that these aren't partials. They don't start with an underscore. These will all render out into their own CSS files. Each brand gets one CSS file. That means each brand will only make one request. That means users will see the page faster. That means users will be happy. Happy users are good.

In each of these files, we'll set a variable called $brand, then import the rest of our components. This $brand variable is the key to this whole technique. It's what we're going to use to output brand-specific code.

Here's bobs-burgers.scss:

// Set a variable containing current brand
$brand: 'bobs-burgers';
// Import all components
@import 'header';
@import 'hero';
@import 'footer';
view raw bobs-burgers.scss hosted with ❤ by GitHub

All the other branded files will look exactly the same, except for a different value set for $brand.

Hold up, "exactly the same"? Smells like repitition! Let's eliminate it. We can move the list of imports at the end into its own file. That way we can edit a single file to update the styles for all brands. We'll call it _manifest.scss (since this one isn't a standalone file, it starts with an underscore). Now we can import _manifest.scss from all brand files to cut down on duplication.

Here's the new and improved, bone DRY bobs-burgers.scss:

// Set a variable containing current brand
$brand: 'bobs-burgers';
// Import all components
@import 'manifest';

And here's _manifest.scss:

@import 'header';
@import 'hero';
@import 'footer';
view raw _manifest.scss hosted with ❤ by GitHub

Now we're set up to write styles for different brands. Since all our brands' base files are setting a $brand variable, we can check the value of this variable to output code based on brand.

Here's what _hero.scss might look like if we wanted a different background color for every brand:

.hero {
// First, styles that all brands
// have in common.
// (The "shared stuff")
font-size: 2em;
font-weight: bold;
border: 1px solid;
// Next, brand-specific styles.
// (The "themed stuff")
@if $brand == 'bobs-burgers' {
border-color: yellow;
}
@if $brand == 'paddys-pub' {
border-color: green;
}
@if $brand == 'stus-stews' {
border-color: blue;
}
@if $brand == 'jurassic-fork' {
border-color: brown;
}
}
view raw _hero.scss hosted with ❤ by GitHub

When this compiles, a given brand's stylesheet will have only what it needs: styles that are common between all brands, plus styles for that brand. No more, no less. In the rendered CSS, there will be no overridden rules. Just good, clean, branded styles.

Sticking brand conditionals right inside a module helps you wrangle what's out there. You don't have to keep track of rules scattered throughout multiple files that may or may not be nested in subdirectories. Everything is right there. This makes writing new branded code easier, and it makes already-written code easier to digest.

Mixin' in Some Convenience

All those @if statements look sorta ugly. And that's a lot of typing to write styles for every brand. And I can never remember if you need to wrap those values in quotes or not.

We can fix all this by plopping each conditional into its own mixin. This will result in a mixin for each brand.

@mixin bobs-burgers {
@if $brand == 'bobs-burgers' {
@content;
}
}
@mixin paddys-pub {
@if $brand == 'paddys-pub' {
@content;
}
}
@mixin stus-stews {
@if $brand == 'stus-stews' {
@content;
}
}
@mixin jurassic-fork {
@if $brand == 'jurassic-fork' {
@content;
}
}
view raw _mixins.scss hosted with ❤ by GitHub

With these mixins in place, _hero.scss can be reduced to this:

.hero {
// First, styles that all brands
// have in common.
// (The "shared stuff")
font-size: 2em;
font-weight: bold;
border: 1px solid;
// Next, brand-specific styles.
// (The "themed stuff")
@include bobs-burgers {
border-color: yellow;
}
@include paddys-pub {
border-color: green;
}
@include stus-stews {
border-color: blue;
}
@include jurassic-fork {
border-color: brown;
}
}
view raw _hero-version-2.scss hosted with ❤ by GitHub

Lo and behold: we have four separately themed files, generated from one source. Easy-to-see-and-manage branded styles for developers. No wasted downloads or requests for users. Everybody wins.

This technique lets us differentiate on more things than just colors. Let's push things just a little bit further:

.hero {
// First, styles that all brands
// have in common.
// (The "shared stuff")
font-size: 2em;
font-weight: bold;
border: 1px solid;
// Next, brand-specific styles.
// (The "themed stuff")
@include bobs-burgers {
border-color: yellow;
}
@include paddys-pub {
border-color: green;
background: url(/images/shamrock.png);
}
@include stus-stews {
border-color: blue;
&:hover,
&:focus {
// You can nest styles and even reference
// the parent from within this mixin!
background-color: blue;
}
}
@include jurassic-fork {
border-color: brown;
background: url(/images/dinosaur-footprints.png);
}
}
view raw _hero-version-3.scss hosted with ❤ by GitHub

You can take this as far as you'd like. Since these mixins are essentially syntactic sugar for a conditional, you can even nest additional selectors and use the parent selector. Using theme mixins in this way eventually turns into a natural, powerful part of your workflow. And this technique doesn't have to stop at theming brands. We used the same "change a variable and import stuff" technique for Structuring And Serving Styles For Older Browsers. You could also do something weird like output a version of your stylesheet with style guide styles concatenated in. Go wild.

Caveats

The largest caveat here is compile time. Since this builds out a fully contained CSS file for each theme you have, styles will take more time to compile. If this turns into a problem, look into libsass. It compiles Sass wicked fast compared to the Ruby compiler you're probably used to.

Also, I've written this post under the assumption that concatenating all your CSS is better than keeping themes separate. This might not be the case if users will be jumping between brands often, or if your theme files are very small. Think about how this would fit your project, then work from there.