Using SCSS mixins to render repetitive selectors

Door Thijs Kramer | Op 25-11-2016
In a project that I’m currently working on I needed per heading different font sizes and line heights per breakpoint.

I had the following things to keep in mind:

  • the designers provided me font-sizes defined in px while I wanted to use rem.
  • There were multiple themes, so the font sizes and line heights differ per theme.

At first I had to write a mixin that returned font-size and line-height properties, and converted pixel values to rem values:

@mixin fontsize-lineheight($font-size, $line-height, $base: 16px) {
    $remsize: ($font-size / $base);
    font-size: #{$remsize}rem;
    line-height: $line-height / $font-size;
}

However I wanted line-height to be unit-less, so I had to convert the variables to unit-less values using the following @function:

@function strip-units($value) {
    @return $value / ($value * 0 + 1);
}

(source: this Stackoverflow answer)

So the final result of the mixin was:

@mixin fontsize-lineheight($font-size, $line-height, $base: 16px) {
    $remsize: (strip-units($font-size) / strip-units($base));
    font-size: #{$remsize}rem;
    line-height: strip-units($line-height / $font-size);
}

Now this mixin was ready to use for every heading:

h1, .h1 {
    @include fontsize-lineheight(30px, 40px);
    @include media-breakpoint-up(md) {
        @include fontsize-lineheight(40px, 50px);
    }
    @include media-breakpoint-up(lg) {
        @include fontsize-lineheight(60px, 72px);
    }
}
// ....and so on

This still looks quite repetitive, so I made a map containing lists for the font sizes and line heights, like this:

// font-sizes, from left to right:
//      lg   md   sm
$heading-sizes: (
    h1: 72px 48px 36px,
    h2: 48px 32px 30px,
    ...
);
$heading-lineheights: (
    h1: 72px 48px 36px,
    h2: 48px 36px 30px,
    ...
);

Every entry in the map can be accessed with map-get, for example:

$font-size-h1: map-get($heading-sizes, h1);

And every entry in a list can be accessed by its index (1-based) using nth, for example:

$font-sizes-h1: map-get($heading-sizes, h1);
$font-size-h1-md: nth($font-sizes-h1, 2);

So all of the above can be combined into the following mixin:

@mixin heading($heading) {
    $sizes: map-get($heading-sizes, $heading);
    $line-heights: map-get($heading-lineheights, $heading);
    @include fontsize-lineheight(nth($sizes, 3), nth($line-heights, 3));
    @include media-breakpoint-up(md) {
        @include fontsize-lineheight(nth($sizes, 2), nth($line-heights, 2));
    }
    @include media-breakpoint-up(lg) {
        @include fontsize-lineheight(nth($sizes, 1), nth($line-heights, 1));
    }
}

So putting everything together the final result is:

@function strip-units($value) {
    @return $value / ($value * 0 + 1);
}
@mixin fontsize-lineheight($font-size, $line-height, $base: 16px) {
    $remsize: (strip-units($font-size) / strip-units($base));
    font-size: #{$remsize}rem;
    line-height: strip-units($line-height / $font-size);
}
@mixin heading($heading) {
    $sizes: map-get($heading-sizes, $heading);
    $line-heights: map-get($heading-lineheights, $heading);
    @include fontsize-lineheight(nth($sizes, 3), nth($line-heights, 3));
    @include media-breakpoint-up(md) {
        @include fontsize-lineheight(nth($sizes, 2), nth($line-heights, 2));
    }
    @include media-breakpoint-up(lg) {
        @include fontsize-lineheight(nth($sizes, 1), nth($line-heights, 1));
    }
}

$heading-sizes: (
    h1: 72px 48px 36px,
    h2: 48px 32px 30px,
    h3: 36px 30px 24px,
    h4: 30px 24px 20px,
    h5: 24px 20px 18px,
    h6: 20px 18px 16px
);
$heading-lineheights: (
    h1: 72px 48px 36px,
    h2: 48px 36px 30px,
    h3: 42px 36px 30px,
    h4: 36px 30px 24px,
    h5: 30px 24px 21px,
    h6: 24px 21px 18px
);

$headings: h1 h2 h3 h4 h5 h6;
@each $heading in $headings {
    #{$heading}, .#{$heading} {
        @include heading($heading);
    }
}

If you have any reactions please leave them in the comments section below!