Complex CSS projects became way more elegant since the availability of pre processors. Nevertheless some stuff always bugged me. We define variables on a global scope inside some project-wide _variables.scss file and include those everywhere. References are scattered everywhere. If you ever tried to comprehend a 2000+ line, not that well documented, non-bem scss file, nested up to 9 levels you’ll know.
Lets modularize our Sass to make it more reusable and easier to understand.
Before we start, please take a look at some best practices and style guides that’ll enable you to write more beautiful sass. Also even if not absolutely necessary for the described approach I’d highly recommend looking into BEM structures. A super nice syntax for that is supported since Sass 3.3. (I’ll use .block__element--modifier
here.)
Each of our modules (or blocks) will live inside it’s own file. Ideally we’ll have to write the module name only once inside that file. All variables used inside the module have to be defined by the module itself and must be scoped to it.
Global variables can be passed down to the module. Listing all of them in the beginning acts like some kind of pseudo-constructor feeding data into the module. If the module is adapted to another project chances are only the variable declarations have to be touched.
My first thought was to use Sass’ build in block scope: A variable defined within a block is scoped to that block. (Unless it has a !global flag.)
I went ahead created a block named after our module(.button {}
). Next I redeclared all needed variables inside it’s scope ($button-border-width: 4px; $button-background-color: $some-global-color; …
). Even though this works I personally do not like that solution. There is no distinction between globals and non-globals. It’s quite easy to accidentally use a global var somewhere inside a module and create an unwanted dependency. Also there is no possibility to store the module name to reuse it and keep things DRY.
My favorite approach is to fake scope. I decided to reserve a global variable for the scope. I went with $this
to make it look a little OO. Meaningful names for this (no pun intended) might also be $scope
or $module
. The variable is set to a map, containing all configurable data. It also has the module name. After all module definitions $this
is set to null to clear it. The next module will then reuse $this
.
To access the scoped data one could simply use the build in map-get()
function. In my opinion it is a little verbose to always call map-get()
passing it $this
and the desired data-key. Therefore I created a little helper function: this($key)
is as simple as it gets. It always map-get()
‘s the key from the $this
map.
A reference to scoped data inside a module then looks like this:
color: this(color);
Sass’ interpolation feature and the fact that were stored the module name in $this allows this selector syntax at our modules root:
.#{this(module)} {
…
And even comments can be fed with this()'
data:
/* #{this(module)} Module */
Let’s take a look at all of that put together.
// layout.scss
@function this($key) {
@return map-get($this, $key);
}
$some-global-color: hotpink;
// _button.scss
$this: (
module: button,
border-width: 3px,
background-color: white,
color: $some-global-color,
background-color-hover: $some-global-color,
color-hover: white,
background-color-disabled: mix(white, lightgrey),
color-disabled: mix($some-global-color, lightgrey),
);
/* #{this(module)} Module */
.#{this(module)} {
background-color: this(background-color);
color: this(color);
display: inline-block;
padding: 10px 20px;
border: this(border-width) solid this(color);
margin: 10px;
text-transform: uppercase;
&:not(#{&}--disabled):hover {
background-color: this(background-color-hover);
color: this(color-hover);
}
&--disabled {
background-color: this(background-color-disabled);
color: this(color-disabled);
border: this(border-width) solid this(color-disabled);
}
}
// unset this
$this: null;
See the Pen Sass module /w faked scope by Jan Willem Henckel (@farly) on CodePen.
Don’t be afraid to store the same value in different keys. I like to think of the $this declaration as some kind of constructor. The time will come to reuse the button module, call it the fancy-button module and have the hover colors be something completely different. Because there is a map-key for all states all that has to be modified is the $this
definition.
I’d love to hear your thoughts about this idea.