Using Sass Maps

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.

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?

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • Mitch Powell

    In Fig. 1, lines 17 and 18, there are three double-quotes (an uneven number.) I couldn’t move past that. Can you explain?

    • http://hugogiraudel.com/ Hugo Giraudel

      I am not sure what you mean. Second code block, line 17 and 18 are 2 concatenated strings.

      • Alex Wright

        Hi Hugo – I think he’s saying here:

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

        In the figure, it’s not immediately obvious that there is more code to the right, hidden out of view. At first glance, it appears there is no closing double quote before the + “Please…

        Cheers – and thanks for the wonderfully informative article!

  • Marcel Tschopp

    Good article, thanks!
    Would be nice if we could do something like
    background-color: $configuration[color];
    instead of
    background-color: map-get($configuration, color);

    • http://hugogiraudel.com/ Hugo Giraudel

      This is not something Sass maintainers have in mind. Since CSS mostly uses functions instead of programming-like syntax, I hardly think we will ever see this.

      That being said, it is possible to make a “dot accessor” function, so you could do `.(“configuration.color”)` or something like that.

      • Marcel Tschopp

        Too bad :-)

        I don’t think a “dot accessor” function would give me more readability. Better use the original function.

        • http://hugogiraudel.com/ Hugo Giraudel

          Certainly.

  • http://www.12snaps.com/ Paweł Grzybek

    Very nice article Hugo! Finally I stopped to be scared of brand new maps in sass. I really like the way how you use it in breakpoints mixin. Thanks you for well explanation!

  • Craig Buckler

    Great article, Hugo. I wish they’d named them something other than Sass Maps, which sounds suspiciously like ‘source maps’!

  • Jayshua

    How does your last example differ from simply using sass’s extend (perhaps with % placeholder)? Why not something like:

    %component {
    padding: 1em;
    margin: 0 1em;
    color: grey;
    }

    .element {
    @extend %component;

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

    • http://hugogiraudel.com/ Hugo Giraudel

      You have to consider my last example as part of a bigger structure. But yes, your code does pretty much the same thing. :)

  • http://cofoh.com/ Tomasz Wyderka

    Good text Hugo! But as you noticed at the end “It’s not much”, I also think that hash maps will be rarely used. Simply it’s too powerful tool for CSS structure. Even some programming languages don’t have them loaded by default.

    • http://hugogiraudel.com/ Hugo Giraudel

      Well, I don’t want to use those programming languages then. Maps are not too good for CSS, trust me. ;)

  • Matt Anderson

    I had previously read your article about managing responsive breakpoints, but something about the method bothers me: don’t you end up with multiple identical media queries, one for each time you use the method? I’d prefer to have one media query that provides all the rules for that breakpoint.

    • http://hugogiraudel.com/ Hugo Giraudel

      I will tell you the same thing I told to someone on the other article: http://www.sitepoint.com/managing-responsive-breakpoints-sass/#comment-1464605676.

      There is little to no difference between combining your media queries and leaving them splitted once Gzip has done its job. You are probably not without knowing that Gzip does a tremendous work on repeated string. This is a repeated string, and it got smushed as hell.

      Also having a single media query for a given breakpoint is unthinkable on medium to large scale projects. An architecture is made of components: each component should have all its styles in the same place, including responsive stuff.

      Now, if you really want to have your media queries combined, use a tool for that, for instance a gem running after Sass. But I wouldn’t recommand it since it can mess with source order, hence your CSS.

      • Matt Anderson

        Very interesting, thanks HG.

  • Bruno de Almeida

    Great post Hugo.

    This is all very functional, but would like to help.
    Using your example generated an error. look error

    assets/sass/application.scss (Line 48 of assets/sass/temp/_participantes.scss: Invalid CSS after ” padding”: expected “)”, was “: 1em,”)

    I’m using version
    sass –v
    Sass 3.3.9 (Maptastic Maple)

    I do not understand why the error. Thanks for help

    • http://hugogiraudel.com/ Hugo Giraudel

      Could you please reproduce the issue on SassMeister so I can have a look?

      • Bruno de Almeida

        I’m sorry to have to take your time with this stupid question I did. Was seeing emails and found your answer, but when I saw the question I asked you, he will be playing the window. I did not even read the message.

        I really am very sorry for that.

        Request permission to delete this question?

  • Joachim Tillessen

    Inspirational article Hugo. I can’t wait to get started with sass maps. I would like to point out one little detail though, maps were not introduced with sass version 3 but with version Sass 3.3 (aka Maptastic Maple). This makes a difference to quite a lot of people since rails-sass does not support this version yet. So unless they want to use teh gem straight from the master branch frontend developer from the rails realm will have to wait for sass maps a little longer.

    • http://hugogiraudel.com/ Hugo Giraudel

      Don’t know how I could miss that. Thanks Joachim.