Sass Theming: The Neverending Story

Kitty Giraudel
Kitty Giraudel
Share

Building grid systems and theming engines is a neverending Sass story. There are probably as many Sass-powered grid systems as JavaScript frameworks nowadays, and pretty much everyone has their own way to deal with color themes in their stylesheets.

In today’s article, I’d like to tackle the latter issue and have a quick round-up of a couple of ways of building color schemes with Sass. But before going any further, what exactly are we talking about?

Let’s say we have a component, such as the classic media object. Now let’s say that we have different color schemes for this component, for instance a palette per category. For a category A, our object would have a red headline and a red-ish border, and for a category B, our object would have a more blue-ish color palette.

Finally, say you don’t have 2 themes but a dozen as well as a lot of color variations within the component. You could concede that managing this by hand would be highly impractical, and this is definitely the kind of thing you want to automate with a tool. In our case, that tool is Sass.

Okay, now we are ready. Although categories are kind of boring so let’s bring in some unicorns and dragons. Any of our approaches will rely on a map of themes. Each theme is a sub-map containing keys mapped to actual colors.

$themes: (
  'unicorn': (
    'primary': hotpink,
    'secondary': pink
  ),
  'dragon': (
    'primary': firebrick,
    'secondary': red
  )
) !default;

Each of our themes consists of two colors: a primary one and a secondary one. We could have named them differently like alpha and beta, it does not matter. The point is only to be able to get “the main color from a theme” for instance.

How does the theming actually work?

There are different ways of course but usually, a theme is nothing but a class to which is bound some specific styles. This class can either be added to the body element to wrap the whole pages, or to specific components to themify a small module only, as we had considered just before.

All in all, in your stylesheet you will probably expect any of those two variations:

.theme-class .component {
  /* Style for the component when child of `.theme-class` */
}

.component.theme-class {
  /* Style for the component when has `.theme-class` */
}

The individual mixins approach

Let’s start with a simple one, and probably my favorite: the individual mixins approach. To put it simply, you have a couple of mixins named after the property they intend to styles. For instance, theme-color to themify the color or theme-background-color to themify the background.

Using those mixins will probably look like this:

/**
 * Media object
 * 1. Make the border-color use the secondary color of the theme
 */
.media {
  margin: 15px;
  padding: 15px 0;
  border-top: 5px solid;
  float: left;
  @include border-color('secondary'); /* 1 */
}

/**
 * Media title
 * 1. Make the heading color use the primary color of the theme
 */
.media__title {
  font-size: 1em;
  margin: 0 0 10px;
  @include color('primary'); /* 1 */
}

You have to agree this is quite elegant. Moreover, I feel like it is obvious, even without the comments.

Code

Let’s have a look at how to build this. We are not going to repeat the code logic inside each of those individual mixins, so we need a private mixin to gather it all and then be used inside each subsequent mixin.

/// Themify mixin
/// @access private
/// @author Kitty Giraudel
/// @param {String} $property - Property to themify
/// @param {String} $key - Key color to use from theme
/// @param {Map} $themes [$themes] - Map of themes to use
@mixin themify($property, $key, $themes: $themes) {
  // Iterate over the themes
  @each $theme, $colors in $themes {
    // Create a selector (e.g. `.media.theme-unicorn, .theme-unicorn .media`)
    &.theme-#{$theme},
    .theme-#{$theme} & {
      // Output the declaration
      #{$property}: map-get($colors, $key);
    }
  }
}

And now, or public API:

/// Shorthand to themify color through `themify` mixin
/// @access public
/// @see {mixin} themify
@mixin color($arguments...) {
  @include themify('color', $arguments...);
}

/// Shorthand to themify border-color through `themify` mixin
/// @access public
/// @see {mixin} themify
@mixin border-color($arguments...) {
  @include themify('border-color', $arguments...);
}

/// Shorthand to themify background-color through `themify` mixin
/// @access public
/// @see {mixin} themify
@mixin background-color($arguments...) {
  @include themify('background-color', $arguments...);
}

That’s it. Coming back to our previous example, here is what the CSS output would look like:

.media {
  margin: 15px;
  padding: 15px 0;
  border-top: 5px solid;
  float: left;
}

.media.theme-unicorn,
.theme-unicorn .media {
  border-color: pink;
}

