Craft context
I started to go deeper in CSS baseline related crafts while designing myself a personnal planner. The planner was constructed around a 0.5cm grid with bullet-journal like dot patterns, and was entierly made with HTML and CSS. When writting with a pen on a quadrillated paper sheet, the grid becomes a nice invitation to write your text so that its baseline matches the horizontal lines of the grid. Because the planner would contains both printed typography and my own handwriting, I wanted to see how the baseline of HTML text could sit on the vertical grid of the planner. That curiosity became a challenge: can I do that so that it magicaly works for any font-familly and font-size by using CSS. Implementing such a design goes against the native way text flows in a webpage and it requires to unfold some complexities of CSS typography. It is important that the context was printing, doing something this complexe for a website is a fragile path.
All those complexities that we're about to unfold come from the fact that the web revolves around different rules than desktop pubishing softwares when handling baselines. The concept of leading and line-height differs. Both tools left different type of cuts on the materials and have proven to create usable reading environment, so this is no place for typography arguments but rather for crafts.
Different rythms
The fluidity of default
Let's take at look at the Default vertical rythm of HTML & CSS. In the absence of any kind of explicitly defined grid the flow is controled by the user-agent stylesheet.
In this case: line-height is unit-less (here it has been adjusted to 1.35
for the paragraph, and 1
for the headers for comparison purposes, while the default is often 1.17
) and vertical margins are defined in em
(here they have been adjusted to 1em
or 2em
values, while the default is often 1em
).
Those defaults doesn't really care about alligning anything on a grid, as we can see by the two highlighted paragraphs.
In Default vertical rythm:
- the boxes of the elements constituting the text flow have totally independant heights and thus does not aligns on a common vertical grid.
- As a consequence, the baselines of the left and right column does not align with each others.
This is easily explained because unitless line-height
vary according to the element's font-size
(like if it was set in em
).
So it produces bigger line-height for a h2
than for a p
: every element with different font-size
having different heights. The spaces in between the boxes are also not regulated and font-size
dependant.
Changing the font-size
of an element impact the rest of the document, everything stay fluid and a modification in styling induces a modification of flows in a continuous manner.
The magnetism of restriction
If you've ever done print publishing using web-standard you've probably encountered this. Constant vertical rythm is done by setting certain values to explicit multiples so that a defined rythmes is able to repeat.
This approach is often desired when you have text flowing on two columns, because it will make side by side things look aligned. However this rythm is also sometimes used on the web (by more nerdy or intricate designer or typographer) to get a magnetized feeling in the rythm of a page.
In Constant vertical rythm:
- the boxes of the elements aligns on a common vertical grid.
- the baselines of the left and right column align with each other but only for element of exact same styling, meaning same font metrics and
font-size
(a paragraph from the left column and one from the right, but not a paragraph from the left and a title from the right who can be more and more misaligned as the font metrics differs).
Changing the style of the elements is independant of the flow of the rest of the document.
This make it easy to adjust your font-size
live through the inspector, without creating misalignment in the rest of the document, which again can be nice for printing.
Note that if you have big font-sizes, it's possible that some texts vertically overflow their HTML element.
If such text as to fit in more than one line, it can become handfull to set its line-height
to be the double of the other (as explained here: css-baseline-the-good-the-bad-and-the-ugly)
The craft of alignement
But as stated, what we want to achieve is a bit more complex than Constant vertical rythm...
In a Base-aligned rythm:
- the boxes of the elements doesn't aligns anymore on a common vertical grid.
- but the baselines of all elements aligns on a common vertical grid (even for elements of different font metrics and
font-sizes
).
The subtetly is that the part that is aligned with the grid is the top and bottom of the elements's boxes, not their baseline. But we could hope for even more: to set up a base-aligned system that, in term of font-sizes changes, works so that it doesn't un-aligned any element before or after and the element stays base-aligned. At this point you could be asking yourself "But, would it be possible to have an implementation of base-aligned rythm that work in CSS only, without having to manually edit or translate each different styling?".
(the answer is yes)
Crafting a constant vertical rythm
In order to be able to do a Base-aligned rythm, we first need to implement Constant vertical rythm.
While there exists different way to do so, the basic mechanism is the following.
A basic height unit is choosen, you can think of it has the smallest step of your vertical grid.
It is often equal to the line-height
of a standard p
, or a fraction of it if we want to go more granular.
For every elements that compose the typographic flow:
- If their heights are determined by their text content (like
p
,h2
,ul
, ...), they must have aline-height
which is a multiple of the height unit (often equals). - If their heights are not determined by their text content (like
img
,aside
with fixed height, ...), they must have a fixedheight
which is a multiple of the height unit. - the sum of their spacing, meaning the
margin
+border-width
+padding
that separate each combination of two elements, must also be a multiple of this height unit.
We see that if this is respected, then this implies that the height and spaces between every elements that compose the typographic flow are equal to a multiple of the height unit, and thus matches our vertical grid.
I love to craft this implementation by introducing a global --lh
CSS custom properties which define the height unit of our vertical grid. We can then start using this properties as our new unit:
- for the
line-height
of the standardp
, - but also to leave a whole line empty between two
p
by setting their margin tomargin: var(--lh) 0;
.
The actual CSS unit that we used for --lh
is also an important choice: rem
, meaning its going to be relative to the font-size
defined in the html
tag, unlike em
which adapts to the current element font-size
.
The idea is that by using rem
we keep it independant of the elements indivual font-size
's, ensuring this value is global over the document, but we stay flexible: by changing the font-size
of the html
tag, from 1em
to 16px
to 12pt
for example, it'll automatically adapt our --lh
to switch unit and size while keeping the same relative ratio.
Another neat craft is to use a class (here .cvr
), this is so we can target parts of the page to follow a constant-vertical rythm by putting this class on the container; and things like menus, buttons, aside, or other are not going to be messed up and can still have their independant line-height
, like probably of 1
after a reset. This implementation, could be used for both print and web and is relatively easy to implement.
:root{
--lh: 1.35rem;
/* font-sizes */
--fs-big: 2.6rem;
--fs-plus: 1.4rem;
--fs: 1rem;
}
html{
/* main font-size,
has to be defined in the html tag
so we can use the rem unit for --lh */
font-size: 1.125em;
/* this value upscale 16px to 18px */
}
.cvr{
font-size: var(--fs);
line-height: var(--lh);
}
/* font-sizes */
.cvr h1{
font-size: var(--fs-big);
line-height: calc(var(--lh) * 2);
}
.cvr h2{
font-size: var(--fs-plus);
}
/* spaces */
.cvr :is(h1,h2){
margin: calc(var(--lh) * 2) 0 calc(var(--lh) * 1);
}
.cvr p{
margin: calc(var(--lh) * 1) 0;
}
.cvr img{
display: block;
height: calc(var(--lh) * 5);
margin: 0 auto;
}
Background on a span and dreaming about lh
Looking at the previous implementation we can start to wonder.
An interestning way to interpret it, is to see it as the implementation of a custom CSS unit.
Like em
(typography relative) or px
(screen relative), we introduced a new typographic relative unit --lh
, and we count this unit by using calc()
which of course doesn't look super appealing.
/* writting this */
margin: calc(2 * var(--lh)) 0;
/* is conceptual like writting this (which doesn't work) */
margin: 2lh 0;
As a reminder the em
is a conceptual square where its size have the lenght of the font metrics from ascender to descender. So let's say that in the font's metric ascender is at 624 and descender is at -400, then the em square is a virtual square of 1024 font units. When we say fontsize: 16px;
in CSS what we say is: "make the em square 16 pixels", meaning from ascender to descender it's going to take 16px, it's like saying "make the conceptual invisible global bounding box of the glyphs in my font take 16px". This shows that em
is not really a visual unit, fontsize: 16px;
doesn't make anything visual on your screen take 16px;
.
By declaring "the vertical margins are equal to (or a fraction of) the em-square" we end up with margins equal to an invisible conceptual square induced but the crafting of the font itself.
But in fact there is one visible element taking 1em: the selection pseudo-element background that you can see by selecting text, or more generally by putting background
on a <span>
(or any inline element).
While using --lh
as margins, we're declaring "the vertical margins are equal to exactly (or a fraction of) an empty line of text".
Our eyes percieved it in a different way, it feels like you would have put a <br/>
, like simply pressing enter on a simple text editor.
Wether one would like to use an invisible line or an invisible bounding box of a glyphs as a spacing unit is a matter of typographic desired feeling that mixes history of lead characters and digital text editor. Anyways, a cool thing about the previous --lh
unit implementation is that it became available a lot of different places, UI elements heights, or even horizontally.
Displaying the grid
:root{
--lh: 1.35rem;
--vg-width: 1px;
--vg-color: cyan;
}
.show-baseline {
background:
linear-gradient(white rgba(0,0,0,0),
rgba(0,0,0,0) calc(var(--lh) - var(--vg-width)),
var(--vg-color) calc(var(--lh) - var(--vg-width)),
var(--vg-color) var(--lh));
background-size: 100% var(--lh);
}
Crafting a Base-aligned rythm
Naïve approaches
Starting from a Constant vertical rythm, a naïve approach would be to vertically translate everything, let's say with either a margin-top
or padding-top
and hope every baselines match the grid, which fails because every elements with different styling may need to be shifted from a different value.
So we could precise a different vertical translation value for every elements, as explained in this article css baseline the good the bad and the ugly.
But this is not the solution we are looking for. Not only because it's long to setup, but also because what we want is get the system right and then think outside of the system we've made. With this solution, everytime we change the font-size
of a elements, we would have to adapt this value so it matches with the grid again. Having to adjust the system everytime we move something makes our thought oscillate between what we are doing and how to do it.
Futhermore putting this shift value in em
for every element as part of their margin-top
doesn't solve it either.
As the line-height
is fixed - not relative to the font-size
of the elements - and some element have different font-size
, it can gives us an instinct on way it's a bit more tricky. Let's put it this way, if an element double it's font-size
, it doesn't imply that the space between their baseline and the bottom of the line-height has doubled, in fact a bigger font-size
can even make this value change sign.
Relative shift computations
Every elements need to be vertically translated, yes:
from the space between their baseline and the bottom of the line-height-area.
We need to look a bit closer at the invisible boxes of web typography.
There is this beautiful article about css font metrics that introduces the difference between the content-area and the line-heiht-area. This concept allow us to understand why adding an element with smaller font-size
but with same line-height
can paradoxically increase the height of the line-height-area (usually like footnotes indices).
The content-area from ascender to descender of the font is vertically centered in the line-height-area.
So we have two even spaces at the top and bottom of our line separating the two.
The property of centering the content-area in the line-height-area is caused by the default vertical-align: baseline;
declared on our block element containing the text (here a p
).
What this does specifically, is that it first aligns every content-area between them by their baseline, then center the group containing all those content-areas vertically in the middle of the line-height-area.
We can now express our translation value of each element as the sum of:
- the space between the baseline and the bottom of the content-area.
- the space between the bottom of the content-area and the bottom of the line-height-area..
The first shift value only depends of the font itself, has shown in the previous figure. This content-area depends on the font-metric of our font, and can not be changed through CSS. Note that it actually is the descender of the font, from the baseline and the bottom, so this value can be expressed in em
.
The second shift value can be computed as (line-height - content-area-height) / 2
.
Which depends of both the font-metrics, and the line-height that we set up in CSS.
But because content-area-height is the sum of ascender and descender of the font, we can express it as (line-height - (asc / desc)) / 2
.
By giving a name to this special value rel_bl = (asc - desc)
, the whole shift value could be expressed as
desc + ((line-height - (desc + asc)) / 2)
= (line-height - (asc - desc)) / 2
= (line-height - rel_bl) / 2
The point is that we don't really care about a more specific expression for rel_bl
, as it only depends of the font-metrics, so we know it can be expressed in em
.
For the same reason this value is unique per font, whatever font-size
or line-height
we give to each elements.
So finding it manually become quite easy by editing through the inspector.
Implementation of Base-aligned rythm
This implementation has to follow the one of Constant vertical rythm.
It works by shifting every single block contained in our Base-aligned rythm class from a value that is relative to both the font's metrics and the fixed height unit (defined by --lh
).
For every different font we have to manually find the --rel-bl
which is the part relative to the font's metrics, this can easily be done by setting up the document then increasing and decreasing the value in the inspector until it matches the vertical grid, also drawn on the page with CSS.
The elements are vertically translated using position: relative;
and a top
value, to ensure their translation doesn't move the initial position in the flow of any of the others.
:root{
--font-1: 'Junicode';
--rel-bl_font-1: 0.6em;
--font-2: 'Liberation';
--rel-bl_font-2: 0.7em;
}
/* GLOBAL */
.ba :is(h1,h2,p){
/* by default everything as a total lh equal to lh */
--lh-total: var(--lh);
line-height: var(--lh-total);
/* by default everything as a rel-bl of the main font */
--rel-bl: var(--rel-bl_font-1);
/* and is in this font */
font-family: var(--font-1);
}
/* EXCEPTIONS */
.ba :is(h1,h2){
/* those changed font */
font-family: var(--font-2);
/* thus must change their --rel-bl value */
--rel-bl: var(--rel-bl_font-2);
}
.ba h1{
/* those double their line-height */
--lh-total: calc(var(--lh) * 2);
}
/* ALIGNMENT */
.ba :is(h1,h2,p){
position: relative;
top: calc(calc(var(--lh-total) - var(--rel-bl))/ 2);
}
Because the --rel-bl
value is relative to font's metrics it is possible that you have to change it for a bold or italic version of a font, depending of it's drawing.
On a document with two fonts as show in the example, once those values are found, we can style every elements as we want and we don't have to adapt any of those values anymore and everything will stay in place griddier than ever, whatever the typographic styles.
Rythms and their grains
By using relative positionning on every element — desynchronising every individual boxes of the native flow of HTML — it is clear that the above methodology goes quite against The Web’s Grain
The web is forcing our hands. And this is fine! Many sites will share design solutions, because we’re using the same materials. The consistencies establish best practices; they are proof of design patterns that play off of the needs of a common medium, and not evidence of a visual monoculture.
For printed material made out of HTML & CSS, the akward zigzag of the code is no more present after printing, and what we see is the baseline of the text, while their weird boxes become totally invisible artefact of the craft. We could say that we finally have here a nice solution to make the DIY practice of CSS-based printed layout benefit from the classic elegance of the academic typographic rules.
However, by becoming it's own culture of alternative to Adobe and proprietary softwares, the web to print practices has inevitably developped it's own grain.
It is clearly revealed to me when I can see the artefact of CSS printed on paper: some words circled by colorfull ellipsis whose dimension where entierly defined by the length of the contained word recognising a border-radius: 50%;
, or the rawness of spacing andabsence of typographic adjustment, or some quite organic and random display: flex
or punk-ish linear-gradient
.
Those type of process afterfacts can trigger some kind of childlike expression on my face because they delicately unveil the singularities how the tool used.
And because web2print is part of a somewhat ideological mouvement trying to take another path from the hegemonic way of thinking print layout (imposed by the closed workflow of adobe), all those typographic artefacts becomes the aesthetic of a counter-culture.
For me as a designer, the pleasure of investigating those questions where certainly not about stating what is more elegant, or trying to fill a hole in something: it is about technical curiosity and exploring different patterns and rythms and learning about the way they feel. In the end this text is, once again, about opening the boxes, and by doing so opening the choices. In hope that you can wonder the right position at the right time between the convenience of simplicity and the precision of constructions.