Forget Sass Variables: Upgrading to Maps + Functions

Doug Avery, Former Senior Developer

Article Category: #Code

Posted on

Sass maps are a powerful feature that allow you to build deep, intelligent objects that represent the information in your CSS. Because they can be looped, merged, and parsed in such smart ways, it's easy to see how maps can be used for one thing or another on almost any project.

But instead of "one thing or another", what if you used maps for everything? In the past few months, I've completely stopped using plain variables when I can, and instead switched to maps and accessor functions. That means that instead of:

$breakpoint-small : 600px;
$breakpoint-big : 1000px;

@media (min-width: $breakpoint-big) {
  ...
}

I write:

$breakpoints: (
  small : 600px,
  big   : 1000px
);

@function breakpoint($key) {
  @return map-get($breakpoints, $key);
}

@media (min-width: breakpoint(big)) {
  ...
}

Why would I do this? The second example is certainly bulkier and has more moving parts, and arguably, it's harder to understand than basic variables. Still, the map/function pattern provides a lot of advantages that helped me justify a wholesale switch:

Looping

When you have a map, you have a loop. @each $color, $value in $colors, for example, returns a loop of your colors, and with a little tweaking, you have classes suitable for atoms or a parts kit:

@each $color, $value in $colors {
  .bg-#{$color} {
    background: $value;
  }

  .fg-#{$color} {
    color: $value;
   }
}

This rule can extend to sizing or transition speeds, and you can even get clever with breakpoints (codepen).

@each $breakpoint, $px in $breakpoints {
  @media (min-width: $px) {
    body:after {
      content: '#{$breakpoint} at #{$px} triggered.';
      padding: 10px;
    }
  }
}

This kind of power is great for a parts-kit.css file - you can list out all your colors, breakpoints, transition speeds, and icons with a couple of loops and some markup.

Transforming

The other half of the equation is the function. In my first example, the function is pretty dumb — it's just aliasing map-get() for us. But what if we gave the function a little more power?

$sizes: (
  gutter : 20px,
  column : percentage(12/100)
);

@function size($key, $multiplier: 1) {
  @return map-get($sizes, $key) * $multiplier;
}

Suddenly, we can write size(gutter, 0.5) and size(column, 6) to return multiples and fractions of the value. This gets more powerful when you combine it with complex color schemes:

// this example uses https://github.com/heygrady/scss-blend-modes
// which is a blast to generate color schemes with

@function color($key: lime, $variant: false) {
  $cool-black : #0A0A33;
  $color : map-get($colors, $key);

  @if $variant == light {
    $color: saturate(blend-screen(rgba(#fff, 0.4), $color), 25%);
  }

  @if $variant == dark {
    $color: blend-multiply(rgba($cool-black, 0.5), $color);
  }

  @if $variant == darker {
    $color: blend-multiply(rgba($cool-black, 0.85), $color);
  }

  @return $color;
}

Eric Suzanne takes this idea much, much farther in his presentation Map Magic, which I highly recommend.

Generating

Another advantage of maps is that, unlike variables, you can generate them programmatically. Consider generating font sizes with a type scale that increments by 1.5:

$font-sizes: ();
$current-size: 12px;

@for $i from 1 through 10 {
 $size: ($i: $current-size);
 $current-size: $current-size * 1.5;
 $font-sizes: map-merge($font-sizes, $size);
}

@function font-size($key) {
  @if map-has-key($font-sizes, $key) {
    @return map-get($font-sizes, $key);
  }

  @warn "Unknown font-size '#{$key}'";
  @return null;
}

Summary

In short: Try trading out your global sets of variables for maps. You'll find that, over time, maps just give you more bang for your Sass buck.

Related Articles