HTML & CSS
Article
By Hugo Giraudel

Using Sass Maps

By Hugo Giraudel

The third major version of Sass brought a new data type called map. While you might be unfamiliar with this name, we already use maps often in other languages, usually under the names of associative arrays, or hashes. In other words, a Sass map is an array matching keys to values.

It is not always clear why we need maps in CSS (yes, Sass is CSS), hence this article, to give you some hints. This list is obviously not exhaustive so feel free to find and share other use cases.

Syntax for Sass Maps

Before going any further, let’s level everyone up on the topic.

A Sass map uses parentheses as external delimiters, colons to map keys and values, and commas to separate key/value pairs. Here is a valid Sass map:

$map: (
  key: value,
  other-key: other-value
);

A few things you need to know:

Yes, keys are not necessarily strings; they can be anything. Even null. Even maps. Chris Eppstein, Micah Godbolt, Jackie Balzer, and I had a conversation about this on Twitter a couple of weeks ago. Having kind of a JavaScript background, I don’t feel comfortable with having anything other than a string as a hash key. Anyway, that’s possible in Sass.

Okay, now you should be ready to go!

Project Configuration

Let’s start with a common use case. Maps are perfect when it comes to project configuration. The idea is simple: You associate values to keys, then you access them from anywhere in the project using map-get($map, $key).

I already dug into this in my article on managing responsive breakpoints in Sass. Here’s a quick example:

// _config.scss
$breakpoints: (
  small: 767px,
  medium: 992px,
  large: 1200px
);

// _mixins.scss
@mixin respond-to($breakpoint) { 
  @if map-has-key($breakpoints, $breakpoint) {
    @media (min-width: #{map-get($breakpoints, $breakpoint)}) {
      @content;
    }
  }

  @else {
    @warn "Unfortunately, no value could be retrieved from `#{$breakpoint}`. "
        + "Please make sure it is defined in `$breakpoints` map.";
  }
}

// _component.scss
.element {
  color: hotpink;

  @include respond-to(small) {
    color: tomato;
  }
}

Resulting in:

.element {
  color: hotpink;
}

@media (min-width: 767px) {
  .element {
    color: tomato;
  }
}

So this is pretty cool. Another popular use case is colors. Distinct variables for colors are fine, but it can become messy when you have dozens of those. Having a map just for colors, possibly divided into sub-maps for themes, could be a good idea.

// _config.scss
$colors: (
  sexy: #FA6ACC,
  glamour: #F02A52,
  sky: #09A6E4
);

// _component.scss
.element {
  background-color: map-get($colors, sky);
}

Eventually, you’ll get tired of repeating map-get($colors, ...) over and over so you can use a little helper function to ease your pain:

// _functions.scss
@function color($key) {
  @if map-has-key($colors, $key) {
    @return map-get($colors, $key);
  }

  @warn "Unknown `#{$key}` in $colors.";
  @return null;
}

// _component.scss
.element {
  background-color: color(sky); // #09A6E4
}

On topic, you might want to read this article by Erskine Design.

Let’s have a look at a last use case for maps as configuration before moving on: z-index layers. I think this first came from Chris Coyier when he wrote about how he manages z-index in Sass projects. You know how you use to write z-index: 999999999? Well, those days are over.

// _config.scss
$z-layers: (
  bottomless-pit: -9999,
  default: 1,
  dropdown: 3000,
  overlay: 4000
  modal: 4001
);

// _functions.scss
@function z($key) {
  @if map-has-key($z-layers, $key) {
    @return map-get($z-layers, $key);
  }

  @warn "Unknown `#{$key}` in $z-layers.";
  @return null;
}

// _component.scss
.overlay {
  z-index: z(overlay);
}

.element {
  z-index: (z(default) + 1);
}

Given how messed up z-index can be, I think it is a good thing to gather all indexes into a single map, and stick to them. Then you know how many layers the application uses, making it easy to understand what’s going on (and, sooner or later, to debug).

Module Configuration

We will stick to configuration but let’s get deeper into application levels, tackling module/component configuration. I already wrote about using Sass maps as configuration objects and why it can be interesting to ditch the multi-arguments signature in favor of a single map parameter. Quick proof of concept:

// _mixins.scss
@mixin module($options: ()) {
  $configuration: map-merge((
    color: grey,
    duration: 0s,
    border: null
  ), $options);

  color: map-get($configuration, color);
  transition: map-get($configuration, duration);
  border: map-get($configuration, border);
}

