DRY approach in bootstrap-sass: should I import custom variables before or after _variables.scss?

833 views Asked by At

I'm rolling out my own bootstrap-sass customization in a pretty big project. In my git repo I have my own application.scss file which mirrors the original bootstrap.scss, removing what I don't need and adding my own custom variables, mixins and styles.

I cannot understand the right way to do this while retaining a DRY approach. Should I @import 'application/variables'; before or after I @import 'path/to/bootstrap/variables';?

The before approach

This seems to be the preferred way for bootstrap developers, because all the variables declared in _variables.scss are followed by the !default flag, which takes action only if the variable has been previously declared.

Where does this fail? Take for example a declaration like this:

$brand-primary: $gray;

Compiling this SASS code will spit out an undeclared variable error, because $gray is defined in bootstrap's _variables.scss, which is imported later. If I want this to work, I'll have to re-declare $gray at the top of my file, even though it didn't change from the default.

This may not sound like a big deal, but over a certain level of complexity it starts to happen quite a lot, and your application/_variables.scss goes from being "the file where I define my own variables" to "the file where I define my variables and copy over some other bootstrap stuff without actually changing it".

The after approach

To overcome the problem of the before approach I tried to import my variables after bootstrap's ones. It turns out it doesn't really work either, and it's broken in subtler ways. Look at this example:

$padding-base-vertical: 8px;

Looks innocent, right? And in fact it will change the vertical padding as needed. How does this break? It breaks, for example, the $input-height-base variable, declared later in bootstrap's _variables.scss, which is not correctly recalculated based on $padding-base-vertical's changes.

And this is how $input-height-base is defined:

$input-height-base: ($line-height-computed + ($padding-base-vertical * 2) + 2) !default;

Thus $input-height-base is calculated with $padding-base-vertical's default value and then never again. As a side effect, this kind of problem is quite hard to debug (at least it was for me).

The "solution"? Redeclare $input-height-base and all the other dependent variables in application/_variables.scss just like they are in bootstrap. Yes, it's the same workaround of the after approach.


So, is there a DRY way to do this? I can't even split my variables in two, part to set before and part to set after, because I could bump in a variable which hits both problems.

The first approach is the less ugly, but it's far from being an ideal solution.

1

There are 1 answers

3
hon2a On BEST ANSWER

This problem took me some time to puzzle out as well. Here's the solution I came up with.

To keep your SASS code modular, keep distinct separation between the two types of code - declarative and imperative. By declarative code I mean definitions of variables, functions, and mixins. By imperative code I mean style definitions (any code that will actually produce some output when compiled). Keep those kinds of code in separate files. You can then transparently declare dependencies (@imports) between files containing declarative code, because including declarative code multiple times will have no effect on the output.

Using the approach outlined above, split your "customized Bootstrap" into two files - bootstrap-declarations and bootstrap-styles (I actually split it into more files to keep variables separate from functions/mixins). Then @import the bootstrap-declarations from your application's variables without any hassle and declare your variables both before and after the @import as needed. In your application's imperative code (styles), @import your application's variables as the very first thing, before @import of bootstrap-styles, and you're all set.