DEV Community

Cover image for Code-efficient Theming and  Coloring in CSS thanks to an OOP-inspired pattern
Dr No
Dr No

Posted on • Edited on

Code-efficient Theming and Coloring in CSS thanks to an OOP-inspired pattern

I'd like to introduce a pattern for writing CSS I've been using in production for almost a year now that aims at solving the hassle of theming and coloring in CSS.

This pattern helped me reduce by 60-90% the size of my CSS code related to coloring and theming compared to libraries like Bootstrap or Semantic.

Warning : If you're a beginner, you might find this too abstract, so I advise you to read what follows only if you have already a bit of experience doing front-end design.

Introduction : context and pattern requirements

To introduce a bit what comes next, here are a few words about my view on front-end design. I've been web-designing since 1999 (starting with the infamous Microsoft Front-Page). I have written quantities of front-end code, for platforms and frameworks like Dotclear, Wordpress, Drupal, making full-fledged Django applications, React, while I still enjoy writing static sites and pure HTML+CSS. I am confortable saying I am quite familiar with not only the DOM syntax, but also with the DOM rationale and philosphy, which is my opinion amounts to kepping the three languages (HTML / CSS / JS) separated by function. As for CSS (the topic of the day), CSS should be as much as possible the only language to take care of styling. HTML shouldnt take care of CSS (avoid using style property), JS shouldnt take care of CSS (avoid using CSS-in-JS, CSS modules, or setting properties in JS). Nevertheless these languages can be interfaced, namely (in a simplified way) HTML interfaces with CSS using classes and ids, and JS can interface with CSS setting classes and ids dynamically. Of course, there may be some exceptions to doing front-end this way, but it's a philosophy I like to stick to as much as I can, it makes my code clean, predicable and maintainable.

Let's get back to our problem. I wanted to expose a theming and coloring CSS library that fullfiled the following requirements :

  • Having a large color palette that encompasses the color wheel (red, blue, pink ... green), monochrome shades (grey, black, white ...), social network shades (instagram, twitter, google), modal shades (success, error, warning)
  • Storing the colors in variables, whether using preprocessor variables (SCSS) or native CSS variables (aka "CSS custom properties", also sometimes wrongly called "CSS4 variables"). So these can be customized by compiling a custom version of the library.
  • The CSS color API doesn't need to know anything at all about all the other parts of my front-end. In other words, the CSS must completely be framework-agnostic (so to avoid being locked down in a way of doing things, like styled components or bootstrap.)
  • Also, the CSS color API must be provided separately from any components or html code. Again, this means the color API cannot know anything about the rest of the front-end framework.
  • Doing all of this in a way that respects the DOM philosphy (C in CSS means cascading, so no CSS modules or weird CSS-in-JS naive reinterpretations of what CSS should be). I just want to import a CSS file into my HTML and leverage the color API out of the box. But I also want to be able to include it in more complex React applications, for instance, and leverage it when designing components.
  • Last but not least : I would consider being successful in designing such a CSS library if I manage to reduce the quantity of code necessary to color components using it from polynomial to linear. Yes, because traditional component libraries such as Bootstrap, Semantic, Material-UI, even Tailwind CSS, encourage you to write "one class per color per component". Instead, I'd like to have only "one class per component", meaning CSS component classes must be able to understand any color.
  • Bonus : Making this color API expose custom themes, like a dark theme.

What we want to avoid : writing a polynomial number of classes to style components per color

Let's look at how Bootstrap works. This is very illustrative of the mainstream way of doing component coloring and theming, and libraries like Semantic-UI and Tailwind offer a similar syntax for classes.

If we look the styling of a button in the bootstrap source we have

@each $color, $value in $theme-colors {
  .btn-#{$color} {
    @include button-variant($value);
  }
}
Enter fullscreen mode Exit fullscreen mode

(bootstrap/scss/_buttons.scss)

Here, in theory the SCSS is quite neat. The code is readable and understandable. You can see that we loop through a hash of colors and define one class per color. The problem is with the compiled output.

This way of writing classes is actually very unefficient.

Why ?

