Sass !default and themeable design systems
I’m working on a couple themeable design systems right now, and we’re architecting components to offer a white list of themeable CSS properties, so each brand can define their design language using design tokens and then pipe those through into the design system’s components.
Making these components sufficiently themeable means making the core components pretty verbose in order to accommodate all the properties we want to allows users to be able to theme. Here’s a basic taste of what we’re talking about here:
.c-text-input { background-color: $form-background-color; padding: $form-background-color; border: $form-border-width solid $form-border-color; border-radius: $form-border-radius; box-shadow: $form-box-shadow; font-family: $form-font-family; font-size: $form-font-size; font-weight: $form-font-weight; /* and so on and so on */ }
This is fine, but what if certain themes don’t need to theme all of these properties? We want to avoid shipping all of these unnecessary CSS declarations to the client. Here’s an example of what we want to avoid:
.c-text-input { background-color: white; padding: 0.5rem; border: 1px solid #333; border-radius: 0; /* not needed */ box-shadow: none; /* not needed */ font-family: inherit; /* not needed */ font-size: inherit; /* not needed */ font-weight: normal; /* not needed */ }
How do we get rid of all of those unneeded properties? Here’s where Sass’s !default
feature comes into play. From the docs:
Normally when you assign a value to a variable, if that variable already had a value, its old value is overwritten. But if you’re writing a Sass library, you might want to allow your users to configure your library’s variables before you use them to generate CSS.
To make this possible, Sass provides the
!default
flag. This assigns a value to a variable only if that variable isn’t defined or its value isnull
. Otherwise, the existing value will be used.
That means that setting up the Sass variables to use null !default
will result in only the variables defined by the theme author to get printed.
$form-border-radius: null !default; $form-box-shadow: null !default; $form-font-family: null !default; $form-font-size: null !default; $form-font-weight: null !default;
Here’s a quick demo showing this solution in action:
See the Pen
Sass null !default by Brad Frost (@bradfrost)
on CodePen.
This is a super nice way of keeping the rendered CSS as lean as possible. It’s also really elegant; I thought I’d have to reach for variable-exists()
, which would essentially mean authoring a conditional for each optional property, which feels a little verbose and gross.
A few questions in my mind are: I wonder how CSS-in-JS solutions handle this? Is it using a ternary for each optional property? And how would this work for CSS custom properties? While CSS variables are amazing, this seems like a case where having a build step really makes a big difference. I wonder if there’s an elegant Sass/CSS properties mashup to accomplish the same results but spitting out custom properties instead?
Anyways, this is fun stuff! Thanks to everyone on Twitter who weighed in on this.