Write More With Less (well actually with Sass)


By

A few months ago, we at Tailor Brands decided to make a concerted effort to improve the consistency of our site. Our codebase had grown to over 50 files and littered among them were bunches of font sizes, dozens of different colors, and a mess of other inconsistencies. The inconsistencies meant that our design and tech teams had to spend extra time figuring out sizes and colors and so on.

So, we developed a living styleguide. We converted sizes and colors to variables. We moved duplicated styles into more reusable classes. Within a few weeks, our codebase was much more manageable.

There was still one big area that needed to be cleaned up and made consistent: our spacing. We had margins and paddings that were all over the place; some were in pixels, some in ems, almost all were different values. We ended up solving all these issues by using a few Sass features together. The result is that our spacing is now uniform and the code used to generate it is smaller and centralized in a single place.

This article walks through how you might accomplish something similar. We’ll take it step by step and look at how you can use some of Sass features to do most of the heavy lifting.

Let’s start by looking at how you might implement spacing classes in plain CSS:

.padding-sm {
  padding: 1rem;
}

.padding-md {
  padding: 1.7rem;
}

.padding-lg {
  padding: 2.8rem;
}

.margin-sm {
  margin: 1rem;
}

.margin-md {
  margin: 1.7rem;
}

.margin-lg {
  margin: 2.8rem;
}

Okay great! That didn’t take long and it’s not that much code, but there are a few shortcomings to this solution. First, because we hardcoded all our values, if we want to change small (sm) from 1rem to 0.8rem then we have to change it in both the padding and margin class.

Let’s fix this issue by using our first Sass feature: variables. Here’s what the same code would look like using variables:

$sm: 1rem;
$md: 1.7rem;
$lg: 2.8rem;

.padding-sm {
  padding: $sm;
}

.padding-md {
  padding: $md;
}

.padding-lg {
  padding: $lg;
}

.margin-sm {
  margin: $sm;
}

.margin-md {
  margin: $md;
}

.margin-lg {
  margin: $lg;
}

By using variables, if we need to make a change in the future, we do it once (at the variable assignment) and we’re done. These classes, however, aren’t that useful. What if we want to have these margins and paddings to only apply to a specific side (e.g. top, right, bottom, or left)? We’d have to have something like this:

.padding-top-sm {
  padding-top: $sm;
}

.padding-top-md {
  padding-top: $md;
}

…you get the idea. If you were doing padding and margins for each individual side and one for all sides at each of the three sizes, you would be writing 30 classes. That’s not too crazy for now, so we’ll keep going.

Let’s see if we can simplify by introducing a mixin. The Sass Language Docs describes mixins as “groups of CSS declarations that you want to reuse throughout your site.” Basically they’re functions that spit out CSS. Let’s look at how we’d do our padding with a mixin:

@mixin padding($top, $right, $bottom, $left) {
  padding: $top $right $bottom $left;
}

Let’s go over what’s happening in this code. First, we’re telling Sass that we’re creating a mixin by using the @mixin directive. Next, we give the mixin a name, “padding”, and then define a set of arguments. Sass allows for either positional or keyword arguments. Above, I’m using positional elements so that I can pass along values for each side. That way we can have spacing classes that work on a single side. Mixins, like functions, don’t do much until you call them. Calling a mixin in Sass looks like this:

.padding-top-sm {
  @include padding($sm, 0, 0, 0);
}

And, ultimately, Sass would spit out our CSS:

.padding-top-sm {
  padding: 1rem 0 0 0;
}

While this CSS is the same length as the pre-mixin approach, there is one important difference: padding-top: 1rem; and padding: 1rem 0 0 0; aren’t the same. These classes will be more useful if they don’t overwrite the values of other sides. We can accomplish this fairly easily using keyword arguments:

@mixin padding($top: null, $right: null, $bottom: null, $left: null) {
  padding-top: $top;
  padding-right: $right;
  padding-bottom: $bottom;
  padding-left: $left;
}

Now, if a value is passed in, it will be used on the appropriate side. If no value is passed in, it remains null and as padding-top: null; isn’t valid CSS, Sass won’t even render it. Now our use of the mixin would look like this:

.padding-top-sm {
  @include padding(top: $sm);
}

And our CSS would come out looking like this:

.padding-top-sm {
  padding-top: 1rem;
}

Okay, we fixed the first issue with our mixin-based approach. While this won’t be any shorter of code, it will be easier to add to. That’s where mixins really shine. Let’s say that we wanted our spacing to only show up on for both horizontal sides. That’s pretty easy with what we have:

.padding-hsides-sm {
  @include padding($left: $sm, $right: $sm);
}

That would produce this CSS:

.padding-hsides-sm {
  padding-left: 1rem;
  padding-right: 1rem;
}

For an extra level of control over our spacing classes, we might want to make it so they only show at certain screen sizes. To do this, we’ll need to extend our mixin a little more:

@mixin padding($top: null, $right: null, $bottom: null, $left: null, $media: null) {
  @if $media {
    @media #{$media} {
      padding-top: $top;
      padding-right: $right;
      padding-bottom: $bottom;
      padding-left: $left;
    }
  } @else {
    padding-top: $top;
    padding-right: $right;
    padding-bottom: $bottom;
    padding-left: $left;
  }  
}

We added a new keyword argument called $media and set it to be null by default. Then we used Sass @if and @else to check to see if $media. If it does, we interpolate the variable and pass the string into CSS’s @media declaration. Using this might look something like this:

$sm-only: "only screen and (max-width: 560px)";

.padding-top-sm.sm-only {
  @include padding($top: $sm, $media: $sm-only);
}

Which will produce this CSS:

@media only screen and (max-width: 560px) {
  .padding-top-sm.sm-only {
    padding-top: 1rem;
  }
}

Just like that, you have the ability to easily make your spacing classes responsive.

There’s one more Sass feature we can use to make these mixins more versatile: maps. Maps allow us to store a list of keys and values. Let’s look at how maps can make our spacing CSS robust. First, let’s re-write the variable declarations for the sizes as a map:

$sizes: (
  sm: $sm,
  md: $md,
  lg: $lg
);

Now, the way we’ll use this is by using Sass’s built in iterator:

@each $name, $value in $sizes {
  .padding-top-#{$name} {
    @include padding($top: $value);
  }
}

That Sass would produce this CSS:

.padding-top-sm {
  padding-top: 1rem;
}

.padding-top-md {
  padding-top: 1.7rem;
}

.padding-top-lg {
  padding-top: 2.8rem;
}

This approach would make it very easy for us to add in extra sizes (e.g. xs or xl). The process could also be repeated for our media queries, so that we’d have spacing classes that only get applied on medium or large size screens.

Conclusion

If your CSS is a big, unwieldy mess of inconsistent values then it’s probably time to start using Sass. Sass can have a big impact on the size, consistency, and extendability of your code. And Sass mixins–especially when combined with maps–are a great way to take some of the dirty work out of writing similar classes without writing a ton of nearly duplicate classes by hand.

Have any other fun uses for mixins or maps/iterators? Let us know in the comments.


Top
This page may contain affiliate links. At no extra cost to you, we may earn a commission from any purchase via the links on our site. You can read our Disclosure Policy at any time.