Using Nested Sass Maps for TypeSetting
July 7, 2014
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!
Tweet about this post and have it show up here!