// _component.scss
.element {
  @include module((
    color: pink,
    duration: .15s
  ));
}

One problem with this solution is it makes the mixin signature terribly generic. In real life, a signature is a way to identify a person. When reading a function/mixin signature, we should be able to identify what arguments are expected. With this approach, it is not, because you have to read the default options in the mixin to know what’s going on.

To work around the problem, define the mixin with a verbose signature (regular multiple arguments) then include it with a single configuration map using .... Let’s rewrite our previous example:

// _mixins.scss
@mixin module($color: grey, $duration: 0s, $border: null) {
  color: $color;
  transition: $duration;
  border: $border;
}

// _component.scss
.element {
  @include module((
    duration: .15s,
    color: pink
  )...);
}

In this case, ... expands the map of values so that each pair is treated as a keyword argument. Not only does it make the mixin signature more explicit, but it also ditches all the map-merging stuff previously required at the top of the mixin’s core.

So this is how you could use Sass maps to configure your mixins that ask for multiple arguments. If I’m not mistaken, Python is one of the few (well-known) languages that allows you to call a function with a multi-dimensional array while it asks for multiple arguments. So the fact Sass is able to do so is pretty awesome.

--ADVERTISEMENT--

Repetitive Stuff

Until now, we have used maps in order to get the value associated to a specific key. A whole other kind of use case for Sass maps is avoiding repetitive stuff. CSS being a very WET (Write Everything Twice) language, code repetitions are common. This is where having a loop and a list/map helps a lot in making code DRY (Don’t Repeat Yourself).

Think of icon fonts. An icon font is usually a collection of encoded characters passed via the content property of the :before pseudo-element. For instance:

.fa-glass:before {
  content: "\f000";
}
.fa-music:before {
  content: "\f001";
}
.fa-search:before {
  content: "\f002";
}
.fa-envelope-o:before {
  content: "\f003";
}
.fa-heart:before {
  content: "\f004";
}

/* ... */

Wow. Very repetition. Much WET. What if instead we mapped class names to the encoded characters?

$icons: (
  glass: "\f000",
  music: "\f001",
  search: "\f002",
  envelope-o: "\f003",
  heart: "\f004"
);

@each $name, $icon in $icons {
  .fa-#{$name}:before {
    content: $icon;
  }
}

Better, isn’t it? Especially when you have dozens and dozens of icons in the font. If you want to push things further, Frédéric Perrin created a cool little demo using this very idea on CodePen. There is also an article from ThoughtBot using the same approach with background images.

CSS Dump

Here’s another use case (first introduced by Micah Godbolt, if I’m not mistaken). We will use a Sass map as a list of CSS declarations. You see, Sass maintainers do their best to stick as close as possible to official CSS syntax when implementing new features. This is why they decided to use () for lists/maps (used in media queries) and the colon as a mapping operator (used in any declaration).

Because Sass maps look like lists of CSS rules, you could possibly have a mixin dumping the content of a Sass map:

// _mixins.scss
@mixin print($declarations) {
  @each $property, $value in $declarations {
    #{$property}: $value
  }
}

// _component.scss
.element {
  @include print((
    margin: 0 auto,
    max-width: 50%,
    overflow: hidden
  ));
}

Now you might be thining Why? Why not just write the CSS manually from the start rather than passing it through a mixin? It is all about context. When dealing with a module or something, you usually have a map storing a couple of variables laying down how the component should behave. For instance:

// _component.scss
$configuration: (
  padding: 1em,
  margin: 0 1em,
  color: grey
);

.element {
  color: map-get($configuration, color);
  padding: map-get($configuration, padding);
  margin: map-get($configuration, margin);

  &::before {
    background-color: map-get($configuration, color);
  }
}

Rather than manually getting each value from the key, you could simply print the map with the css() mixin;

// _component.scss
.element {
  @include print($configuration);

  &::before {
    background-color: map-get($configuration, color);
  }
}

Resulting in:

.element {
  padding: 1em;
  margin: 0 1em;
  color: grey;
}

.element::before {
  background-color: grey;
}

It’s not much, but it’s something!

Final Thoughts

As you can see, the Sass maps feature is a powerful tool. Not only does it make the code more structured, but it also helps you avoid some code bloat and repetition. What about you, how do you use Sass maps?

Recommended
Sponsors
The most important and interesting stories in tech. Straight to your inbox, daily. Get Versioning.
Login or Create Account to Comment
Login Create Account