Because this makes us compile to a polynomial quantity of code :

  • If we have a number i of colors (red, green, blue, yellow ...)
  • and a number n of components (button, card, modal, ...) We would end up having i * n CSS classes. (Number of colors times number of components). If we increase one or the other, this has a polynomial (> linear) effect on the size of the compiled output. This will make your CSS files huge, with all the issues that come with it (increased page loading time, browser unable to pick up what's really important to render, not to mention hard to read compiled output for debugging).

A code-efficient pattern for coloring, inspired by OOP, using CSS variables

Let's take a look at what CSS variables can do for us. For the sake of example simplicity, we'll say we have 3 colors : red, blue and yellow.

First, let's store the colors in CSS variables

We store the variables according to the spec the colors in the root HTML element. Namely :

:root {
  --red:#DF2F00;
  --yellow:#FFDA22;
  --blue:#89A6FB;
}
Enter fullscreen mode Exit fullscreen mode

(main.css/main.scss)

If we're using a preprocessor, we could have something like this to make it a bit more maintainable

$colors:(
  "red":#DF2F00;
  "yellow":#FFDA22;
  "blue":#89A6FB;
)!default;

@mixin define-root-variables {
  :root{
   @each $color, $value in $colors{
     --#{$color}:#{$value};
   }
  }
}
Enter fullscreen mode Exit fullscreen mode

Then, if we don't look to improve on having the polynomial number of classes, we would write component classes like this

(I'm just adapting the bootstrap code, don't do this at home kids!)

@each $color, $value in $theme-colors {
  .btn-#{$color} {
    @include button-variant(var(--#{$color})); //We don't need to provide the value anymore because the CSS var takes care of it !
  }
}
Enter fullscreen mode Exit fullscreen mode

At the moment, this doesn't seem like a huge improvement and it is not ! ... but wait, the magic has yet to come. We're almost there.

Introducing Setters and Getter classes

Taking inspiration from the world of Object-Oriented Programming, we create helper classes which we call setters and getters, that set and read a local variable.

Setters set :root variables to an intermediate variable that we call (at the moment, in a bit verbose way) --current-color.

Getters consume the locally set --current-color to apply it to an element in a particular way.

An example is worth a thousand words :

As you can see, with this approach :

  • We only have to create one class per 'use-case', in the example we provided three (text color, background color, and a transparent button). This class is compatible with all the colors.
  • Subsequently, the theming of html block or components only requires, for n components and i colors :
    • i classes for the color setters
    • n classes for the components
    • = n+i classes which is linear.

Let's take a second to realize the code savings:

  • For 5 colors (i=5) and 10 components (n=10), the "classic" approach would imply the creation of i*n = 5 * 10 = 50 CSS classes, whereas the Setter/Getter pattern would only create i + n = 5 + 10 = 15. This is a 70% reduction in the number of CSS classes needed.
  • For 10 colors (i=10) and 20 components (n=20), the "classic" approach would imply the creation of i*n = 10 * 20 = 200 CSS classes, whereas the Setter/Getter pattern would only create i + n = 10 + 20 = 30. This is a 85% reduction in the number of CSS classes needed.
  • For 15 colors (i=15) and 100 components (n=100), the "classic" approach would imply the creation of i*n = 15 * 100 = 1500 CSS classes, whereas the Setter/Getter pattern would only create i + n = 15 + 100 = 115. This is a 93% reduction in the number of CSS classes needed.

A few limitations of this approach (at this point, as we will solve most of them in the part 2) :

  • The CSS classes used above as setters are quite verbose (.current-color-red), this can be improved before using this in production
  • If we wish to have more complex color interactions, such as changing color shade/tint on hover, or contrasting the text automatically, we need a slightly more complex pattern, but it's completely feasible in a linear number of classes as well. Look at the second part of the article for how to do this.
  • The CSS vars are only compatible with 95%+ of browsers. This will not make it in IE11.

Conclusion

By using an intermediate class we call setter, that acts in between the component class (getter) and the :root CSS variables, we are able to reduce the quantity of CSS classes needed to style components from a polynomial number to a linear number.

I write this hoping to give you some inspiration on how to improve your theming and coloring game. IMHO this is long overdue in 2020 !

If this helped you, I invite you to look at Swatch (docs | github ), a library that exposes this pattern in CSS with more than 80 colors. And it's only 2.5KB gzipped. If you like it, a star on github would warm our heart !

In the part 2 of the article (coming soon)

  • I will introduce the CSS library Swatch (docs | github ), that exposes a coloring API based on the previous pattern. We will take a look at how to use it in just a few minutes :)
  • We will see how to use this pattern with Swatches of colors (dark, normal, light, contrast), to make coloring more complex, interesting and interactive

Thank you for reading !

Top comments (2)

Collapse
 
invaderb profile image
Braydon Harris

I really like this approach very well thought out and great article! I'll have to play around with this.

while I'm not a huge fan of css-in-js myself I like the dynamic aspect of it, I'm curious your thoughts on why to avoid it?

Collapse
 
drno profile image
Dr No • Edited

Thanks you for your comment and for reading ! I will write, hopefully tomorrow, a second part on how to use multi variable setters to achieve more complex looks.

Here are in my view the so-called advantages of CSS in JS :

  • Allows for a "dynamic" aspect, as you say with different kinds of variables
  • Makes a 'build system' default, to can take care of anything from minimizing the code, renaming the classes to something random, to subsequently namespace components

If we debunk a bit this, and reflect on the pattern, we realize

  • It silos the CSS/SCSS into javascript modules, which goes against the DOM principle of language/function separation. Also, there's a need to configure a specific linter/syntax hightlighting having some 'unnatural' nested syntax in JS.
  • It's actually quite easy to implement a SCSS/CSS build system independently of javascript (sass src/main.scss dist/main.css -I ./node_modules -c --no-source-map && postcss dist/main.css -o dist/main.mincss is simply as powerful, and does miracles with a nodemon script)
  • Namespacing of modules goes against the very concept of CSS being cascading, in other words CSS is being by nature a scoped language, and the process of properly learning CSS should imply learning how the scoping works (including, for more advanced developpers, understanding how CSS scoping and CSS variables relate to the Shadow DOM)
  • Also, the variables use following CSS-in-JS can be mostly implemented using vanilla CSS (not even using a preprocessor), for instance .component { padding : calc(var(--page-padding) / 2)}. A bit more verbose, that's true (some would say ugly), but I believe that puts to good use the CSS specs. For the colors, that's a bit less obvious, but that's also why I wrote this article !
  • More importantly, it invites beginner / intermediate developpers to "not think" about these problems and jump straight ahead to the solution, even though this solution is highly opinionated and creates what is in my view an unnecessary dependency on javascript to seldom manage stylesheets.
  • Finally, it removes the satisfaction of finding solutions in just CSS ! 😊

This is only my modest view on it. Maybe I'm missing out on some particulars of it, or maybe I'm very old fashioned by having this view, what do you think ?