Using Nested Sass Maps for TypeSetting

Published

July 7, 2014

Reading time
5 min read

If you've written much CSS then you've probably gotten into the situation where multiple styles are repeated throughout your project. Trying to make a global change in that environment can become VERY cumbersome to say the least.

// ----
// Sass (v3.3.9)
// Compass (v1.0.0.alpha.20)
// Scut (v0.10.4)
// ----

@import "scut";

h1 {
  font-size: scut-em(48px);
  font-weight: 800;
}

h2 {
  font-size: scut-em(16px);
  font-weight: 700;
  line-height: scut-em(22px, 16px);
}

p {
  font-size: scut-em(16px);
  line-height: scut-em(24px, 16px);
}

// _hero.scss
.Hero {
  .Hero-heading {
    font-size: scut-em(16px);
    font-weight: 700;
    line-height: scut-em(22px, 16px);
  }
}

The above example defines a group of type settings over and over again across multiple rule sets. You'll most likely find that as your stylesheets get larger and more complex it will become increasingly more difficult to maintain something like this.

Using Sass Placeholder Selectors

One way to solve this problem is to define unique a placeholder selector for each Type Setting configuration. These placeholder selectors aren't meant to used by your HTML directly, but rather to be extended with Sass.

// ----
// Sass (v3.3.9)
// Compass (v1.0.0.alpha.20)
// Scut (v0.10.4)
// ----

@import "scut";

%primary-heading {
  font-size: scut-em(48px);
  font-weight: 800;
}

%secondary-heading {
  font-size: scut-em(16px);
  font-weight: 700;
  line-height: scut-em(22px, 16px);
}

%body-copy {
  font-size: scut-em(16px);
  line-height: scut-em(24px, 16px);
}

h1 {
  @extend %primary-heading;
}

h2 {
  @extend %secondary-heading;
}

p {
  @extend %body-copy;
}

// _hero.scss
.Hero {
  .Hero-heading {
    @extend %secondary-heading;
  }
}
Play with this gist on SassMeister.

This technique allows you to @extend the appropriate Type Setting where ever you might need it. The nice thing here is that within the .Hero-heading rule set you can reuse the predefined %secondary-heading placeholder selector.

This technique (and those below) can be helpful if you have defined a set of approved font guidelines (size, weight, etc) and you want to abide by them across your web application.

Using Sass 3.2 Nested Lists

Instead of using placeholder selectors, we could refactor the above Sass and write a custom mixin with nested lists to define our various type settings.

The typeset mixin has a parameter of $level that is defaulted to body-copy if not provided. The $types variable stores a nested list containing the type settings for primary-heading, secondary-heading, and body-copy. The mixin loops over the list looking for a match to the passed in $level and then picks apart the type metatdata for that entry.

// ----
// Sass (v3.2.19)
// Compass (v0.12.6)
// Scut (v0.10.4)
// ----

@import "scut";

@mixin typeset( $level: body-copy ) {
  $types: (
    ( primary-heading,   48px, 800 ),
    ( secondary-heading, 16px, 700,    22px ),
    ( body-copy,         16px, normal, 24px )
  );

  @each $item in $types {
    @if $level == nth($item, 1) {
      font-size: scut-em(nth($item, 2));
      @if nth($item, 3) != "normal" {
        font-weight: nth($item, 3);
      }
      @if length($item) >= 4 {
        line-height: scut-em(nth($item, 4), nth($item, 2));
      }
    }
  }
}

h1 {
  @include typeset(primary-heading);
}

h2 {
  @include typeset(secondary-heading);
}

p {
  @include typeset(body-copy);
}

// _hero.scss
.Hero {
  .Hero-heading {
    @include typeset(secondary-heading);
  }
}
Play with this gist on SassMeister.

This solution isn't very elegant, but it is much more powerful and flexible than the previous placeholder selector version. One of the downsides of this approach is the rigid nature of placing CSS properties at hard-coded indexes (example: font-size at the 2nd index of each nested list). In addition the above mixin is getting somewhat complex and could be difficult to grok over time.

Using Sass 3.3 Maps

Version 3.3 of Sass included a new data structure called the map. This enables you to organize data with a familiar key/value type of structure. Along with the map feature came a slew of helper functions that are very helpful.

The following Sass snippet is a refactored version of the above typeset mixin, but this time using nested maps.

// ----
// Sass (v3.3.9)
// Compass (v1.0.0.alpha.20)
// Scut (v0.10.4)
// ----

@import "scut";

@mixin typeset( $type: body-copy ) {
  $types: (
    primary-heading: ( font-size: 48px, font-weight: 800 ),
    secondary-heading: ( font-size: 16px, font-weight: 700, line-height: 22px ),
    body-copy: ( font-size: 16px, line-height: 24px ),
  );

  $type: map-get($types, $type);
  font-size: scut-em(map-get($type, font-size));
  @if map-has-key($type, font-weight) {
    font-weight: map-get($type, font-weight);
  }
  @if map-has-key($type, line-height) {
    line-height: scut-em(map-get($type, line-height), map-get($type, font-size));
  }
}

h1 {
  @include typeset(primary-heading);
}

h2 {
  @include typeset(secondary-heading);
}

p {
  @include typeset(body-copy);
}

// _hero.scss
.Hero {
  .Hero-heading {
    @include typeset(secondary-heading);
  }
}
Play with this gist on SassMeister.

Thanks to the map-get and map-has-key functions the above mixin code is dramatically cleaner than the previous nested list version. I've enjoyed playing with the new map data type and have found it be very powerful.

Conclusion

Whatever solution you use, I encourage you to treat your Sass as you would your code. Think modularity, reusability, and maintainability!

Web Mentions
0
0

Tweet about this post and have it show up here!