The way font-size tokens work now
Codex internally defines the values of the various font-size tokens in pixels. For example, font-size-large is defined as 18px. But the value that is actually output for this token and is used in practice (both in CSS for Codex's own components, and for consuming code that uses @font-size-large or var( --font-size-large ) in its own Less or CSS), is first transformed to ems. This transformation is based on the base font size for the skin we're targeting. Codex generates two different token outputs, for two different base font sizes: 16px (the default; used in Minerva) and 14px ("legacy"; used in Vector). When a font-size token is transformed from pixels to ems, the transformation is done such that the resulting pixel value is the same. For example, font-size-large is defined as 18px, so in the default output it's 1.125em (assuming a 16px base font size that's 1.125 * 16 = 18px) and in the legacy output it's 1.285714 (assuming a 14px base font size that's 1.285714 * 14 = 18px).
Token name | Internal definition | Default output | Pixel value in default skins | Legacy output | Pixel value in legacy skins |
---|---|---|---|---|---|
font-size-x-small | 12px | 0.75em | 0.75 * 16 = 12px | 0.8571429em | 0.8571429 * 14 = 12px |
font-size-small | 14px | 0.875em | 0.875 * 16 = 14px | 1em | 1 * 14 = 14px |
font-size-medium | 16px | 1em | 1* 16 = 16px | 1.1428571em | 1.1428571 * 14 = 16px |
font-size-large | 18px | 1.125em | 1.125 * 16 = 18px | 1.2857143em | 1.2857143 * 14 = 18px |
font-size-x-large | 20px | 1.25em | 1.25 * 16 = 20px | 1.4285714em | 1.4285714 * 14 = 20px |
font-size-xx-large | 24px | 1.5em | 1.5 * 16 = 24px | 1.7142857em | 1.7142857 * 14 = 24px |
font-size-xxx-large | 28px | 1.75em | 1.75 * 16 = 28px | 2em | 2 * 14 = 28px |
This same technique of computing the number of ems to arrive at the same number of pixels regardless of the base font size is helpful for the size tokens, which are conceptually absolute but are implemented using ems to support text zooming. But doing this for font-size tokens causes issues like T357964: Dialog: fix the text size in the dialog's title on Desktop and T357965: Accordion: fix the label size on dekstop where the font size of certain elements of components is out of step with the font size of the surrounding text. For example, the Dialog component uses font-size-large for its header, which means the header is always 18px. This means that relative to the font size of regular text, it looks bigger in Vector (18px vs 14px) than in Minerva (18px vs 16px).
The way line-height tokens work now
Meanwhile, the line-height tokens are defined as relative values: they are multiples of the font size. For example, line-height-xx-small is defined as 1.375, meaning the line height will be 1.375x the font size, whatever the font size is. For text with a 16px font size that works out to 22px; for text with a 14px font size it's 19.25px. This means line-heights aren't always round numbers of pixels, especially in legacy skins. Note that the line height is relative to the font size of the specific line of text it's applied to, not to the base font size, so e.g. line-height-xxx-small (1.25) is 20px when applied to regular text (font size 16px), but 22.5px when applied to a dialog header where the font size is 18px.
Token name | Internal definition | Default output | Pixel value for normal text in default skins | Legacy output | Pixel value for normal text in legacy skins |
---|---|---|---|---|---|
line-height-xxx-small | 1.25 | 1.25 | 1.25 * 16 = 20px | 1.25 | 1.25 * 14 = 17.5px |
line-height-xx-small | 1.375 | 1.375 | 1.375 * 16 = 22px | 1.375 | 1.375 * 14 = 19.25px |
line-height-small | 1.5714285 | 1.5714285 | 1.5714285 * 16 = 25.14285px | 1.5714285 | 1.5714285 * 14 = 22px |
line-height-medium | 1.6 | 1.6 | 1.6 * 16 = 25.6px | 1.6 | 1.6 * 14 = 22.4px |
Option 0: Keep everything the way it is
If we decide that the font size of e.g. a dialog header should always be the same pixel value regardless of the font size of the surrounding text, then the way things work now is fine, and we don't need to change anything.
Option 1: Make font-size tokens relative
We could define font-size tokens as relative values that are multiples of the base font size. (This is somewhat similar to how line-height tokens work, except that line-height is relative to the font size of the text it's applied to, which may be different from the base font size.) For example, we could say that font-size-large should always be 1.125x the base font size, meaning that it would be 18px in default skins (where the base font size is 16px) but 15.75px in legacy skins (where the base font size is 14px). This way, a dialog header for example would be approximately 2px larger than the surrounding text in both skins.
The full list of font-size values would be:
Token name | Internal definition | Default output | Pixel value in default skins | Legacy output | Pixel value in legacy skins |
---|---|---|---|---|---|
font-size-x-small | 0.75em | 0.75em | 0.75 * 16 = 12px | 0.75em | 0.75 * 14 = 10.5px |
font-size-small | 0.875em | 0.875em | 0.875 * 16 = 14px | 0.875em | 0.875 * 14 = 12.25px |
font-size-medium | 1em | 1em | 1* 16 = 16px | 1em | 1em * 14 = 14px |
font-size-large | 1.125em | 1.125em | 1.125 * 16 = 18px | 1.125em | 1.125 * 14 = 15.75px |
font-size-x-large | 1.25em | 1.25em | 1.25 * 16 = 20px | 1.25em | 1.25 * 14 = 17.5px |
font-size-xx-large | 1.5em | 1.5em | 1.5 * 16 = 24px | 1.5em | 1.5 * 14 = 21px |
font-size-xxx-large | 1.75em | 1.75em | 1.75 * 16 = 28px | 1.75em | 1.75 * 14 = 24.5px |
Option 2: Pick different values for font-size tokens for different base font sizes
We could define the values of each of these font-size tokens for default skins and legacy skins separately. This would allow us to achieve something like option 1, but instead of sticking exactly to the same multipliers, we could tweak the values (e.g. to make them round numbers).
With this approach, an example list of font sizes could be:
Token name | Internal definition | Default output | Pixel value in default skins | Legacy output | Pixel value in legacy skins |
---|---|---|---|---|---|
font-size-x-small | 12px / 10px | 0.75em | 0.75 * 16 = 12px | 0.7142857em | 0.7142857 * 14 = 10px |
font-size-small | 14px / 12px | 0.875em | 0.875 * 16 = 14px | 0.8571429em | 0.857129 * 14 = 12px |
font-size-medium | 16px / 14px | 1em | 1* 16 = 16px | 1em | 1em * 14 = 14px |
font-size-large | 18px / 16px | 1.125em | 1.125 * 16 = 18px | 1.1428571em | 1.1428571 * 14 = 16px |
font-size-x-large | 20px / 18px | 1.25em | 1.25 * 16 = 20px | 1.2857143em | 1.2857143 * 14 = 18px |
font-size-xx-large | 24px / 20px | 1.5em | 1.5 * 16 = 24px | 1.4285714em | 1.4285714 * 14 = 20px |
font-size-xxx-large | 28px / 24px | 1.75em | 1.75 * 16 = 28px | 1.7142857em | 1.7142857 * 14 = 24px |
Note that the internal definitions of these font sizes would be in pixels that are then transformed to ems (like option 0, and unlike option 1). These tokens would be defined with different values for the default theme vs the legacy theme; no other tokens currently do this, so we would need to make some changes to the tokens code to support this.
Augmentation A: use rems instead of ems
Separately from the options above (regardless of whether we choose option 0, 1 or 2), we could output values expressed in rem instead of em. In (almost) all MediaWiki skins, the root element's font size is 16px, even if the base font size is something else (the base font size is set on a different element, not the root element). This means that 1rem always means 16px. Using rems would allow us to simplify the transformation and make it independent of the base font size that is used (instead we just need to divide the pixel value by 16), while still technically using a relative unit so that text zooming is still supported.
For example, font-size-large would be 1.125rem in the default output, and in the legacy output it would be 1.125rem (if we want it to be 18px, like in option 0) or 0.984375rem (if we want it to be 15.75px, like in option 1) or 1rem (if we want it to be 16px, like in option 2). All of these are just the desired pixel value divided by 16.
Doing this requires changing "almost all MediaWiki skins" to "actually all MediaWiki skins", which is captured in T343316: Promote `rem` technical requirements across themes.
The proof of concept patch for this approach combines rems with option 0.