Adam Johnston

Talking CSS Architecture

Writing about CSS architecture has been my crutch. It’s one of the topics up to this point in my front-end career that I have read, thought over and evolved upon the most. Possibly for the last time I wanted to document how my approach to CSS architecture has changed and explain why I feel it might be for the last time.

Firstly I want to talk about what brought on these changes. My older approach but I recently read about the problems of using @extend. At one time I went a little overboard with using @extend in the belief that it was a benefit. I would have an entire folder littered with partials, dedicated to patterns that could be re-used across the app. Let me show you a brief example of what I believe now to be a mistake.

//  scss/patterns/_margins-gutters.scss
//  gutter half
//  -----------
%gutter-half {
  padding-left: 0.5em;
  padding-right: 0.5em;
}
//  a whole lot more patterns…

//  scss/patterns/_displays.scss
//  display block
//  -------------
%display-block {
  display: block;
}
//  display inline block
//  --------------------
%display-inline-block {
  display: inline-block;
}
//  a whole lot more patterns…

//  scss/modules/_grid.scss
//  grid wrapper
//  ------------
.grid {
  @extend %gutter-half;
  @extend %display-block;
  //  other styles
}
//  grid columns
//  ------------
[class*='grid__'] {
  @extend %gutter-half;
  @extend %display-inline-block;
  //  other styles
}

I originally thought this would be, that while the selectors were large due to the nature of using @extend, it would keep file sizes small and so download times would be short. I have read articles recently that state the contrary and in larger CSS code bases that rely heavily on @extend can cause errors due to large selector sizes and file sizes are greater than those that use mixins after gzipping. You can read about @extend issues and statistics provided by Shay Howe of Belly here.

So, now with this evidence I have an alternative approach. I have seen some say that you should never use @extend and I think that could be something of an extreme but I do believe you need to be careful. If Spidey was a developer, this would be the point he’d recite Uncle Ben. Here’s a short example for buttons using mixins.

<!-- html -->
<button class="btn btn--warn btn--lrg">Delete</button>
//  css
//  button mixin (not found in utils/mixins because it is specific for buttons)
//  ------------
@mixin button-colour($background-colour, $border-colour) {
  background-color: $background-colour;
  border-color: $border-colour;
}
@mixin button-size($size) {
  @if ($size === 'small') {
    padding-left: 0.5em;
    padding-right: 0.5em;
    font-size: 0.8em;
    line-height: 1em;
  } @else if ($size === 'large') {
    padding-left: 1em;
    padding-right: 1em;
    font-size: 1.5em;
    line-height: 2em;
  }
}
//  button blueprint
//  ----------------
.btn {
  border-width: 1px;
  border-style: solid;
  display: inline-block;
  vertical-align: top;
}

//  button default
//  --------------
.btn--def {
  @include button-colour(grey, darken(grey, 10%));
}
//  button warning
//  --------------
.btn--warn {
  @include button-colour(red, darken(red, 10%));
}
//  button call-to-action
//  ---------------------
.btn--cta {
  @include button-colour(lighten(green, 10%), green);
}

//  button small
//  ------------
.btn--sm {
  @include button-size(small);
}
//  button large
//  ------------
.btn--lrg {
  @include button-size(large);
}

The reason I think this is a better approach is we have a parent class called .btn which will need to be added to all our button elements and then modifier classes to handle how the button should appear.

Our button classes are short and precise. They’re singular classes doing one thing and exactly how we want them to and there are also no declarations that are overwritten and so we’re not outputting useless CSS.

Managing changes are easier as we have broken our classes up into smaller pieces. If we decide we want to change how all the large buttons should look, it’s easy we can just change the mixin. We can also then change our warning buttons without having to worry about breaking other types of button.

So now I’ve dropped the patterns folder and individual partials, this is how my current folder structure looks:

→ utils
  → vars
    ↳ a bunch of variables.
  → functions
    ↳ a bunch of function.
  → mixins
    ↳ a bunch of mixins.
  → third-party
    ↳ Bootstrap
    ↳ etc…
→ generic
  ↳ _main.scss
  ↳ _typography.scss
  ↳ etc…
→ modules
  ↳ _grid.scss
  ↳ _form.scss
  ↳ etc…
→ singletons
  ↳ _buttons.scss
  ↳ _typography.scss
  ↳ etc…
→ themes
  ↳ _default.scss
  ↳ etc…
→ app.scss

A quick discussion on ReactJS

So now we’ve cleared that up, on to my second point. The reason I think this may be the last time I’ll be talking about this subject is due to the rise of ReactJS and it’s ideology of components and encapsulating technologies. I actually like this idea as it’s something we as developers have been doing but instead we’ve focused on separating the technologies as our concern, not what they do as a concern.

Buttons are a great example. We have a button class in our CSS files for styling, events attached to buttons to handle functionality in a JS file and perhaps we have a pattern library with all our button’s HTML which ties the other two technologies together.

With ReactJS’ approach you would group JS, CSS and HTML into a single component and reuse that component across your app. I actually think this is a good approach since everything is contained one place and we are still separating our concerns, but not separating our technologies.

In summary

Using @extend has issues but we can avoid them by structuring our classes to do very specific jobs and using mixins to DRY up our CSS.

I’m new to ReactJS and currently I haven’t been convinced that its approach will be adopted by other frameworks or platforms so for now having a strong CSS architecture is still a great advantage. Maybe less so if you’ve adopted ReactJS however.