.media.theme-dragon,
.theme-dragon .media {
  border-color: red;
}

.media__title {
  font-size: 1em;
  margin: 0 0 10px;
}

.media__title.theme-unicorn,
.theme-unicorn .media__title {
  color: hotpink;
}

.media__title.theme-dragon,
.theme-dragon .media__title {
  color: firebrick;
}

Pros

  • Thanks to property-named mixins, the API is both clean and clear, even to a non-experienced developer.

Cons

  • This approach involves several mixins instead of one which might or might not be considered as extra complexity, even if most of them are just clones.
  • Because the colors are directly fetched from the color map based on their key, it is not possible to manipulate them with color functions such as lighten or mix. It might be doable with an extended version of the mixin though.

The block mixin approach

The block mixin approach uses a single mixin instead of several one and relies heavily on the usage of the @content directive. Using it would look something like:

/**
 * Media object
 * 1. Make the border-color use the secondary color of the theme
 */
.media {
  margin: 15px;
  padding: 15px 0;
  border-top: 5px solid;
  float: left;
  @include themify {
    border-color: $color-secondary; /* 1 */
  }
}

/**
 * Media title
 * 1. Make the heading color use the primary color of the theme
 */
.media__title {
  font-size: 1em;
  margin: 0 0 10px;
  @include themify {
    color: $color-primary; /* 1 */
  }
}

Code

The idea is simple: exposing the two colors as variables inside the themify mixin. The problem is that we cannot actually do this in a clean way. Variables defined in a mixin are not accessible to the content passed through @content as per the documentation:

The block of content passed to a mixin are evaluated in the scope where the block is defined, not in the scope of the mixin. This means that variables local to the mixin cannot be used within the passed style block and variables will resolve to the global value.

Because of this limitation, we have to hack our way around it. The work-around is actually not that complicated: before outputting @content, we define one global variable per color in the theme, and after outputting @content we unset those variables. This way, they are only accessible in the themify call.

// Initialize our variables as `null` so that when used outside of `themify`,
// they actually output nothing.
$color-primary: null;
$color-secondary: null;

/// Themify mixin
/// @author Kitty Giraudel
/// @param {Map} $themes [$themes] - Map of themes to use
@mixin themify($themes: $themes) {
  // Iterate over the themes
  @each $theme, $colors in $themes {
    // Create a selector (e.g. `.media.theme-unicorn, .theme-unicorn .media`)
    &.theme-#{$theme},
    .theme-#{$theme} & {
      // Set the theme variables with `!global`
      $color-primary: map-get($colors, 'primary') !global;
      $color-secondary: map-get($colors, 'secondary') !global;

      // Output user content
      @content;

      // Unset the theme variables with `!global`
      $color-primary: null !global;
      $color-secondary: null !global;
    }
  }
}

Our previous example would look exactly as with the first approach:

.media {
  margin: 15px;
  padding: 15px 0;
  border-top: 5px solid;
  float: left;
}

.media.theme-unicorn,
.theme-unicorn .media {
  border-color: pink;
}

.media.theme-dragon,
.theme-dragon .media {
  border-color: red;
}

.media__title {
  font-size: 1em;
  margin: 0 0 10px;
}

.media__title.theme-unicorn,
.theme-unicorn .media__title {
  color: hotpink;
}

.media__title.theme-dragon,
.theme-dragon .media__title {
  color: firebrick;
}

Pros

  • Contrary to the individual mixins solution, the block mixin gives the ability to manipulate colors with functions since we have direct access to the colors stored in theme variables.
  • I feel like the API is still quick clear with this approach, especially since we use actual CSS declarations inside the mixin call, which might be easier to understand for some people.

Cons

  • The use of global variables in such a way is kind of hacky I must say. No big deal, but code quality is not really the plus side here.

The big ol’ theme mixin approach

This approach is actually something I have already written about here at SitePoint, in this article from last year. The idea is that you have a big ol’ mixin you update every time you need something to be themified.

This means that this mixin is the same for all components across the project. If you want to make this new component themified in some way, you need to open the file where the themify mixin lives and add a couple of extra rules in there.

// Somewhere in the project, the `themify` mixin
@mixin themify($theme, $colors) {
  // See `Code` section
} @include themify;

/**
 * Media object
 */
.media {
  margin: 15px;
  padding: 15px 0;
  border-top: 5px solid;
  float: left;
}

/**
 * Media title
 */
.media__title {
  font-size: 1em;
  margin: 0 0 10px;
}

@each $theme, $colors in $themes {
  @include themify($theme, $colors);
}

Code

/// Themify mixin
/// @author Kitty Giraudel
/// @param {String} $theme - Theme to print
/// @param {Map} $colors - Theme colors
@mixin themify($theme, $colors) {
  // Output a theme selector
  .theme-#{$theme} {
    // Create the two variations of our selector
    // e.g. `.theme .component, .theme.component`
    .media,
    &.media {
      border-color: map-get($colors, 'primary');
    }

    .media__title
    &.media__title {
      color: map-get($colors, 'secondary');
    }
  }
}

Pros

  • Since the mixin content is manually maintained, it gives a high flexibility with selectors. You can pretty much do whatever you want in there.
  • For the same reason, it also gives the ability to manipulate colors with functions like darken.

Cons

  • Because everything themed is stored in this mixin’s content, it is not well suited for a modular approach. It is probably okay on small to medium projects with global stylesheets though.
  • It might be confusing to have component styles divided into several places, i.e. the module stylesheet and the theme mixin.

The class approach

The class approach is actually a DOM-driven one. The idea is that instead of applying specific theme styles from the stylesheet, you instead add theme classes to your markup such as border-color-primary. These classes, generated with Sass, do nothing on their own but apply some styles when used in combination with our eternal theme-$theme classes.

You can read more about this system in this article from Harry Roberts.

<div class="media  theme-unicorn  border-color-primary">
  <img class="media__image" src="https://lorempixel.com/100/100" />
  <h2 class="media__title  color-secondary">This is the headline</h2>
  <p class="media__content">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Provident nulla voluptatibus quisquam tenetur quas quidem, repudiandae vel beatae iure odit odio quae.</p>
</div>
// Somewhere in the stylesheet (once)
@mixin themify($themes: $themes) {
  // See `Code` section
} @include themify($themes);

/**
 * Media object
 */
.media {
  margin: 15px;
  padding: 15px 0;
  border-top: 5px solid;
  float: left;
}

/**
 * Media title
 */
.media__title {
  font-size: 1em;
  margin: 0 0 10px;
}

Code

/// Themify mixin
/// @param {Map} $themes [$themes] - Map of themes to use
@mixin themify($themes: $themes) {
  // Properties to output, more can be added (e.g. `border-left-color`)
  $properties: ('border-color', 'background-color', 'color');

  // Iterate over the themes
  @each $theme, $colors in $themes {
    // Iterate over the colors from the theme
    @each $color-name, $color in $colors {
      // Iterate over the properties
      @each $property in $properties {
        // Create a selector
        // e.g. `.theme .color-primary, .theme.color-primary`
        .theme-#{$theme} .#{$property}-#{$color-name},
        .theme-#{$theme}.#{$property}-#{$color-name} {
          #{$property}: $color;
        }
      }
    }
  }
}

The output of this code would be:

.theme-unicorn .border-color-primary,
.theme-unicorn.border-color-primary {
  border-color: hotpink;
}

.theme-unicorn .background-color-primary,
.theme-unicorn.background-color-primary {
  background-color: hotpink;
}

.theme-unicorn .color-primary,
.theme-unicorn.color-primary {
  color: hotpink;
}

.theme-unicorn .border-color-secondary,
.theme-unicorn.border-color-secondary {
  border-color: pink;
}

.theme-unicorn .background-color-secondary,
.theme-unicorn.background-color-secondary {
  background-color: pink;
}

.theme-unicorn .color-secondary,
.theme-unicorn.color-secondary {
  color: pink;
}

.theme-dragon .border-color-primary,
.theme-dragon.border-color-primary {
  border-color: firebrick;
}

.theme-dragon .background-color-primary,
.theme-dragon.background-color-primary {
  background-color: firebrick;
}

.theme-dragon .color-primary,
.theme-dragon.color-primary {
  color: firebrick;
}

.theme-dragon .border-color-secondary,
.theme-dragon.border-color-secondary {
  border-color: red;
}

