Taming typography using Sass maps

Working on a project with many designers and engineers can make it difficult to manage type styles. I find that we often end up with different sizes or weight definitions littered throughout the project with a lot of repetition as styles are redefined within media queries.

On a recent large project at Friday we took the time to thoroughly plan our approach. The result is a simple set of Sass maps and mixins for defining and exposing and consuming typographic rules.

I started by assigning short names to the font families being exposed from our @font-face declarations. This is neatly enumerated into a map as follows:

$font-families: (
  'light': 'Avenir Light',
  'roman': 'Avenir Roman',
  'roman-oblique': 'Avenir Roman Oblique',
  'black': 'Avenir Black',
  'black-oblique': 'Avenir Black Oblique'
);

We had already agreed on the type size hierarchy (12–72px) so these are also stored in a map with short names along with specific leading rules for each size.

$font-sizes: (
  'xxsmall': (font-size: 12, line-height: 1.333),
  'xsmall': (font-size: 14, line-height: 1.333),
  'small': (font-size: 16, line-height: 1.333),
  'medium': (font-size: 18, line-height: 1.4),
  'large': (font-size: 24, line-height: 1.25),
  'xlarge': (font-size: 30, line-height: 1.2),
  'xxlarge': (font-size: 36, line-height: 1.2),
  'xxxlarge': (font-size: 48, line-height: 1.2),
  'massive': (font-size: 60, line-height: 1.2),
  'mega': (font-size: 72, line-height: 1.1)
);

I then access these maps through some simple mixins, for example:

@include font('light', 'medium', $caps:true);
// font-family: 'Avenir Light';
// font-size: 1.8rem;
// text-transform: uppercase;

@include font-family('black');
// font-family: 'Avenir Black';

@include font-size('small');
// font-size: 1.6rem;

Conversations with our designers quickly revealed that Avenir needed tweaking at certain sizes. At large sizes the light and black weights need tighter kerning. No problem. Another map to the rescue where we can define properties to tweak at specific size/weight combinations:

$font-tweaks: (
  'light-mega': (letter-spacing: -.06em, text-indent: -2px),
  'black-large': (letter-spacing: -.02em),
  'black-xlarge': (letter-spacing: -.02em),
  'black-xxlarge': (letter-spacing: -.03em),
  'black-massive': (letter-spacing: -.03em),
  'black-mega': (letter-spacing: -0.03em, text-indent: -3px)
);

The full mixin source for your delectation:

@mixin font-family($font-cut) {
  font-family: map-get($font-families, $font-cut);
}

@mixin font-size($size) {
  $font-size-properties: map-get($font-sizes, $size);

  @if $font-size-properties {
    @include font-size(map-get($font-size-properties, font-size));
  } @else {
    font-size: #{$size}px;
    font-size: #{$size / 10}rem;
  }
}

@mixin font($font-cut, $font-size, $caps: false) {
  $font-size-properties: map-get($font-sizes, $font-size);
  $tweaks: map-get($font-tweaks, #{$font-cut}-#{$font-size});

  @include font-size(map-get($font-size-properties, font-size));

  font-family: map-get($font-families, $font-cut);
  line-height: map-get($font-size-properties, line-height);

  @if $tweaks {
    @each $tweak-property, $tweak-value in $tweaks {
      #{$tweak-property}: #{$tweak-value};
    }
  }

  @if $caps == true {
    text-transform: uppercase;
  } @else {
    text-transform: none;
  }
}

By enumerating these decisions with short names we have established a common vocabulary for chatting about our chosen type styles. Happy designers, happy engineers, happy days.