Mathias Polligkeit
  • Dev
  • Impro
  • Sheet Music
  • Contact
Nov 21, 2021 (last updated: Nov 24, 2021)

Design Systems, Pt. 2: Generating Sizes, Line Heights and Spacers

The first part of this series mentioned the automatic generation of type scales, line heights and spacers from the configuration. Let’s have a look at how to do this with SASS.

Font sizes

For generating font sizes from a modular type scale, three variables need to be set.

// The ratio between two font sizes.
$modular-scale: 1.25;

// The number of font sizes smaller than the base size to be generated.
$smaller-sizes: 3;

// The number of font sizes larger than the base size to be generated.
$larger-sizes: 3;

To generate the font sizes, we are going to assign an integer to each size. The base size will be 0, smaller sizes will have negative numbers and larger sizes will have positive numbers. The sizes will be defined in rem (relative to the font size of the root element). The size can then be generated with this function:

@use "sass:math";

@function size-from-scale($scale, $exponent) {
  $calculated-size: math.pow($scale, $exponent);
  @return $calculated-size * 1rem;
}

Here, the $exponent is the integer we assigned to the font size. If you want to use another base for the generation than the root font size, you can replace 1rem here.

We are going to use clothing sizes as identifiers for the sizes and name the base size m. In the function below, we initialize the $sizes variable as an empty map. Then we first iterate over the smaller sizes starting at the smallest, call the size-from-scale function to get the rem value, and build up the identifier. Then we add the value for the base size, and finally we add the larger sizes to the map.

@use "sass:map";

@function generate-sizes($modular-scale, $smaller-sizes, $larger-sizes) {
  $sizes: ();

  @for $i from $smaller-sizes through 1 {
    $rem: size-from-scale($modular-scale, -1 * $i);

    @if $i == 1 {
      $sizes: map.set($sizes, "s", $rem);
    } @else {
      $s: "s";

      @for $i from $i through 2 {
        $s: "x#{$s}";
      }

      $sizes: map.set($sizes, $s, $rem);
    }
  }

  $sizes: map.set($sizes, "m", size-from-scale($modular-scale, 0));

  @for $i from 1 through $larger-sizes {
    $rem: size-from-scale($modular-scale, $i);

    @if $i == 1 {
      $sizes: map.set($sizes, "l", $rem);
    } @else {
      $s: "l";

      @for $i from 2 through $i {
        $s: "x#{$s}";
      }

      $sizes: map.set($sizes, $s, $rem);
    }
  }

  @return $sizes;
}

We can now assign the result of this function to the $sizes variable.

$sizes: generate-sizes($modular-scale, $smaller-sizes, $larger-sizes);

For the values above, this results in the following map:

(
  "xxs": 0.512rem,
  "xs": 0.64rem,
  "s": 0.8rem,
  "m": 1rem,
  "l": 1.25rem,
  "xl": 1.5625rem,
  "xxl": 1.953125rem
)

Line heights

We define these variables:

$base-line-height: 1.5;
$line-heights: (0.5, 1, 1.5, 2, 3);

The $base-line-height defines the height of a single line relating to the base size. In this case, one line equals 1.5rem.

The $line-heights defines the valid line height values relating to the base line height. In this case, we allow half lines, full lines, one and a half lines, double lines and triple lines.

The identifiers for those line values need to be valid CSS class names, so they cannot contain the decimal point. We’re going to convert the decimal numbers into fractions and replace the / of the fraction with a _.

Since we only have a very limited number of values, we’ll avoid the effort of writing a general purpose to-fraction function in SASS and just hard-code the values we need. If you need quarter lines or three-quarter lines, you can add the values to the $mapping variable.

@use "sass:map";

@function number-to-fraction($n) {
  $mapping: (0.5: 1_2, 1.5: 3_2);

  @if map.has-key($mapping, $n) {
    @return map.get($mapping, $n);
  } @else {
    @return $n;
  }
}

To generate the map from number of lines to values, we add this function:

@function generate-lines($base-line-height, $line-heights) {
  $lines: ();

  @each $factor in $line-heights {
    $value: $base-line-height * $factor * 1rem;
    $key: number_to_fraction($factor);

    $lines: map.set($lines, $key, $value);
  }

  @return $lines;
}

This allows use to generate the map of lines:

$lines: generate-lines($base-line-height, $line-heights);

Which will generate the following map:

(
  1_2: 0.75rem,
  1: 1.5rem,
  3_2: 2.25rem,
  2: 3rem,
  3: 4.5rem
)

Spacers

Finally, we generate the spacer values. We are using two variables:

$spacer-base: 0.25rem;
$spacer-factors: (1, 2, 4, 6, 8);

The $spacer-base is the smallest spacer value. I usually just add a function that takes a factor and returns a spacer value, without generating a map with spacer values. In that case, I only use $spacer-factors for generating utility classes (which is out of scope for this article).

$spacer-base: 0.25rem;

@function spacer($factor) {
  @return $factor * $spacer-base;
}

Assuming this was defined in functions.scss, you would call use the function like this:

@use "functions" as f;

.some-class {
  margin-right: f.spacer(2);
}

If you prefer to restrict the spacer factors used in your styles, you can generate a map instead:

@function generate-spacers($spacer-base, $spacer-factors) {
  $spacers: ();

  @each $factor in $spacer-factors {
    $value: $spacer-base * $factor;
    $spacers: map.set($spacers, $factor, $value);
  }

  @return $spacers;
}

Then generate the spacer values with:

$spacers: generate-spacers($spacer-base, $spacer-factors);

Which would generate this map:

(
  1: 0.25rem,
  2: 0.5rem,
  4: 1rem,
  6: 1.5rem,
  8: 2rem
)

Next part: Design Systems, Pt. 3: Generating Custom Properties and Accessor Functions

  • design-systems
  • sass

See Also

  • Design Systems, Pt. 9: Conclusion
  • Design Systems, Pt. 8: Layout Rules
  • Design Systems, Pt. 7: Project Structure
  • Design Systems, Pt. 6: Variable Export
  • Design Systems, Pt. 5: Themes
  • privacy policy