.theme-dragon .background-color-secondary,
.theme-dragon.background-color-secondary {
  background-color: red;
}

.theme-dragon .color-secondary,
.theme-dragon.color-secondary {
  color: red;
}

It might looks like an awful lot of generated CSS, but this is something you can re-use several times throughout your whole project, so it actually is not that bad.

Pros

  • This solution has the benefit of being a DOM-based approach which turns out to be very interesting when having to manipulate themes on the fly with JavaScript. Indeed, it’s only a matter of adding/removing theme classes to/from elements; very handy.
  • While the output of the themify mixin might look big, it actually is a quite DRY approach since every themed color, border-color, background-color and whatelse is applied through these classes.

Cons

  • In some cases, a DOM-based approach might not be the correct way to proceed for instance when the markup is not hand-crafted (CMS, user-generated content…).

Final thoughts

I am sure I forgot maybe a dozen of other ways to apply theme styles with Sass, but I feel like these 4 different versions already cover a good area of the topic. If I get my choice we either go for the individual mixins approach, or the DOM-drive way with classes all the way if we want to go very modular (which is always good).

What about you, how do you do it?

Frequently Asked Questions about Sass Theming

What is Sass and why is it important for theming?

Sass, which stands for Syntactically Awesome Style Sheets, is a preprocessor scripting language that is interpreted or compiled into Cascading Style Sheets (CSS). It introduces features that CSS lacks, such as variables, nested rules, mixins, and functions. These features make the CSS more maintainable, themeable, and extendable. Sass allows developers to write code in a more efficient and clean way, which can be particularly useful when creating themes for websites or applications.

How does Sass improve CSS theming?

Sass enhances CSS theming by introducing features like variables, mixins, and nested rules. Variables allow you to define something once and use it throughout your style sheet, making it easier to change a theme’s color scheme or font stack. Mixins let you group CSS declarations that you want to reuse throughout your site, making your code DRY (Don’t Repeat Yourself). Nested rules make your code more readable and easier to maintain by keeping related code together.

What are the differences between Sass and SCSS?

Sass has two syntaxes: the original Sass, also known as the indented syntax, and SCSS (Sassy CSS). The main difference between them is the way they use syntax. Sass uses indentation to separate code blocks and newline characters to separate rules, while SCSS uses brackets to denote code blocks and semicolons to separate rules. SCSS is more similar to CSS, making it easier for those familiar with CSS to pick up.

How can I start using Sass for theming?

To start using Sass, you need to install it on your system. You can do this by using a package manager like npm (Node Package Manager). Once installed, you can start writing your styles in a .sass or .scss file. To compile these files into CSS, you can use a task runner like Gulp or a module bundler like Webpack.

What are some best practices for theming with Sass?

When theming with Sass, it’s important to keep your code DRY (Don’t Repeat Yourself) and organized. Use variables for values that are used multiple times, like colors and fonts. Use mixins for reusable chunks of CSS. Organize your code into separate files and use the @import directive to combine them. This will make your code easier to maintain and understand.

How can I create a dark mode theme with Sass?

Creating a dark mode theme with Sass can be achieved by using CSS custom properties (variables) and the prefers-color-scheme media feature. You can define a set of colors for the light theme and another set for the dark theme, then use the prefers-color-scheme media feature to switch between them based on the user’s system preference.

Can I use Sass with a CSS framework like Bootstrap?

Yes, you can use Sass with CSS frameworks like Bootstrap. In fact, Bootstrap uses Sass for its source files. This means you can customize Bootstrap’s default styles by overriding its Sass variables, mixins, and functions before compiling the CSS.

How can I debug Sass code?

Sass provides a few tools for debugging, like the @debug and @warn directives, which print values to the console, and the @error directive, which throws an error and stops the compilation. You can also use source maps to see where the CSS rules come from in the Sass source files.

Can I use Sass in a React project?

Yes, you can use Sass in a React project. You can either configure it manually by adding a Sass compiler to your build process, or you can use a tool like Create React App, which has built-in support for Sass.

What are some resources for learning more about Sass and theming?

There are many resources available for learning Sass and theming. The official Sass website provides a comprehensive guide to the language. Websites like CSS-Tricks and Smashing Magazine often publish articles about Sass and theming. You can also find video courses on platforms like Udemy and Coursera.