Easy theming with SASS mixins.

Subscribe to my newsletter and never miss my upcoming articles

While building a dark theme for my React App, I've found myself escaping the nesting a lot in order to make styles for the dark theme . Then, this mixin was born.

Show me the code!

@mixin atDarkTheme {
    $selector: #{&};
    @at-root body.dark-theme #{$selector} {
        @content
    }
}

It's worth mentioning, this will work in any kind of project that uses Sass, not just React. And even though styled components can tackle theming easily too, I'm a fan of Scss and having my components styled with their own Scss file.

Breakdown

When dark mode is enabled, <body> receives the class dark-theme. So in order to prepend the body.dark-theme selector the mixin works like this:

  • $selector: parses our current selector making use of & (parent selector) and #{}, and then stores it for later use.
  • @at-root: moves the next selector to the root of the document, escaping the current nesting.
    • body.dark-theme: the class that will be responsible for our dark-mode styles.
      • #{selector}: adding our previous selector back again.
        • @content: everything we add between the brackets {} in our @include goes here.

Usage

.just-a-box {
    background: white; // ☀
    color: black; // ☀
    width: 100px;
    height: 100px;
    // Dark Theme styles
    @include atDarkTheme {
        background: black; // 🌙
        color: white; // 🌙
    }
}

After compiling this code into CSS, it will look like this:

.just-a-box {
    background: white;
    color: black;
    width: 100px;
    height: 100px;
}
body.dark-theme .just-a-box {
    background: black;
    color: white;
}

🧬Evolving the mixin: Multiple Themes

Why conform with dark mode, let's create themes with one mixin to rule'em all!

@mixin atTheme($theme: "default") {
    $selector: #{&};
    @at-root body.#{$theme}-theme #{$selector} {
        @content
    }
}

Usage

Now we can pass an argument to the mixin to dinamically add styles to the themes we create.

.amazing-navbar {
    background: white;
    color: black;
    width: 100%;
    height: 52px;
    // Theme styles
    @include atTheme("red") {
        background: red; // 🟥
    }
    @include atTheme("green") {
        background: green; // 🟩
    }
    @include atTheme("blue") {
        background: blue; // 🟦
    }
    @include atTheme("halloween") {
        background: purple; // 👻
    }
}

After compiling this code into CSS, it will look like this:

.amazing-navbar {
    background: white;
    color: black;
    width: 100%;
    height: 52px;
}

body.red-theme .amazing-navbar {
    background: red;
}

body.blue-theme .amazing-navbar {
    background: blue;
}

body.green-theme .amazing-navbar {
    background: green;
}

body.halloween-theme .amazing-navbar {
    background: purple;
}

💡A few more ideas

To keep the post simple I'll just give you a few hints to where you can improve upon on your project:

  • CSS Variables
  • Predefined $themes variable to check if the argument passed to the atTheme mixin is valid.
  • "Automatic" dark-theming with the invert filter.

Conclusion

Using @at-root directive in mixins will save us a lot of time, creating theme-specific selectors for our elements we are working on in a breeze, relying only in CSS' powerful specificity.

If this post was any help or do you think this can be improved upon, let me know in the comments!

If you are still hungry for more sassy knowledge, I've written another post about it and the Bulma framework.

Until next time 👋!

¡Versión en español!

Comments (4)

Jome Favourite's photo

Thanks for sharing, I've got to try it out

Show +1 replies
Jome Favourite's photo

It worked perfectly, I'll start using this approach when ever I need dark mode toggle Lucas Terracino

Lucas Terracino's photo

I'm glad it worked out for you Jome Favourite! Happy to help