Exploring Maps In Sass 3.3(Part 3): Calling Variables with Variables

Micah Godbolt, Developer
#Mapping | Posted

For this blog entry, the third in a series about Sass Maps, I am going to move away from niche application, and introduce some more practical uses of maps.

Living Style Guides

In my current project, a large Drupal media site,  I wanted to have a style guide, a single static page where we could see all of the site colors, along with the variable name. I collected all of my color variables, and created some static markup with empty divs. Below is the loop I started to write.

[xhtml]<!-- The HTML for our Style Guide -->
<div class="styleguide">
  <div class="primary-color"></div>
  <div class="secondary-color"></div>
  <div class="tertiary-color"></div>
  1. // Our site color variables
  2. $primary-color: #111111;
  3. $secondary-color: #222222;
  4. $tertiary-color: #333333;
  6. // Make a list of the colors to display
  7. $styleguide-colors: primary-color, secondary-color, tertiary-color;
  9. // Loop through each color name, create class name and styles
  10. @each $color in $styleguide-colors {
  11. .styleguide .#{$color} {
  12. background-color: $#{$color}; // Spoiler Alert: Does not work!!
  13. &:after {
  14. content: “variable name is #{$color}
  15. }
  16. }
  17. }

This loop goes through each color in my $styleguide-colors list and creates a class name based on the color name. It then attempts to set the background-color by calling a variable that matches the name from the list. We also set the content of a pseudo element to the variable name, so that our styleguide automatically prints out the name of the color.

This is what we want the first loop to return:

  1. .styleguide .primary-color {
  2.  background-color: $primary-color; // Nope, we won’t get this variable
  3.  &:after {
  4.      content: “variable name is primary-color
  5.    }
  6. }

The problem is that we can’t interpolate one variable to call another variable! $#{$color}  doesn’t actually work in Sass. It won’t interpolate into $ + primary-color , and then yield #111111  in the final CSS. This 3 year old github issue points out this exact issue, and hints at how maps is going to be introduced in Sass 3.3 to solve this problem. https://github.com/nex3/sass/issues/132

Make it better with maps

So now that we have maps, how can we create this color styleguide? Lets take this a step at a time.

First we need to wrap all of our colors in a map. Remember, any of these colors can be accessed like this: map-get($site-colors, primary-color)

  1. $site-colors: (
  2.  primary-color: #111111,
  3.  secondary-color: #222222,
  4.  tertiary-color: #333333,
  5. );

Now we can create a list of the colors we want to iterate through and loop through them just like we did before.

  1. $styleguide-colors: primary-color, secondary-color, tertiary-color;
  3. @each $color in $styleguide-colors {
  4.  .styleguide .#{$color} {
  5.    background-color: map-get($site-colors, $color); // This DOES work!
  6.    &:after {
  7.      content: “variable name is #{$color}
  8.    }
  9.  }
  10. }

This time when we loop through our colors we get the same class name and pseudo element content, but lets look at what happens with the background color. Here is the first pass through the loop, using primary-color as $color :

  1. .styleguide .primary-color {
  2.   background-color: map-get($site-colors, primary-color);
  3.   &:after {
  4.      content: “variable name is primary-color
  5.    }
  6. }

As you can see in this intermediate step, we are able to use map-get($site-colors, primary-color)  to programmatically pass our color name into a function, and get a returned value. Without maps we’d be stuck waiting for $#{$color} to be supported (which will probably never happen). Or in the case of my project, write all 20 site color classes out by hand!

Make it awesomer with maps

Astute readers might realize that I am still doing things the hard way. I created a map of colors, and then duplicated their names in a list called $styleguide-colors . We can skip that middle step and greatly simplify our code, if we are wanting to print out every single value in the map.

  1. $site-colors: (
  2. primary-color: #111111,
  3. secondary-color: #222222,
  4. tertiary-color: #333333,
  5. );
  7. @each $color, $value in $site-colors {
  8. .styleguide .#{$color} {
  9. background-color: $value;
  10. &:after {
  11. content: “variable name is #{$color}
  12. }
  13. }
  14. }

Now, instead of passing a list into the @each loop, we pass the entire map. We can do this with the following pattern: @each $key, $value in $map . Each iteration of the loop has access to both the key primary-color  AND the value #111111 , so we don’t even need the map-get function. The ability to 'call variables with variables’ is incredibly useful for creating these programmatic classes, and is a foundational process upon which we start to build more complex systems. Be sure to check out part 1 and 2 of my Sass Maps blog series!


Micah Godbolt