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