Skip to content

Instantly share code, notes, and snippets.

@vnys
Last active April 3, 2023 19:53
Show Gist options
  • Save vnys/ac50e8189dcae883a7fb09083fed83fa to your computer and use it in GitHub Desktop.
Save vnys/ac50e8189dcae883a7fb09083fed83fa to your computer and use it in GitHub Desktop.
EDS typography and spacing
Title:  EDS 2.0 Typography and Spacing
Author: Victor Nystad <vnys@equinor.com>
Staus:  Work in progress
Date:   Dec 08 2022

Equinor Design System 2.0 typography and spacing

What we are trying to achieve

  1. A harmonious typographic scale based on the classic scale used in print for centuries
  2. Line-heights that are rounded up to the nearest 4px grid-line
  3. Spacing which uses a combination of line-height and vertical padding so that base-lines aligns with grid-lines

Typographic scale

Based on the classical typographic scale from the movable type era. Best described by Spencer Mortensen in his article “The Typographic Scale”, where the typographic scale is based on the pentatonic (five-tone) musicale scale, so the next step after the base font-size can be calculated using the formula $f1 = f0r^\frac{i}{n}$.

  • f0 is the fundamental frequency, or 16px.
  • r is the ratio, or 2
  • i is the step in the scale, for example 1 for the next step above f0, or -1 for the next step below f0
  • n is the number of notes in the scale, or 5

So to find the next step after 16px, the formula is $16 * 2 ^ \frac{1}{5}$, which is 18,37917368. That number is normally rounded to the closest integer, which would give us the following scale:

  • f-4 = 9px
  • f-3 = 11px
  • f-2 = 12px
  • f-1 = 14px
  • f0 = 16px
  • f1 = 18px
  • f2 = 21px
  • f3 = 24px

In our typography tokens step 3 corresponds to the fundamental frequency, for historical reasons when font-size 3 was the default font-size in the browser.

One obvious problem with that is the increment between 9 and 11, and 11 and 12 – where there are two pixels between the two smallest values, and then just one pixel between the second and third smallest. A better solution then, would be to round up to the nearest 0.5.

That would give us the following modified scale, with a much smoother transition from one step to the next:

  • f-4 = 9px
  • f-3 = 10.5px
  • f-2 = 12px
  • f-1 = 14px
  • f0 = 16px
  • f1 = 18.5px
  • f2 = 21px
  • f3 = 24.5px

In the Figma Tokens plugin the formula for f1 looks like this:

5*roundTo((16*2^(1/5)/5),1)

… or using aliases:

5*roundTo(({eds.core.const.f0}*{eds.core.const.r}^(1/{eds.core.const.n})/5),1)

Line-heights and 4px hard grid

In the EDS we use a 4px hard grid. That means that instead of using spacing values in-between invisible bounding boxes, we use space between base-lines. That also means that we try to align the base-lines in a text-frame with the grid-lines in a layout-grid, with some exceptions where it makes more sense to vertically align a line of text to center, for example in a button component. The first step in achieving this, is rounding the line-height up to the nearest grid-line. We have four line-height scales with various density:

Density Value
Tight 100%
Compressed 110%
Comfortable 125%
Relaxed 150%

Relaxed is WCAG compliant, and is used for body copy. As the font-size increases with the header levels, the $\frac{font-size}{line-height}$ decreases, as legibility in large titles does not require a line-height of 150%.

Using fontSize2 (14px) and relaxed (150%) as an example, simply using a percentage would give us a line-height of 21px, which does not align with the layout grid. 24px, however, does. In order to achieve that, we need to use the following calculation in the token:

4 * ceil(14 * 1.5 / 4)

… or using aliases:

{eds.core.const.grid} * ceil({eds.core.fontSize.3} * {eds.core.const.relaxed} / {eds.core.const.base})

There are some overlapping numbers in the scales, for example, the line-heights for fontSize3 are as follows:

Density Value
tight3 16px
compressed3 20px
comfortable3 20px
relaxed3 24px

However, from a user perspective we believe the advantage of using the scales outweighs the increase in number of tokens.

Spacing

Heads up! The following calculations work perfectly in Figma, but not in browsers due to how line-height is calculated, and have to be changed. Working in Codepen at the moment, and will update this document once it’s solved.

In addition to using a hard grid in the EDS, we also use optical margins. That means that in some cases where the optical margin is a certain value, we might have to use a number that seems out of place to arrive at the optimal result.

One of the reasons that this is sometimes necessary, is that Figma does not have support for negative padding or leading-trim. The consequence of that is that if you have a textframe where the base-lines are aligned to the layout-grid, and you increase the line-height, the lines of text will be moved down vertically, and so the top padding has to be decreased so that the first line stays in place.

This may seem like a lot of work for components that only have one line of text, but even though we recommend that buttons for example shouldn’t have multiple lines of text, we should still ensure that the components don’t break for the product teams who choose to ignore our recommendation. That also means that none of our components that contain textframes have fixed heights.

The calculation for the vertical padding in combination with the line-height requires us to know the actual cap-height of the font. Using FontForge, we can se that the height of 1em is 1000, and the cap-height is 700 – or in other words, the cap-height is 70% of 1em, which is an easy value to work with. That means that in order to have a line of text that has no vertical space, we can set the line-height to 70%.

For anyone else wanting to try out the tokens who don’t have access to the Equinor fontfiles, Inter has an em-height of 2816, and a cap-height of 2048, or 72,7272%.

So let’s say we have an autolayout container with a textframe with a 14px font-size and a 20px line-height, where the calculated height of the container is 36px, this is how we find the top and bottom padding needed to ensure that the base-line aligns with the layout grid.

Property Value
Font-size 14px
Line-height 20px
Cap-height 0.7em
Rounded cap-height 14 * 0.7 = 9.8 ≈ 10
Nearest grid 10 ≈ 12
Optical vertical spacing (36 - 12) / 2 = 12
Average vertical padding (20 - 10) / 2 = 5
Padding bottom 12 - 5 = 7
Padding top 36 - 20 - 7 = 9

📝 TODO: Replace Google Sheets table with formula.


This document is work in progress, check out a simplified copy of the tokens repo which only deals with spacing and typography at https://github.com/vnys/token-scripts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment