Mastering Maps: Build a Flexible Variable System in Sass

Sass maps give you clearer, more flexible variables — learn how to make a map-powered stylesheet with some basic examples.

A few years ago, Miriam Suzanne turned me onto using maps for Sass variables with the excellent Sass Map Magic presentation. If you're not familiar with the syntax, a Sass map is a simple structure like this, accessed with the map-get function:

$colors: (
  text: #16140F,
  themes: (
    dark: (
      background: #1D1921,
      text: #B9A5CF,
    ),
    light: (
      background: #FFECC7,
      text: #16140F
    )
  )
);

color: map-get($colors, text);

Sass map highlights

Replacing your base variables with maps is easy, and it'll do a few things immediately:

  • Prevent name collisions.
    In big Sass systems, it's easy to accidentally write two vars with the same name, and Sass doesn't give you any warning once you've done it. Since these overwrites occur while parsing, duplicate names can create some hard-to-find bugs where only a few instances of a variable aren't behaving.
  • Simplify naming.
    Variable names tend to creep over time, starting with $body-color and growing into more-confusing name combinations like $body-color-modal-light. A good map system groups variables in progressively deeper trees instead of just name-concatenation, which results in clearer and more consistent names.
  • Group like values together.
    Sass vars for an idea like "color" can be spread across files or organized randomly — in a codebase, it can be hard to tell exactly what colors might be in use and where to find them. Maps help out by enforcing a pattern that groups them into a single object, and if need be, groups further into sub-objects.

Building a basic, nested accessor

Let's say you have a number of UI elements with managed z-indexes across your app. Since z-indexes are relative to others in the stack, a complex z-index stack can be hard to parse — where are these values being set? Why are they so high on modals? Why is there a 5 and a 7, but not a 6?

Z-indexes are a great case for maps, simplifying your whole stack into a nice tree:

$z-indexes: (
  global: (
    nav: 1000,
    modal: 1001,
    hoverContent: 1002,
  ),

  sidebar: (
    body: 1,
    actionBar: 2,
    header: 3
  )
);

Once you have this, you can access a value with z-index: map-get(map-get($z-indexes, sidebar), header);. That's pretty ugly, so give it an accessor function:

@function z-index($keys...) {
  $value: $z-indexes;

  @each $key in $keys {
    $value: map-get($value, $key);
  }

  @return $value;
}

Now you can access z-indexes with z-index: z-index(sidebar, body);. Much better!

Why not a mixin?

With a task as simple as setting a z-index, a mixin might sound like a better choice than a function. I prefer functions for a few reason:

  • Mixins can contain additional CSS rules and logic, making them more powerful. This comes at a cost: In order to trust a mixin called z-index, you have to verify that it does indeed set the z-index and do nothing else. A function is clearer — it takes an input and returns an output, every time.
  • The value a function returns can be manipulated and combined with other values, letting you write code like z-index: z-index(global, nav) + 1 when you need to. Since mixins only insert complete CSS rules, you can't work with their values this way.

My rule of thumb is use mixins to manage multiple lines of CSS at once, use functions for everything else.

Putting math in your maps

When building interfaces in CSS, you sometimes need to reference and extend global values to match a particular spec. For example:

.gallery {
  max-width: size(contentWrapper, maxWidth) + (size(padding) * 2);

  .next {
    height: size(gallery, height) - 2px;
    right: size(padding) / 2;
  }
}

In some codebases, this inline math can become cumbersome to read and write, and easy to get wrong (especially when dealing with lots of breakpoints). Since size is always going to be a map of some kind of unit, an accessor can be extended to allow basic math, by passing units instead of strings:

@function size($keys...) {
  $value: $sizes;

  @each $key in $keys {
    // if key is a number, assume we want to do some math with it
    @if (type-of($key) == number) {

      // a unitless number (0.6, -1, 5) should multiply the current value
      @if (unitless($key) == true) {
        $value: $value * $key;

      // otherwise, assume the number should be added
      } @else {
        $value: $value + $key;
      }
    }

    // otherwise, assume it's a string, and keep recursing as usual
    } @else {
      $value: map-get($value, $key);
    }
  }

  @return $value;
}

New we can clean up the above Sass a little bit:

.gallery {
  max-width: size(contentWrapper, maxWidth, size(padding, 2));

  .next {
    height: size(gallery, height, -2px);
    right: size(padding, 0.5);
  }
}

Next steps

Thanks for reading! If you use Sass, I hope this article gave you a few ideas for managing your variables.

Once you have a map system, it's easy to keep extending it with convenient features. A few more ideas:

  • Throwing errors and auto-suggesting keys — to see this in action, open the editor and click the red exclamation mark in the bottom right corner.
  • Sass Map Magic has a clever technique for writing map that can copy and augment their own values when accessed (see the "Possible Solution?" slide). That sounds abstract, but it's very useful when you have an existing value (darkGray: #bbbbbb) and want to create a common variant (mediumGray: darkGray (adjust-lightness 15%)).
  • Generate All Your Utility Classes with Sass Maps (Sarah Dayan, fronstuff.io) is a guide to using maps to construct your atomic helper classes.

Further reading

  • Advanced Use of Sass Maps (Nenad Novaković, ITNEXT) covers more map features, and describes a function that uses dot.separated.keys instead of argument lists for map access (neat!).
  • Extra Map Functions describes some helpful tools you can use when working with maps.
  • postcss-map, a plugin for using some basic map features in PostCSS.