Browse Source

Links

master
David Larlet 1 week ago
parent
commit
271d694cf3
Signed by: David Larlet <david@larlet.fr> GPG Key ID: 3E2953A359E7E7BD

+ 185
- 0
cache/2024/1137631455ddd7b2acdd1f4071756ba6/index.html View File

@@ -0,0 +1,185 @@
<!doctype html><!-- This is a valid HTML5 document. -->
<!-- Screen readers, SEO, extensions and so on. -->
<html lang="en">
<!-- Has to be within the first 1024 bytes, hence before the `title` element
See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
<meta charset="utf-8">
<!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
<!-- The viewport meta is quite crowded and we are responsible for that.
See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
<meta name="viewport" content="width=device-width,initial-scale=1">
<!-- Required to make a valid HTML5 document. -->
<title>How cheap, outsourced labour in Africa is shaping AI English (archive) — David Larlet</title>
<meta name="description" content="Publication mise en cache pour en conserver une trace.">
<!-- That good ol' feed, subscribe :). -->
<link rel="alternate" type="application/atom+xml" title="Feed" href="/david/log/">
<!-- Generated from https://realfavicongenerator.net/ such a mess. -->
<link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons2/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons2/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons2/favicon-16x16.png">
<link rel="manifest" href="/static/david/icons2/site.webmanifest">
<link rel="mask-icon" href="/static/david/icons2/safari-pinned-tab.svg" color="#07486c">
<link rel="shortcut icon" href="/static/david/icons2/favicon.ico">
<meta name="msapplication-TileColor" content="#f7f7f7">
<meta name="msapplication-config" content="/static/david/icons2/browserconfig.xml">
<meta name="theme-color" content="#f7f7f7" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#272727" media="(prefers-color-scheme: dark)">
<!-- Is that even respected? Retrospectively? What a shAItshow…
https://neil-clarke.com/block-the-bots-that-feed-ai-models-by-scraping-your-website/ -->
<meta name="robots" content="noai, noimageai">
<!-- Documented, feel free to shoot an email. -->
<link rel="stylesheet" href="/static/david/css/style_2021-01-20.css">
<!-- See https://www.zachleat.com/web/comprehensive-webfonts/ for the trade-off. -->
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
<script>
function toggleTheme(themeName) {
document.documentElement.classList.toggle(
'forced-dark',
themeName === 'dark'
)
document.documentElement.classList.toggle(
'forced-light',
themeName === 'light'
)
}
const selectedTheme = localStorage.getItem('theme')
if (selectedTheme !== 'undefined') {
toggleTheme(selectedTheme)
}
</script>

<meta name="robots" content="noindex, nofollow">
<meta content="origin-when-cross-origin" name="referrer">
<!-- Canonical URL for SEO purposes -->
<link rel="canonical" href="https://simonwillison.net/2024/Apr/18/delve/">

<body class="remarkdown h1-underline h2-underline h3-underline em-underscore hr-center ul-star pre-tick" data-instant-intensity="viewport-all">


<article>
<header>
<h1>How cheap, outsourced labour in Africa is shaping AI English</h1>
</header>
<nav>
<p class="center">
<a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
</svg> Accueil</a> •
<a href="https://simonwillison.net/2024/Apr/18/delve/" title="Lien vers le contenu original">Source originale</a>
<br>
Mis en cache le 2024-04-18
</p>
</nav>
<hr>
<p><a href="https://www.theguardian.com/technology/2024/apr/16/techscape-ai-gadgest-humane-ai-pin-chatgpt">How cheap, outsourced labour in Africa is shaping AI English</a>. The word “delve” has been getting a lot of attention recently as an example of something that might be an indicator of ChatGPT generated content.</p>

<p>One example: articles on medical research site PubMed now use “delve” 10 to 100 times more than a few years ago!</p>

<p>Nigerian Twitter took offense recently to Paul Graham’s suggestion that “delve” is a sign of bad writing. It turns out Nigerian formal writing has a subtly different vocabulary.</p>

<p>Alex Hern theorizes that the underlying cause may be related. Companies like OpenAI frequently outsource data annotation to countries like Nigeria that have excellent English skills and low wages. RLHF (reinforcement learning from human feedback) involves annotators comparing and voting on the “best” responses from the models.</p>

<p>Are they teaching models to favour Nigerian-English? It’s a pretty solid theory! </p>
</article>


<hr>

<footer>
<p>
<a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
</svg> Accueil</a> •
<a href="/david/log/" title="Accès au flux RSS"><svg class="icon icon-rss2">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-rss2"></use>
</svg> Suivre</a> •
<a href="http://larlet.com" title="Go to my English profile" data-instant><svg class="icon icon-user-tie">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-user-tie"></use>
</svg> Pro</a> •
<a href="mailto:david%40larlet.fr" title="Envoyer un courriel"><svg class="icon icon-mail">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-mail"></use>
</svg> Email</a> •
<abbr class="nowrap" title="Hébergeur : Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33184162340"><svg class="icon icon-hammer2">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-hammer2"></use>
</svg> Légal</abbr>
</p>
<template id="theme-selector">
<form>
<fieldset>
<legend><svg class="icon icon-brightness-contrast">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-brightness-contrast"></use>
</svg> Thème</legend>
<label>
<input type="radio" value="auto" name="chosen-color-scheme" checked> Auto
</label>
<label>
<input type="radio" value="dark" name="chosen-color-scheme"> Foncé
</label>
<label>
<input type="radio" value="light" name="chosen-color-scheme"> Clair
</label>
</fieldset>
</form>
</template>
</footer>
<script src="/static/david/js/instantpage-5.1.0.min.js" type="module"></script>
<script>
function loadThemeForm(templateName) {
const themeSelectorTemplate = document.querySelector(templateName)
const form = themeSelectorTemplate.content.firstElementChild
themeSelectorTemplate.replaceWith(form)

form.addEventListener('change', (e) => {
const chosenColorScheme = e.target.value
localStorage.setItem('theme', chosenColorScheme)
toggleTheme(chosenColorScheme)
})

const selectedTheme = localStorage.getItem('theme')
if (selectedTheme && selectedTheme !== 'undefined') {
form.querySelector(`[value="${selectedTheme}"]`).checked = true
}
}

const prefersColorSchemeDark = '(prefers-color-scheme: dark)'
window.addEventListener('load', () => {
let hasDarkRules = false
for (const styleSheet of Array.from(document.styleSheets)) {
let mediaRules = []
for (const cssRule of styleSheet.cssRules) {
if (cssRule.type !== CSSRule.MEDIA_RULE) {
continue
}
// WARNING: Safari does not have/supports `conditionText`.
if (cssRule.conditionText) {
if (cssRule.conditionText !== prefersColorSchemeDark) {
continue
}
} else {
if (cssRule.cssText.startsWith(prefersColorSchemeDark)) {
continue
}
}
mediaRules = mediaRules.concat(Array.from(cssRule.cssRules))
}

// WARNING: do not try to insert a Rule to a styleSheet you are
// currently iterating on, otherwise the browser will be stuck
// in a infinite loop…
for (const mediaRule of mediaRules) {
styleSheet.insertRule(mediaRule.cssText)
hasDarkRules = true
}
}
if (hasDarkRules) {
loadThemeForm('#theme-selector')
}
})
</script>
</body>
</html>

+ 18
- 0
cache/2024/1137631455ddd7b2acdd1f4071756ba6/index.md View File

@@ -0,0 +1,18 @@
title: How cheap, outsourced labour in Africa is shaping AI English
url: https://simonwillison.net/2024/Apr/18/delve/
hash_url: 1137631455ddd7b2acdd1f4071756ba6
archive_date: 2024-04-18
og_image:
description: The word "delve" has been getting a lot of attention recently as an example of something that might be an indicator of ChatGPT generated content. One example: articles on medical …
favicon: https://simonwillison.net/favicon.ico
language: en_GB

<p><a href="https://www.theguardian.com/technology/2024/apr/16/techscape-ai-gadgest-humane-ai-pin-chatgpt">How cheap, outsourced labour in Africa is shaping AI English</a>. The word “delve” has been getting a lot of attention recently as an example of something that might be an indicator of ChatGPT generated content.</p>

<p>One example: articles on medical research site PubMed now use “delve” 10 to 100 times more than a few years ago!</p>

<p>Nigerian Twitter took offense recently to Paul Graham’s suggestion that “delve” is a sign of bad writing. It turns out Nigerian formal writing has a subtly different vocabulary.</p>

<p>Alex Hern theorizes that the underlying cause may be related. Companies like OpenAI frequently outsource data annotation to countries like Nigeria that have excellent English skills and low wages. RLHF (reinforcement learning from human feedback) involves annotators comparing and voting on the “best” responses from the models.</p>

<p>Are they teaching models to favour Nigerian-English? It’s a pretty solid theory! </p>

+ 407
- 0
cache/2024/157e744e8062e5495ba700566e99f8f2/index.html View File

@@ -0,0 +1,407 @@
<!doctype html><!-- This is a valid HTML5 document. -->
<!-- Screen readers, SEO, extensions and so on. -->
<html lang="en">
<!-- Has to be within the first 1024 bytes, hence before the `title` element
See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
<meta charset="utf-8">
<!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
<!-- The viewport meta is quite crowded and we are responsible for that.
See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
<meta name="viewport" content="width=device-width,initial-scale=1">
<!-- Required to make a valid HTML5 document. -->
<title>Deep dive CSS: font metrics, line-height and vertical-align (archive) — David Larlet</title>
<meta name="description" content="Publication mise en cache pour en conserver une trace.">
<!-- That good ol' feed, subscribe :). -->
<link rel="alternate" type="application/atom+xml" title="Feed" href="/david/log/">
<!-- Generated from https://realfavicongenerator.net/ such a mess. -->
<link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons2/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons2/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons2/favicon-16x16.png">
<link rel="manifest" href="/static/david/icons2/site.webmanifest">
<link rel="mask-icon" href="/static/david/icons2/safari-pinned-tab.svg" color="#07486c">
<link rel="shortcut icon" href="/static/david/icons2/favicon.ico">
<meta name="msapplication-TileColor" content="#f7f7f7">
<meta name="msapplication-config" content="/static/david/icons2/browserconfig.xml">
<meta name="theme-color" content="#f7f7f7" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#272727" media="(prefers-color-scheme: dark)">
<!-- Is that even respected? Retrospectively? What a shAItshow…
https://neil-clarke.com/block-the-bots-that-feed-ai-models-by-scraping-your-website/ -->
<meta name="robots" content="noai, noimageai">
<!-- Documented, feel free to shoot an email. -->
<link rel="stylesheet" href="/static/david/css/style_2021-01-20.css">
<!-- See https://www.zachleat.com/web/comprehensive-webfonts/ for the trade-off. -->
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
<script>
function toggleTheme(themeName) {
document.documentElement.classList.toggle(
'forced-dark',
themeName === 'dark'
)
document.documentElement.classList.toggle(
'forced-light',
themeName === 'light'
)
}
const selectedTheme = localStorage.getItem('theme')
if (selectedTheme !== 'undefined') {
toggleTheme(selectedTheme)
}
</script>

<meta name="robots" content="noindex, nofollow">
<meta content="origin-when-cross-origin" name="referrer">
<!-- Canonical URL for SEO purposes -->
<link rel="canonical" href="https://iamvdo.me/en/blog/css-font-metrics-line-height-and-vertical-align">

<body class="remarkdown h1-underline h2-underline h3-underline em-underscore hr-center ul-star pre-tick" data-instant-intensity="viewport-all">


<article>
<header>
<h1>Deep dive CSS: font metrics, line-height and vertical-align</h1>
</header>
<nav>
<p class="center">
<a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
</svg> Accueil</a> •
<a href="https://iamvdo.me/en/blog/css-font-metrics-line-height-and-vertical-align" title="Lien vers le contenu original">Source originale</a>
<br>
Mis en cache le 2024-04-18
</p>
</nav>
<hr>
<p><code>Line-height</code> and <code>vertical-align</code> are simple CSS properties. So simple that most of us are convinced to fully understand how they work and how to use them. But it’s not. They really are complex, maybe the hardest ones, as <strong>they have a major role in the creation of one of the less-known feature of CSS: inline formatting context</strong>.</p>
<p>For example, <code>line-height</code> can be set as a length or a unitless value , but the default is <code>normal</code>. OK, but what normal is? We often read that it is (or should be) 1, or maybe 1.2, even the <a href="https://www.w3.org/TR/CSS2/visudet.html#propdef-line-height">CSS spec is unclear on that point</a>. We know that unitless <code>line-height</code> is <code>font-size</code> relative, but the problem is that <code>font-size: 100px</code> behaves differently across font-families, so is <code>line-height</code> always the same or different? Is it really between 1 and 1.2? And <code>vertical-align</code>, what are its implications regarding <code>line-height</code>?</p>
<p>Deep dive into a not-so-simple CSS mechanism…</p>
<h2 id="lets-talk-about-font-size-first">Let’s talk about <code>font-size</code> first<a href="#lets-talk-about-font-size-first" class="self-link"></a></h2>
<p>Look at this simple HTML code, a <code>&lt;p&gt;</code> tag containing 3 <code>&lt;span&gt;</code>, each with a different <code>font-family</code>:</p>
<pre class="language-markup"><code>&lt;p&gt;
&lt;span class="a"&gt;Ba&lt;/span&gt;
&lt;span class="b"&gt;Ba&lt;/span&gt;
&lt;span class="c"&gt;Ba&lt;/span&gt;
&lt;/p&gt;</code></pre>
<pre><code>p { font-size: 100px }
.a { font-family: Helvetica }
.b { font-family: Gruppo }
.c { font-family: Catamaran }</code></pre>
<p>Using the same <code>font-size</code> with different font-families produce elements with various heights:</p>
<figure><img src="/content/01-blog/30-css-avance-metriques-des-fontes-line-height-et-vertical-align/font-size.png"><figcaption class="caption">Different font-families, same font-size, give various heights</figcaption></figure>
<p>Even if we’re aware of that behavior, why <code>font-size: 100px</code> does not create elements with 100px height? I’ve measured and found these values: Helvetica: 115px, Gruppo: 97px and Catamaran: 164px</p>
<figure><img src="/content/01-blog/30-css-avance-metriques-des-fontes-line-height-et-vertical-align/font-size-line-height.png"><figcaption class="caption">Elements with font-size: 100px have height that varies from 97px to 164px</figcaption></figure>
<p>Although it seems a bit weird at first, it’s totally expected. <strong>The reason lays down inside the font itself</strong>. Here is how it works:</p>
<ul>
<li>a font defines its <a href="http://designwithfontforge.com/en-US/The_EM_Square.html">em-square</a> (or UPM, units per em), a kind of container where each character will be drawn. This square uses relative units and is generally set at 1000 units. But it can also be 1024, 2048 or anything else.</li>
<li>based on its relative units, metrics of the fonts are set (ascender, descender, capital height, x-height, etc.). Note that some values can bleed outside of the em-square.</li>
<li>in the browser, relative units are scaled to fit the desired font-size.</li>
</ul>
<p>Let’s take the Catamaran font and open it in <a href="https://fontforge.github.io/en-US/">FontForge</a> to get metrics:</p>
<ul>
<li>the em-square is 1000</li>
<li>the ascender is 1100 and the descender is 540. After running some tests, it seems that browsers use the <em>HHead Ascent</em>/<em>Descent</em> values on Mac OS, and <em>Win Ascent</em>/<em>Descent</em> values on Windows (these values may differ!). We also note that <em>Capital Height</em> is 680 and <em>X height</em> is 485.</li>
</ul>
<figure><img src="/content/01-blog/30-css-avance-metriques-des-fontes-line-height-et-vertical-align/font-forge-metrics.png"><figcaption class="caption">Font metrics values using FontForge</figcaption></figure>
<p>That means the Catamaran font uses 1100 + 540 units in a 1000 units em-square, which gives a height of 164px when setting <code>font-size: 100px</code>. <strong>This computed height defines the <em>content-area</em> of an element</strong> and I will refer to this terminology for the rest of the article. You can think of the <em>content-area</em> as the area where the <code>background</code> property applies .</p>
<p>We can also predict that capital letters are 68px high (680 units) and lower case letters (x-height) are 49px high (485 units). As a result, <code>1ex</code> = 49px and <code>1em</code> = 100px, not 164px (thankfully, <code>em</code> is based on <code>font-size</code>, not computed height)</p>
<figure><img src="/content/01-blog/30-css-avance-metriques-des-fontes-line-height-et-vertical-align/upm-px-equivalent.png"><figcaption class="caption">Catamaran font: UPM —Units Per Em— and pixels equivalent using font-size: 100px</figcaption></figure>
<p>Before going deeper, a short note on what this it involves. When a <code>&lt;p&gt;</code> element is rendered on screen, it can be composed of many lines, according to its width. Each line is made up of one or many inline elements (HTML tags or anonymous inline elements for text content) and is called a <em>line-box</em>. <strong>The height of a <em>line-box</em> is based on its children’s height</strong>. The browser therefore computes the height for each inline elements, and thus the height of the <em>line-box</em> (from its child’s highest point to its child’s lowest point). As a result, a <em>line-box</em> is always tall enough to contain all its children (by default).</p>
<blockquote>
<p>Each HTML element is actually a stack of <em>line-boxes</em>. If you know the height of each <em>line-box</em>, you know the height of an element.</p>
</blockquote>
<p>If we update the previous HTML code like this:</p>
<pre class="language-markup"><code>&lt;p&gt;
Good design will be better.
&lt;span class="a"&gt;Ba&lt;/span&gt;
&lt;span class="b"&gt;Ba&lt;/span&gt;
&lt;span class="c"&gt;Ba&lt;/span&gt;
We get to make a consequence.
&lt;/p&gt;</code></pre>
<p>It will generate 3 <em>line-boxes</em>:</p>
<ul>
<li>the first and last one each contain a single anonymous inline element (text content)</li>
<li>the second one contains two anonymous inline elements, and the 3 <code>&lt;span&gt;</code></li>
</ul>
<figure><img src="/content/01-blog/30-css-avance-metriques-des-fontes-line-height-et-vertical-align/line-boxes.png"><figcaption class="caption">A <code>&lt;p&gt;</code> (black border) is made of line-boxes (white borders) that contain inline elements (solid borders) and anonymous inline elements (dashed borders)</figcaption></figure>
<p>We clearly see that the second <em>line-box</em> is taller than the others, due to the computed <em>content-area</em> of its children, and more specifically, the one using the Catamaran font.</p>
<p><strong>The difficult part in the <em>line-box</em> creation is that we can’t really see, nor control it with CSS</strong>. Even applying a background to <code>::first-line</code> does not give us any visual clue on the first <em>line-box</em>’s height.</p>
<h2 id="line-height-problems-and-beyond"><code>line-height</code>: to the problems and beyond<a href="#line-height-problems-and-beyond" class="self-link"></a></h2>
<p>Until now, I introduced two notions: <em>content-area</em> and <em>line-box</em>. If you’ve read it well, I told that a <em>line-box</em>’s height is computed according to its children’s height, I didn’t say its children <em>content-area</em>’s height. And that makes a big difference.</p>
<p>Even though it may sound strange, <strong>an inline element has two different height: the <em>content-area</em> height and the <em>virtual-area</em> height</strong> (I invented the term <em>virtual-area</em> as the height is invisible to us, but you won’t find any occurrence in the spec).</p>
<ul>
<li>the <em>content-area</em> height is defined by the font metrics (as seen before)</li>
<li><strong>the <em>virtual-area</em> height is the <code>line-height</code></strong>, and it is the height <strong>used to compute the <em>line-box</em>’s height</strong></li>
</ul>
<figure><img src="/content/01-blog/30-css-avance-metriques-des-fontes-line-height-et-vertical-align/line-height.png"><figcaption class="caption">Inline elements have two different height</figcaption></figure>
<p>That being said, it breaks down the popular belief that <code>line-height</code> is the distance between baselines. In CSS, it is not .</p>
<figure><img src="/content/01-blog/30-css-avance-metriques-des-fontes-line-height-et-vertical-align/line-height-yes-no.png"><figcaption class="caption">In CSS, the line-height is not the distance between baselines</figcaption></figure>
<p>The computed difference of height between the <em>virtual-area</em> and the <em>content-area</em> is called the leading. Half this leading is added on top of the <em>content-area</em>, the other half is added on the bottom. <strong>The <em>content-area</em> is therefore always on the middle of the <em>virtual-area</em></strong>.</p>
<p>Based on its computed value, the <code>line-height</code> (<em>virtual-area</em>) can be equal, taller or smaller than the <em>content-area</em>. In case of a smaller <em>virtual-area</em>, leading is negative and a <em>line-box</em> is visually smaller than its children.</p>
<p>There are also other kind of inline elements:</p>
<ul>
<li>replaced inline elements (<code>&lt;img&gt;</code>, <code>&lt;input&gt;</code>, <code>&lt;svg&gt;</code>, etc.)</li>
<li><code>inline-block</code> and all <code>inline-*</code> elements</li>
<li>inline elements that participate in a specific formatting context (eg. in a flexbox element, all flex items are <em>blocksified</em>)</li>
</ul>
<p>For these specific inline elements, height is computed based on their <code>height</code>, <code>margin</code> and <code>border</code> properties. If <code>height</code> is <code>auto</code>, then <code>line-height</code> is used and the <em>content-area</em> is strictly equal to the <code>line-height</code>.</p>
<figure><img src="/content/01-blog/30-css-avance-metriques-des-fontes-line-height-et-vertical-align/line-height-inline-block.png"><figcaption class="caption">Inline replaced elements, inline-block/inline-* and blocksified inline elements have a content-area equal to their height, or line-height</figcaption></figure>
<p>Anyway, the problem we’re still facing is how much the <code>line-height</code>’s <code>normal</code> value is? And the answer, as for the computation of the <em>content-area</em>’s height, is to be found inside the font metrics.</p>
<p>So let’s go back to FontForge. The Catamaran’s em-square is 1000, but we’re seeing many ascender/descender values:</p>
<ul>
<li>generals <em>Ascent/Descent</em>: ascender is 770 and descender is 230. Used for character drawings. (table <em>“OS/2”</em>)</li>
<li>metrics <em>Ascent/Descent</em>: ascender is 1100 and descender is 540. Used for <em>content-area</em>’s height. (table <em>“hhea”</em> and table <em>“OS/2”</em>)</li>
<li>metric <em>Line Gap</em>. Used for <code>line-height: normal</code>, by adding this value to <em>Ascent/Descent</em> metrics. (table <em>“hhea”</em>)</li>
</ul>
<p>In our case, the Catamaran font defines a 0 unit line gap, so <strong><code>line-height: normal</code> will be equal to the <em>content-area</em>, which is 1640 units, or 1.64</strong>.</p>
<p>As a comparison, the Arial font describes an em-square of 2048 units, an ascender of 1854, a descender of 434 and a line gap of 67. It means that <code>font-size: 100px</code> gives a <em>content-area</em> of 112px (1117 units) and a <code>line-height: normal</code> of 115px (1150 units or 1.15). All these metrics are font-specific, and set by the font designer.</p>
<p><strong>It becomes obvious that setting <code>line-height: 1</code> is a bad practice</strong>. I remind you that unitless values are <code>font-size</code> relative, not <em>content-area</em> relative, and dealing with a <em>virtual-area</em> smaller than the <em>content-area</em> is the origin of many of our problems.</p>
<figure><img src="/content/01-blog/30-css-avance-metriques-des-fontes-line-height-et-vertical-align/line-height-1.png"><figcaption class="caption">Using line-height: 1 can create a line-box smaller than the content-area</figcaption></figure>
<p>But not only <code>line-height: 1</code>. For what it’s worth, on the 1117 fonts installed on my computer (yes, <a href="https://github.com/qrpike/Web-Font-Load">I installed all fonts from Google Web Fonts</a>), 1059 fonts, around 95%, have a computed <code>line-height</code> greater than 1. Their computed <code>line-height</code> goes from 0.618 to 3.378. You’ve read it well, 3.378!</p>
<p>Small details on <em>line-box</em> computation:</p>
<ul>
<li>for inline elements, <code>padding</code> and <code>border</code> increases the background area, but not the <em>content-area</em>’s height (nor the <em>line-box</em>’s height). The <em>content-area</em> is therefore not always what you see on screen. <code>margin-top</code> and <code>margin-bottom</code> have no effect.</li>
<li>for replaced inline elements, <code>inline-block</code> and <em>blocksified</em> inline elements: <code>padding</code>, <code>margin</code> and <code>border</code> increases the <code>height</code>, so the <em>content-area</em> and <em>line-box</em>’s height</li>
</ul>
<h2 id="vertical-align-one-property-rule-them-all"><code>vertical-align</code>: one property to rule them all<a href="#vertical-align-one-property-rule-them-all" class="self-link"></a></h2>
<p>I didn’t mention the <code>vertical-align</code> property yet, even though it is an essential factor to compute a <em>line-box</em>’s height. We can even say that <strong><code>vertical-align</code> may have the leading role in inline formatting context</strong>.</p>
<p>The default value is <code>baseline</code>. Do you remind font metrics ascender and descender? These values determine where the baseline stands, and so the ratio. As the ratio between ascenders and descenders is rarely 50/50, it may produce unexpected results, for example with siblings elements.</p>
<p>Start with that code:</p>
<pre class="language-markup"><code>&lt;p&gt;
&lt;span&gt;Ba&lt;/span&gt;
&lt;span&gt;Ba&lt;/span&gt;
&lt;/p&gt;</code></pre>
<pre><code>p {
font-family: Catamaran;
font-size: 100px;
line-height: 200px;
}</code></pre>
<p>A <code>&lt;p&gt;</code> tag with 2 siblings <code>&lt;span&gt;</code> inheriting <code>font-family</code>, <code>font-size</code> and fixed <code>line-height</code>. Baselines will match and the <em>line-box</em>’s height is equal to their <code>line-height</code>.</p>
<figure><img src="/content/01-blog/30-css-avance-metriques-des-fontes-line-height-et-vertical-align/vertical-align-baseline.png"><figcaption class="caption">Same font values, same baselines, everything seems OK</figcaption></figure>
<p>What if the second element has a smaller <code>font-size</code>?</p>
<pre><code>span:last-child {
font-size: 50px;
}</code></pre>
<p>As strange as it sounds, <strong>default baseline alignment may result in a higher (!) <em>line-box</em></strong>, as seen in the image below. I remind you that a <em>line-box</em>’s height is computed from its child’s highest point to its child’s lowest point.</p>
<figure><img src="/content/01-blog/30-css-avance-metriques-des-fontes-line-height-et-vertical-align/vertical-align-baseline-nok.png"><figcaption class="caption">A smaller child element may result in a higher line-box's height</figcaption></figure>
<p>That could be <a href="http://allthingssmitty.com/2017/01/30/nope-nope-nope-line-height-is-unitless/">an argument in favor of using <code>line-height</code> unitless values</a>, but sometimes you need fixed ones to <a href="https://scotch.io/tutorials/aesthetic-sass-3-typography-and-vertical-rhythm#baseline-grids-and-vertical-rhythm">create a perfect vertical rhythm</a>. <strong>To be honest, no matter what you choose, you’ll always have trouble with inline alignments</strong>.</p>
<p>Look at this another example. A <code>&lt;p&gt;</code> tag with <code>line-height: 200px</code>, containing a single <code>&lt;span&gt;</code> inheriting <code>line-height</code></p>
<pre class="language-markup"><code>&lt;p&gt;
&lt;span&gt;Ba&lt;/span&gt;
&lt;/p&gt;</code></pre>
<pre><code>p {
line-height: 200px;
}
span {
font-family: Catamaran;
font-size: 100px;
}</code></pre>
<p>How high is the <em>line-box</em>? We should expect 200px, but it’s not what we get. The problem here is that the <code>&lt;p&gt;</code> has its own, different <code>font-family</code> (default to <code>serif</code>). Baselines between the <code>&lt;p&gt;</code> tag and the <code>&lt;span&gt;</code> are likely to be different, the height of the <em>line-box</em> is therefore higher than expected. <strong>This happens because browsers do their computation as if each <em>line-box</em> starts with a zero-width character</strong>, that the spec called a strut.</p>
<blockquote>
<p>An invisible character, but a visible impact.</p>
</blockquote>
<p>To resume, we’re facing the same previous problem as for siblings elements.</p>
<figure><img src="/content/01-blog/30-css-avance-metriques-des-fontes-line-height-et-vertical-align/vertical-align-strut.png"><figcaption class="caption">Each child is aligned as if its line-box starts with an invisible zero-width character</figcaption></figure>
<p>Baseline alignment is screwed, but what about <code>vertical-align: middle</code> to the rescue? As you can read in the spec, <code>middle</code> “aligns the vertical midpoint of the box with the baseline of the parent box plus half the x-height of the parent”. <strong>Baselines ratio are different, as well as x-height ratio, so <code>middle</code> alignment isn’t reliable either</strong>. Worst, in most scenarios, <code>middle</code> is never really “at the middle”. Too many factors are involved and cannot be set via CSS (x-height, ascender/descender ratio, etc.)</p>
<p>As a side note, there are 4 other values, that may be useful in some cases:</p>
<ul>
<li><code>vertical-align: top</code> / <code>bottom</code> align to the top or the bottom of the <em>line-box</em></li>
<li><code>vertical-align: text-top</code> / <code>text-bottom</code> align to the top or the bottom of the <em>content-area</em></li>
</ul>
<figure><img src="/content/01-blog/30-css-avance-metriques-des-fontes-line-height-et-vertical-align/vertical-align-top-bottom-text.png"><figcaption class="caption">Vertical-align: top, bottom, text-top and text-bottom</figcaption></figure>
<p>Be careful though, in all cases, it aligns the <em>virtual-area</em>, so the invisible height. Look at this simple example using <code>vertical-align: top</code>. <strong>Invisible <code>line-height</code> may produce odd, but unsurprising, results</strong>.</p>
<figure><img src="/content/01-blog/30-css-avance-metriques-des-fontes-line-height-et-vertical-align/vertical-align-top-virtual-height.png"><figcaption class="caption">vertical-align may produce odd result at first, but expected when visualizing line-height</figcaption></figure>
<p>Finally, <code>vertical-align</code> also accepts numerical values which raise or lower the box regarding to the baseline. That last option could come in handy.</p>
<h2 id="css-awesome">CSS is awesome<a href="#css-awesome" class="self-link"></a></h2>
<p>We’ve talked about how <code>line-height</code> and <code>vertical-align</code> work together, but now the question is: are font metrics controllable with CSS? Short answer: no. Even if I really hope so.
Anyway, I think we have to play a bit. Font metrics are constant, so we should be able to do something.</p>
<p>What if, for example, we want a text using the Catamaran font, where the capital height is exactly 100px high? Seems doable: let’s do some maths.</p>
<p>First we set all font metrics as CSS custom properties , then compute <code>font-size</code> to get a capital height of 100px.</p>
<pre><code>p {
/* font metrics */
--font: Catamaran;
--fm-capitalHeight: 0.68;
--fm-descender: 0.54;
--fm-ascender: 1.1;
--fm-linegap: 0;

/* desired font-size for capital height */
--capital-height: 100;

/* apply font-family */
font-family: var(--font);

/* compute font-size to get capital height equal desired font-size */
--computedFontSize: (var(--capital-height) / var(--fm-capitalHeight));
font-size: calc(var(--computedFontSize) * 1px);
}</code></pre>
<figure><img src="/content/01-blog/30-css-avance-metriques-des-fontes-line-height-et-vertical-align/css-metrics-capital-height.png"><figcaption class="caption">The capital height is now 100px high</figcaption></figure>
<p>Pretty straightforward, isn’t it? But what if we want the text to be visually at the middle, so that the remaining space is equally distributed on top and bottom of the “B” letter? To achieve that, we have to compute <code>vertical-align</code> based on ascender/descender ratio.</p>
<p>First, compute <code>line-height: normal</code> and <em>content-area</em>’s height:</p>
<pre><code>p {
--lineheightNormal: (var(--fm-ascender) + var(--fm-descender) + var(--fm-linegap));
--contentArea: (var(--lineheightNormal) * var(--computedFontSize));
}</code></pre>
<p>Then, we need:</p>
<ul>
<li>the distance from the bottom of the capital letter to the bottom edge</li>
<li>the distance from the top of the capital letter to the top edge</li>
</ul>
<p>Like so:</p>
<pre><code>p {
--distanceBottom: (var(--fm-descender));
--distanceTop: (var(--fm-ascender) - var(--fm-capitalHeight));
}</code></pre>
<p>We can now compute <code>vertical-align</code>, which is the difference between the distances multiplied by the computed <code>font-size</code>. (we must apply this value to an inline child element)</p>
<pre><code>p {
--valign: ((var(--distanceBottom) - var(--distanceTop)) * var(--computedFontSize));
}
span {
vertical-align: calc(var(--valign) * -1px);
}</code></pre>
<p>At the end, we set the desired <code>line-height</code> and compute it while maintaining a vertical alignment:</p>
<pre><code>p {
/* desired line-height */
--line-height: 3;
line-height: calc(((var(--line-height) * var(--capital-height)) - var(--valign)) * 1px);
}</code></pre>
<figure><img src="/content/01-blog/30-css-avance-metriques-des-fontes-line-height-et-vertical-align/css-metrics-results-line-height.png"><figcaption class="caption">Results with different line-height. The text is always on the middle</figcaption></figure>
<p>Adding an icon whose height is matching the letter “B” is now easy:</p>
<pre><code>span::before {
content: '';
display: inline-block;
width: calc(1px * var(--capital-height));
height: calc(1px * var(--capital-height));
margin-right: 10px;
background: url('https://cdn.pbrd.co/images/yBAKn5bbv.png');
background-size: cover;
}</code></pre>
<figure><img src="/content/01-blog/30-css-avance-metriques-des-fontes-line-height-et-vertical-align/css-metrics-results-icon.png"><figcaption class="caption">Icon and B letter are the same height</figcaption></figure>
<p><a href="http://jsbin.com/tufatir/edit?css,output">See result in JSBin</a></p>
<p>Note that this test is for demonstration purpose only. You can’t rely on this. Many reasons:</p>
<ul>
<li>unless font metrics are constant, <a href="https://www.brunildo.org/test/normal-lh-plot.html">computations in browsers are not</a> ¯⁠\<em>⁠(ツ)⁠</em>/⁠¯</li>
<li>if font is not loaded, fallback font has probably different font metrics, and dealing with multiple values will quickly become quite unmanageable</li>
</ul>
<h2 id="takeaways">Takeaways<a href="#takeaways" class="self-link"></a></h2>
<p>What we learned:</p>
<ul>
<li>inline formatting context is really hard to understand</li>
<li>all inline elements have 2 height:
<ul>
<li>the <em>content-area</em> (based on font metrics)</li>
<li>the <em>virtual-area</em> (<code>line-height</code>)</li>
<li>none of these 2 heights can be visualize with no doubt. (if you're a devtools developer and want to work on this, it could be awesome)</li>
</ul></li>
<li><code>line-height: normal</code> is based on font metrics</li>
<li><code>line-height: n</code> may create a <em>virtual-area</em> smaller than <em>content-area</em></li>
<li><code>vertical-align</code> is not very reliable</li>
<li>a <em>line-box</em>’s height is computed based on its children’s <code>line-height</code> and <code>vertical-align</code> properties</li>
<li>we cannot easily get/set font metrics with CSS</li>
</ul>
<p>But I still love CSS :)</p>
</article>


<hr>

<footer>
<p>
<a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
</svg> Accueil</a> •
<a href="/david/log/" title="Accès au flux RSS"><svg class="icon icon-rss2">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-rss2"></use>
</svg> Suivre</a> •
<a href="http://larlet.com" title="Go to my English profile" data-instant><svg class="icon icon-user-tie">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-user-tie"></use>
</svg> Pro</a> •
<a href="mailto:david%40larlet.fr" title="Envoyer un courriel"><svg class="icon icon-mail">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-mail"></use>
</svg> Email</a> •
<abbr class="nowrap" title="Hébergeur : Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33184162340"><svg class="icon icon-hammer2">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-hammer2"></use>
</svg> Légal</abbr>
</p>
<template id="theme-selector">
<form>
<fieldset>
<legend><svg class="icon icon-brightness-contrast">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-brightness-contrast"></use>
</svg> Thème</legend>
<label>
<input type="radio" value="auto" name="chosen-color-scheme" checked> Auto
</label>
<label>
<input type="radio" value="dark" name="chosen-color-scheme"> Foncé
</label>
<label>
<input type="radio" value="light" name="chosen-color-scheme"> Clair
</label>
</fieldset>
</form>
</template>
</footer>
<script src="/static/david/js/instantpage-5.1.0.min.js" type="module"></script>
<script>
function loadThemeForm(templateName) {
const themeSelectorTemplate = document.querySelector(templateName)
const form = themeSelectorTemplate.content.firstElementChild
themeSelectorTemplate.replaceWith(form)

form.addEventListener('change', (e) => {
const chosenColorScheme = e.target.value
localStorage.setItem('theme', chosenColorScheme)
toggleTheme(chosenColorScheme)
})

const selectedTheme = localStorage.getItem('theme')
if (selectedTheme && selectedTheme !== 'undefined') {
form.querySelector(`[value="${selectedTheme}"]`).checked = true
}
}

const prefersColorSchemeDark = '(prefers-color-scheme: dark)'
window.addEventListener('load', () => {
let hasDarkRules = false
for (const styleSheet of Array.from(document.styleSheets)) {
let mediaRules = []
for (const cssRule of styleSheet.cssRules) {
if (cssRule.type !== CSSRule.MEDIA_RULE) {
continue
}
// WARNING: Safari does not have/supports `conditionText`.
if (cssRule.conditionText) {
if (cssRule.conditionText !== prefersColorSchemeDark) {
continue
}
} else {
if (cssRule.cssText.startsWith(prefersColorSchemeDark)) {
continue
}
}
mediaRules = mediaRules.concat(Array.from(cssRule.cssRules))
}

// WARNING: do not try to insert a Rule to a styleSheet you are
// currently iterating on, otherwise the browser will be stuck
// in a infinite loop…
for (const mediaRule of mediaRules) {
styleSheet.insertRule(mediaRule.cssText)
hasDarkRules = true
}
}
if (hasDarkRules) {
loadThemeForm('#theme-selector')
}
})
</script>
</body>
</html>

+ 240
- 0
cache/2024/157e744e8062e5495ba700566e99f8f2/index.md View File

@@ -0,0 +1,240 @@
title: Deep dive CSS: font metrics, line-height and vertical-align
url: https://iamvdo.me/en/blog/css-font-metrics-line-height-and-vertical-align
hash_url: 157e744e8062e5495ba700566e99f8f2
archive_date: 2024-04-18
og_image: http://iamvdo.me/images/apple-touch-152.png
description: An introduction to the inline formatting context. Explores line-height and vertical-align properties, as well as the font metrics. Understand how text is rendered on screen, and how to control it with CSS.
favicon: https://iamvdo.me/images/favicon.png
language: en_US

<p><code>Line-height</code> and <code>vertical-align</code> are simple CSS properties. So simple that most of us are convinced to fully understand how they work and how to use them. But it’s not. They really are complex, maybe the hardest ones, as <strong>they have a major role in the creation of one of the less-known feature of CSS: inline formatting context</strong>.</p>
<p>For example, <code>line-height</code> can be set as a length or a unitless value , but the default is <code>normal</code>. OK, but what normal is? We often read that it is (or should be) 1, or maybe 1.2, even the <a href="https://www.w3.org/TR/CSS2/visudet.html#propdef-line-height">CSS spec is unclear on that point</a>. We know that unitless <code>line-height</code> is <code>font-size</code> relative, but the problem is that <code>font-size: 100px</code> behaves differently across font-families, so is <code>line-height</code> always the same or different? Is it really between 1 and 1.2? And <code>vertical-align</code>, what are its implications regarding <code>line-height</code>?</p>
<p>Deep dive into a not-so-simple CSS mechanism…</p>
<h2 id="lets-talk-about-font-size-first">Let’s talk about <code>font-size</code> first<a href="#lets-talk-about-font-size-first" class="self-link"></a></h2>
<p>Look at this simple HTML code, a <code>&lt;p&gt;</code> tag containing 3 <code>&lt;span&gt;</code>, each with a different <code>font-family</code>:</p>
<pre class="language-markup"><code>&lt;p&gt;
&lt;span class="a"&gt;Ba&lt;/span&gt;
&lt;span class="b"&gt;Ba&lt;/span&gt;
&lt;span class="c"&gt;Ba&lt;/span&gt;
&lt;/p&gt;</code></pre>
<pre><code>p { font-size: 100px }
.a { font-family: Helvetica }
.b { font-family: Gruppo }
.c { font-family: Catamaran }</code></pre>
<p>Using the same <code>font-size</code> with different font-families produce elements with various heights:</p>
<figure><img src="/content/01-blog/30-css-avance-metriques-des-fontes-line-height-et-vertical-align/font-size.png"><figcaption class="caption">Different font-families, same font-size, give various heights</figcaption></figure>
<p>Even if we’re aware of that behavior, why <code>font-size: 100px</code> does not create elements with 100px height? I’ve measured and found these values: Helvetica: 115px, Gruppo: 97px and Catamaran: 164px</p>
<figure><img src="/content/01-blog/30-css-avance-metriques-des-fontes-line-height-et-vertical-align/font-size-line-height.png"><figcaption class="caption">Elements with font-size: 100px have height that varies from 97px to 164px</figcaption></figure>
<p>Although it seems a bit weird at first, it’s totally expected. <strong>The reason lays down inside the font itself</strong>. Here is how it works:</p>
<ul>
<li>a font defines its <a href="http://designwithfontforge.com/en-US/The_EM_Square.html">em-square</a> (or UPM, units per em), a kind of container where each character will be drawn. This square uses relative units and is generally set at 1000 units. But it can also be 1024, 2048 or anything else.</li>
<li>based on its relative units, metrics of the fonts are set (ascender, descender, capital height, x-height, etc.). Note that some values can bleed outside of the em-square.</li>
<li>in the browser, relative units are scaled to fit the desired font-size.</li>
</ul>
<p>Let’s take the Catamaran font and open it in <a href="https://fontforge.github.io/en-US/">FontForge</a> to get metrics:</p>
<ul>
<li>the em-square is 1000</li>
<li>the ascender is 1100 and the descender is 540. After running some tests, it seems that browsers use the <em>HHead Ascent</em>/<em>Descent</em> values on Mac OS, and <em>Win Ascent</em>/<em>Descent</em> values on Windows (these values may differ!). We also note that <em>Capital Height</em> is 680 and <em>X height</em> is 485.</li>
</ul>
<figure><img src="/content/01-blog/30-css-avance-metriques-des-fontes-line-height-et-vertical-align/font-forge-metrics.png"><figcaption class="caption">Font metrics values using FontForge</figcaption></figure>
<p>That means the Catamaran font uses 1100 + 540 units in a 1000 units em-square, which gives a height of 164px when setting <code>font-size: 100px</code>. <strong>This computed height defines the <em>content-area</em> of an element</strong> and I will refer to this terminology for the rest of the article. You can think of the <em>content-area</em> as the area where the <code>background</code> property applies .</p>
<p>We can also predict that capital letters are 68px high (680 units) and lower case letters (x-height) are 49px high (485 units). As a result, <code>1ex</code> = 49px and <code>1em</code> = 100px, not 164px (thankfully, <code>em</code> is based on <code>font-size</code>, not computed height)</p>
<figure><img src="/content/01-blog/30-css-avance-metriques-des-fontes-line-height-et-vertical-align/upm-px-equivalent.png"><figcaption class="caption">Catamaran font: UPM —Units Per Em— and pixels equivalent using font-size: 100px</figcaption></figure>
<p>Before going deeper, a short note on what this it involves. When a <code>&lt;p&gt;</code> element is rendered on screen, it can be composed of many lines, according to its width. Each line is made up of one or many inline elements (HTML tags or anonymous inline elements for text content) and is called a <em>line-box</em>. <strong>The height of a <em>line-box</em> is based on its children’s height</strong>. The browser therefore computes the height for each inline elements, and thus the height of the <em>line-box</em> (from its child’s highest point to its child’s lowest point). As a result, a <em>line-box</em> is always tall enough to contain all its children (by default).</p>
<blockquote>
<p>Each HTML element is actually a stack of <em>line-boxes</em>. If you know the height of each <em>line-box</em>, you know the height of an element.</p>
</blockquote>
<p>If we update the previous HTML code like this:</p>
<pre class="language-markup"><code>&lt;p&gt;
Good design will be better.
&lt;span class="a"&gt;Ba&lt;/span&gt;
&lt;span class="b"&gt;Ba&lt;/span&gt;
&lt;span class="c"&gt;Ba&lt;/span&gt;
We get to make a consequence.
&lt;/p&gt;</code></pre>
<p>It will generate 3 <em>line-boxes</em>:</p>
<ul>
<li>the first and last one each contain a single anonymous inline element (text content)</li>
<li>the second one contains two anonymous inline elements, and the 3 <code>&lt;span&gt;</code></li>
</ul>
<figure><img src="/content/01-blog/30-css-avance-metriques-des-fontes-line-height-et-vertical-align/line-boxes.png"><figcaption class="caption">A <code>&lt;p&gt;</code> (black border) is made of line-boxes (white borders) that contain inline elements (solid borders) and anonymous inline elements (dashed borders)</figcaption></figure>
<p>We clearly see that the second <em>line-box</em> is taller than the others, due to the computed <em>content-area</em> of its children, and more specifically, the one using the Catamaran font.</p>
<p><strong>The difficult part in the <em>line-box</em> creation is that we can’t really see, nor control it with CSS</strong>. Even applying a background to <code>::first-line</code> does not give us any visual clue on the first <em>line-box</em>’s height.</p>
<h2 id="line-height-problems-and-beyond"><code>line-height</code>: to the problems and beyond<a href="#line-height-problems-and-beyond" class="self-link"></a></h2>
<p>Until now, I introduced two notions: <em>content-area</em> and <em>line-box</em>. If you’ve read it well, I told that a <em>line-box</em>’s height is computed according to its children’s height, I didn’t say its children <em>content-area</em>’s height. And that makes a big difference.</p>
<p>Even though it may sound strange, <strong>an inline element has two different height: the <em>content-area</em> height and the <em>virtual-area</em> height</strong> (I invented the term <em>virtual-area</em> as the height is invisible to us, but you won’t find any occurrence in the spec).</p>
<ul>
<li>the <em>content-area</em> height is defined by the font metrics (as seen before)</li>
<li><strong>the <em>virtual-area</em> height is the <code>line-height</code></strong>, and it is the height <strong>used to compute the <em>line-box</em>’s height</strong></li>
</ul>
<figure><img src="/content/01-blog/30-css-avance-metriques-des-fontes-line-height-et-vertical-align/line-height.png"><figcaption class="caption">Inline elements have two different height</figcaption></figure>
<p>That being said, it breaks down the popular belief that <code>line-height</code> is the distance between baselines. In CSS, it is not .</p>
<figure><img src="/content/01-blog/30-css-avance-metriques-des-fontes-line-height-et-vertical-align/line-height-yes-no.png"><figcaption class="caption">In CSS, the line-height is not the distance between baselines</figcaption></figure>
<p>The computed difference of height between the <em>virtual-area</em> and the <em>content-area</em> is called the leading. Half this leading is added on top of the <em>content-area</em>, the other half is added on the bottom. <strong>The <em>content-area</em> is therefore always on the middle of the <em>virtual-area</em></strong>.</p>
<p>Based on its computed value, the <code>line-height</code> (<em>virtual-area</em>) can be equal, taller or smaller than the <em>content-area</em>. In case of a smaller <em>virtual-area</em>, leading is negative and a <em>line-box</em> is visually smaller than its children.</p>
<p>There are also other kind of inline elements:</p>
<ul>
<li>replaced inline elements (<code>&lt;img&gt;</code>, <code>&lt;input&gt;</code>, <code>&lt;svg&gt;</code>, etc.)</li>
<li><code>inline-block</code> and all <code>inline-*</code> elements</li>
<li>inline elements that participate in a specific formatting context (eg. in a flexbox element, all flex items are <em>blocksified</em>)</li>
</ul>
<p>For these specific inline elements, height is computed based on their <code>height</code>, <code>margin</code> and <code>border</code> properties. If <code>height</code> is <code>auto</code>, then <code>line-height</code> is used and the <em>content-area</em> is strictly equal to the <code>line-height</code>.</p>
<figure><img src="/content/01-blog/30-css-avance-metriques-des-fontes-line-height-et-vertical-align/line-height-inline-block.png"><figcaption class="caption">Inline replaced elements, inline-block/inline-* and blocksified inline elements have a content-area equal to their height, or line-height</figcaption></figure>
<p>Anyway, the problem we’re still facing is how much the <code>line-height</code>’s <code>normal</code> value is? And the answer, as for the computation of the <em>content-area</em>’s height, is to be found inside the font metrics.</p>
<p>So let’s go back to FontForge. The Catamaran’s em-square is 1000, but we’re seeing many ascender/descender values:</p>
<ul>
<li>generals <em>Ascent/Descent</em>: ascender is 770 and descender is 230. Used for character drawings. (table <em>“OS/2”</em>)</li>
<li>metrics <em>Ascent/Descent</em>: ascender is 1100 and descender is 540. Used for <em>content-area</em>’s height. (table <em>“hhea”</em> and table <em>“OS/2”</em>)</li>
<li>metric <em>Line Gap</em>. Used for <code>line-height: normal</code>, by adding this value to <em>Ascent/Descent</em> metrics. (table <em>“hhea”</em>)</li>
</ul>
<p>In our case, the Catamaran font defines a 0 unit line gap, so <strong><code>line-height: normal</code> will be equal to the <em>content-area</em>, which is 1640 units, or 1.64</strong>.</p>
<p>As a comparison, the Arial font describes an em-square of 2048 units, an ascender of 1854, a descender of 434 and a line gap of 67. It means that <code>font-size: 100px</code> gives a <em>content-area</em> of 112px (1117 units) and a <code>line-height: normal</code> of 115px (1150 units or 1.15). All these metrics are font-specific, and set by the font designer.</p>
<p><strong>It becomes obvious that setting <code>line-height: 1</code> is a bad practice</strong>. I remind you that unitless values are <code>font-size</code> relative, not <em>content-area</em> relative, and dealing with a <em>virtual-area</em> smaller than the <em>content-area</em> is the origin of many of our problems.</p>
<figure><img src="/content/01-blog/30-css-avance-metriques-des-fontes-line-height-et-vertical-align/line-height-1.png"><figcaption class="caption">Using line-height: 1 can create a line-box smaller than the content-area</figcaption></figure>
<p>But not only <code>line-height: 1</code>. For what it’s worth, on the 1117 fonts installed on my computer (yes, <a href="https://github.com/qrpike/Web-Font-Load">I installed all fonts from Google Web Fonts</a>), 1059 fonts, around 95%, have a computed <code>line-height</code> greater than 1. Their computed <code>line-height</code> goes from 0.618 to 3.378. You’ve read it well, 3.378!</p>
<p>Small details on <em>line-box</em> computation:</p>
<ul>
<li>for inline elements, <code>padding</code> and <code>border</code> increases the background area, but not the <em>content-area</em>’s height (nor the <em>line-box</em>’s height). The <em>content-area</em> is therefore not always what you see on screen. <code>margin-top</code> and <code>margin-bottom</code> have no effect.</li>
<li>for replaced inline elements, <code>inline-block</code> and <em>blocksified</em> inline elements: <code>padding</code>, <code>margin</code> and <code>border</code> increases the <code>height</code>, so the <em>content-area</em> and <em>line-box</em>’s height</li>
</ul>
<h2 id="vertical-align-one-property-rule-them-all"><code>vertical-align</code>: one property to rule them all<a href="#vertical-align-one-property-rule-them-all" class="self-link"></a></h2>
<p>I didn’t mention the <code>vertical-align</code> property yet, even though it is an essential factor to compute a <em>line-box</em>’s height. We can even say that <strong><code>vertical-align</code> may have the leading role in inline formatting context</strong>.</p>
<p>The default value is <code>baseline</code>. Do you remind font metrics ascender and descender? These values determine where the baseline stands, and so the ratio. As the ratio between ascenders and descenders is rarely 50/50, it may produce unexpected results, for example with siblings elements.</p>
<p>Start with that code:</p>
<pre class="language-markup"><code>&lt;p&gt;
&lt;span&gt;Ba&lt;/span&gt;
&lt;span&gt;Ba&lt;/span&gt;
&lt;/p&gt;</code></pre>
<pre><code>p {
font-family: Catamaran;
font-size: 100px;
line-height: 200px;
}</code></pre>
<p>A <code>&lt;p&gt;</code> tag with 2 siblings <code>&lt;span&gt;</code> inheriting <code>font-family</code>, <code>font-size</code> and fixed <code>line-height</code>. Baselines will match and the <em>line-box</em>’s height is equal to their <code>line-height</code>.</p>
<figure><img src="/content/01-blog/30-css-avance-metriques-des-fontes-line-height-et-vertical-align/vertical-align-baseline.png"><figcaption class="caption">Same font values, same baselines, everything seems OK</figcaption></figure>
<p>What if the second element has a smaller <code>font-size</code>?</p>
<pre><code>span:last-child {
font-size: 50px;
}</code></pre>
<p>As strange as it sounds, <strong>default baseline alignment may result in a higher (!) <em>line-box</em></strong>, as seen in the image below. I remind you that a <em>line-box</em>’s height is computed from its child’s highest point to its child’s lowest point.</p>
<figure><img src="/content/01-blog/30-css-avance-metriques-des-fontes-line-height-et-vertical-align/vertical-align-baseline-nok.png"><figcaption class="caption">A smaller child element may result in a higher line-box's height</figcaption></figure>
<p>That could be <a href="http://allthingssmitty.com/2017/01/30/nope-nope-nope-line-height-is-unitless/">an argument in favor of using <code>line-height</code> unitless values</a>, but sometimes you need fixed ones to <a href="https://scotch.io/tutorials/aesthetic-sass-3-typography-and-vertical-rhythm#baseline-grids-and-vertical-rhythm">create a perfect vertical rhythm</a>. <strong>To be honest, no matter what you choose, you’ll always have trouble with inline alignments</strong>.</p>
<p>Look at this another example. A <code>&lt;p&gt;</code> tag with <code>line-height: 200px</code>, containing a single <code>&lt;span&gt;</code> inheriting <code>line-height</code></p>
<pre class="language-markup"><code>&lt;p&gt;
&lt;span&gt;Ba&lt;/span&gt;
&lt;/p&gt;</code></pre>
<pre><code>p {
line-height: 200px;
}
span {
font-family: Catamaran;
font-size: 100px;
}</code></pre>
<p>How high is the <em>line-box</em>? We should expect 200px, but it’s not what we get. The problem here is that the <code>&lt;p&gt;</code> has its own, different <code>font-family</code> (default to <code>serif</code>). Baselines between the <code>&lt;p&gt;</code> tag and the <code>&lt;span&gt;</code> are likely to be different, the height of the <em>line-box</em> is therefore higher than expected. <strong>This happens because browsers do their computation as if each <em>line-box</em> starts with a zero-width character</strong>, that the spec called a strut.</p>
<blockquote>
<p>An invisible character, but a visible impact.</p>
</blockquote>
<p>To resume, we’re facing the same previous problem as for siblings elements.</p>
<figure><img src="/content/01-blog/30-css-avance-metriques-des-fontes-line-height-et-vertical-align/vertical-align-strut.png"><figcaption class="caption">Each child is aligned as if its line-box starts with an invisible zero-width character</figcaption></figure>
<p>Baseline alignment is screwed, but what about <code>vertical-align: middle</code> to the rescue? As you can read in the spec, <code>middle</code> “aligns the vertical midpoint of the box with the baseline of the parent box plus half the x-height of the parent”. <strong>Baselines ratio are different, as well as x-height ratio, so <code>middle</code> alignment isn’t reliable either</strong>. Worst, in most scenarios, <code>middle</code> is never really “at the middle”. Too many factors are involved and cannot be set via CSS (x-height, ascender/descender ratio, etc.)</p>
<p>As a side note, there are 4 other values, that may be useful in some cases:</p>
<ul>
<li><code>vertical-align: top</code> / <code>bottom</code> align to the top or the bottom of the <em>line-box</em></li>
<li><code>vertical-align: text-top</code> / <code>text-bottom</code> align to the top or the bottom of the <em>content-area</em></li>
</ul>
<figure><img src="/content/01-blog/30-css-avance-metriques-des-fontes-line-height-et-vertical-align/vertical-align-top-bottom-text.png"><figcaption class="caption">Vertical-align: top, bottom, text-top and text-bottom</figcaption></figure>
<p>Be careful though, in all cases, it aligns the <em>virtual-area</em>, so the invisible height. Look at this simple example using <code>vertical-align: top</code>. <strong>Invisible <code>line-height</code> may produce odd, but unsurprising, results</strong>.</p>
<figure><img src="/content/01-blog/30-css-avance-metriques-des-fontes-line-height-et-vertical-align/vertical-align-top-virtual-height.png"><figcaption class="caption">vertical-align may produce odd result at first, but expected when visualizing line-height</figcaption></figure>
<p>Finally, <code>vertical-align</code> also accepts numerical values which raise or lower the box regarding to the baseline. That last option could come in handy.</p>
<h2 id="css-awesome">CSS is awesome<a href="#css-awesome" class="self-link"></a></h2>
<p>We’ve talked about how <code>line-height</code> and <code>vertical-align</code> work together, but now the question is: are font metrics controllable with CSS? Short answer: no. Even if I really hope so.
Anyway, I think we have to play a bit. Font metrics are constant, so we should be able to do something.</p>
<p>What if, for example, we want a text using the Catamaran font, where the capital height is exactly 100px high? Seems doable: let’s do some maths.</p>
<p>First we set all font metrics as CSS custom properties , then compute <code>font-size</code> to get a capital height of 100px.</p>
<pre><code>p {
/* font metrics */
--font: Catamaran;
--fm-capitalHeight: 0.68;
--fm-descender: 0.54;
--fm-ascender: 1.1;
--fm-linegap: 0;

/* desired font-size for capital height */
--capital-height: 100;

/* apply font-family */
font-family: var(--font);

/* compute font-size to get capital height equal desired font-size */
--computedFontSize: (var(--capital-height) / var(--fm-capitalHeight));
font-size: calc(var(--computedFontSize) * 1px);
}</code></pre>
<figure><img src="/content/01-blog/30-css-avance-metriques-des-fontes-line-height-et-vertical-align/css-metrics-capital-height.png"><figcaption class="caption">The capital height is now 100px high</figcaption></figure>
<p>Pretty straightforward, isn’t it? But what if we want the text to be visually at the middle, so that the remaining space is equally distributed on top and bottom of the “B” letter? To achieve that, we have to compute <code>vertical-align</code> based on ascender/descender ratio.</p>
<p>First, compute <code>line-height: normal</code> and <em>content-area</em>’s height:</p>
<pre><code>p {
--lineheightNormal: (var(--fm-ascender) + var(--fm-descender) + var(--fm-linegap));
--contentArea: (var(--lineheightNormal) * var(--computedFontSize));
}</code></pre>
<p>Then, we need:</p>
<ul>
<li>the distance from the bottom of the capital letter to the bottom edge</li>
<li>the distance from the top of the capital letter to the top edge</li>
</ul>
<p>Like so:</p>
<pre><code>p {
--distanceBottom: (var(--fm-descender));
--distanceTop: (var(--fm-ascender) - var(--fm-capitalHeight));
}</code></pre>
<p>We can now compute <code>vertical-align</code>, which is the difference between the distances multiplied by the computed <code>font-size</code>. (we must apply this value to an inline child element)</p>
<pre><code>p {
--valign: ((var(--distanceBottom) - var(--distanceTop)) * var(--computedFontSize));
}
span {
vertical-align: calc(var(--valign) * -1px);
}</code></pre>
<p>At the end, we set the desired <code>line-height</code> and compute it while maintaining a vertical alignment:</p>
<pre><code>p {
/* desired line-height */
--line-height: 3;
line-height: calc(((var(--line-height) * var(--capital-height)) - var(--valign)) * 1px);
}</code></pre>
<figure><img src="/content/01-blog/30-css-avance-metriques-des-fontes-line-height-et-vertical-align/css-metrics-results-line-height.png"><figcaption class="caption">Results with different line-height. The text is always on the middle</figcaption></figure>
<p>Adding an icon whose height is matching the letter “B” is now easy:</p>
<pre><code>span::before {
content: '';
display: inline-block;
width: calc(1px * var(--capital-height));
height: calc(1px * var(--capital-height));
margin-right: 10px;
background: url('https://cdn.pbrd.co/images/yBAKn5bbv.png');
background-size: cover;
}</code></pre>
<figure><img src="/content/01-blog/30-css-avance-metriques-des-fontes-line-height-et-vertical-align/css-metrics-results-icon.png"><figcaption class="caption">Icon and B letter are the same height</figcaption></figure>
<p><a href="http://jsbin.com/tufatir/edit?css,output">See result in JSBin</a></p>
<p>Note that this test is for demonstration purpose only. You can’t rely on this. Many reasons:</p>
<ul>
<li>unless font metrics are constant, <a href="https://www.brunildo.org/test/normal-lh-plot.html">computations in browsers are not</a> ¯⁠\<em>⁠(ツ)⁠</em>/⁠¯</li>
<li>if font is not loaded, fallback font has probably different font metrics, and dealing with multiple values will quickly become quite unmanageable</li>
</ul>
<h2 id="takeaways">Takeaways<a href="#takeaways" class="self-link"></a></h2>
<p>What we learned:</p>
<ul>
<li>inline formatting context is really hard to understand</li>
<li>all inline elements have 2 height:
<ul>
<li>the <em>content-area</em> (based on font metrics)</li>
<li>the <em>virtual-area</em> (<code>line-height</code>)</li>
<li>none of these 2 heights can be visualize with no doubt. (if you're a devtools developer and want to work on this, it could be awesome)</li>
</ul></li>
<li><code>line-height: normal</code> is based on font metrics</li>
<li><code>line-height: n</code> may create a <em>virtual-area</em> smaller than <em>content-area</em></li>
<li><code>vertical-align</code> is not very reliable</li>
<li>a <em>line-box</em>’s height is computed based on its children’s <code>line-height</code> and <code>vertical-align</code> properties</li>
<li>we cannot easily get/set font metrics with CSS</li>
</ul>
<p>But I still love CSS :)</p>

+ 192
- 0
cache/2024/2b6f113e6c47bf6d2b282fa0a48b48a6/index.html View File

@@ -0,0 +1,192 @@
<!doctype html><!-- This is a valid HTML5 document. -->
<!-- Screen readers, SEO, extensions and so on. -->
<html lang="en">
<!-- Has to be within the first 1024 bytes, hence before the `title` element
See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
<meta charset="utf-8">
<!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
<!-- The viewport meta is quite crowded and we are responsible for that.
See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
<meta name="viewport" content="width=device-width,initial-scale=1">
<!-- Required to make a valid HTML5 document. -->
<title>Vibe Driven Development (archive) — David Larlet</title>
<meta name="description" content="Publication mise en cache pour en conserver une trace.">
<!-- That good ol' feed, subscribe :). -->
<link rel="alternate" type="application/atom+xml" title="Feed" href="/david/log/">
<!-- Generated from https://realfavicongenerator.net/ such a mess. -->
<link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons2/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons2/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons2/favicon-16x16.png">
<link rel="manifest" href="/static/david/icons2/site.webmanifest">
<link rel="mask-icon" href="/static/david/icons2/safari-pinned-tab.svg" color="#07486c">
<link rel="shortcut icon" href="/static/david/icons2/favicon.ico">
<meta name="msapplication-TileColor" content="#f7f7f7">
<meta name="msapplication-config" content="/static/david/icons2/browserconfig.xml">
<meta name="theme-color" content="#f7f7f7" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#272727" media="(prefers-color-scheme: dark)">
<!-- Is that even respected? Retrospectively? What a shAItshow…
https://neil-clarke.com/block-the-bots-that-feed-ai-models-by-scraping-your-website/ -->
<meta name="robots" content="noai, noimageai">
<!-- Documented, feel free to shoot an email. -->
<link rel="stylesheet" href="/static/david/css/style_2021-01-20.css">
<!-- See https://www.zachleat.com/web/comprehensive-webfonts/ for the trade-off. -->
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
<script>
function toggleTheme(themeName) {
document.documentElement.classList.toggle(
'forced-dark',
themeName === 'dark'
)
document.documentElement.classList.toggle(
'forced-light',
themeName === 'light'
)
}
const selectedTheme = localStorage.getItem('theme')
if (selectedTheme !== 'undefined') {
toggleTheme(selectedTheme)
}
</script>

<meta name="robots" content="noindex, nofollow">
<meta content="origin-when-cross-origin" name="referrer">
<!-- Canonical URL for SEO purposes -->
<link rel="canonical" href="https://robinrendle.com/notes/vibe-driven-development/">

<body class="remarkdown h1-underline h2-underline h3-underline em-underscore hr-center ul-star pre-tick" data-instant-intensity="viewport-all">


<article>
<header>
<h1>Vibe Driven Development</h1>
</header>
<nav>
<p class="center">
<a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
</svg> Accueil</a> •
<a href="https://robinrendle.com/notes/vibe-driven-development/" title="Lien vers le contenu original">Source originale</a>
<br>
Mis en cache le 2024-04-18
</p>
</nav>
<hr>
<p>Building a great product is a matter of two questions: How should we measure progress? And what should we build next?</p>
<p>That first question is the most important because how you collect data and what you count as success influences everything else; if your organization is measuring shit then they’ll build nothing but shit. And most product orgs suck and churn out garbage projects because they waste so much time thinking in terms of junk data and half baked user inputs to inform their decisions.</p>
<p>(Show me what your org measures and I’ll show ya the crappy product that comes out the other side.)</p>
<p>The problem underlying all this is that when it comes to building a product, all data is garbage, a lie, or measuring the wrong thing. Folks will be obsessed with clicks and charts and NPS scores—the NFTs of product management—and in this sea of noise they believe they can see the product clearly. There are courses and books and talks all about measuring happiness and growth—surveys! surveys! surveys!—with everyone in the field believing that they’ve built a science when they’ve really built a cult.</p>
<p>(No great product has ever been made because of the answers collected in a dumb user survey.)</p>
<p>So how do you measure progress then?</p>
<p>I guess customers could tell us the answer, right? Well, no. Sure, you can talk to customers to see <em>how</em> they struggle but they cannot tell you <em>why</em> they struggle. They’ll have terrible ideas for improvements like “I really wish AI could show me all the relevant things on this page” or “I want more dashboards” where the answer is always much simpler than that. Customer feedback is a geiger counter: they can tell you about the problem coming your way but not how to prevent it.</p>
<p>(Customers, like data, will always mislead us on what to build next.)</p>
<p>See, I don’t think you can build a great product for customers. Yes, yes, yes; you can make billions of dollars building something for customers and go live on a beach in the south of France. But you’ll have built junk in the process; the product will suffer if you build it for customers. You’ll spend every waking moment trying to measure user happiness and score feelings in a spreadsheet and not improving the product.</p>
<p>In every product org it feels as if folks mistake qualitative data—stuff that can’t be measured like feelings—with quantitative data—stuff that can be measured like numbers or time or temperature. They’ll say “this user is 4.5% happy” and, okay, great. Now what? This numerical value sure is bullshit but it’s not even helpful bullshit because these numbers never explain why things suck.</p>
<p>(Just look at the product and it will tell you why it sucks.)</p>
<p>It comes down to this annoying, upsetting, stupid fact: the only way to build a great product is to use it every day, to stare at it, to hold it in your hands to feel its lumps. The data and customers will lie to you but the product never will. And most product orgs suck because they simply don’t use the products that they’re building; they ship incremental nothings without direction because they’re looking at spreadsheets all day long filled with junk data nothings.</p>
<p>See, I don’t know much about product stuff. I have no experience as a product manager, no experience running teams or building a company. Take everything I say here with an enormous silo of salt. But: I don’t care what the data shows me and I’m not sure I ever will. You can show me charts and spreadsheets all day long and I will not care. Tell me what your gut says instead after relentless experience of the product every day. This is the only way to see the world clearly.</p>
<p>Perhaps arbitrary, perhaps a bit naive, but the answer is <a href="https://weeknotes.buttondown.email/archive/trading-time-for-visibility/">vibe-driven development</a>. If you have good experience of the product, your vibes will lead you down the right path of what to build next. I think this is why small orgs make better things faster than large orgs; they’re all about the vibes. Large orgs are bloated and frozen in place because they spend all their time talking about bullshit numbers instead of looking at the product. Whilst smaller orgs typically aren’t run by the numbers, they’re so focused on the product because they have to.</p>
<p>So when we use data to drive development it always leads us down the wrong path, it forces us to look in all the wrong places for the answers. Customers can tell you what sucks, sure. Dashboards and spreadsheets can show you attrition or whatever, yes. But these inputs can’t build a vision of your product for you. They’re mostly distractions and any moment with your eye off the product is a moment lost to making it better.</p>
<p>You can only build a great product if you care more for the vibes than for the data.</p>
</article>


<hr>

<footer>
<p>
<a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
</svg> Accueil</a> •
<a href="/david/log/" title="Accès au flux RSS"><svg class="icon icon-rss2">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-rss2"></use>
</svg> Suivre</a> •
<a href="http://larlet.com" title="Go to my English profile" data-instant><svg class="icon icon-user-tie">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-user-tie"></use>
</svg> Pro</a> •
<a href="mailto:david%40larlet.fr" title="Envoyer un courriel"><svg class="icon icon-mail">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-mail"></use>
</svg> Email</a> •
<abbr class="nowrap" title="Hébergeur : Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33184162340"><svg class="icon icon-hammer2">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-hammer2"></use>
</svg> Légal</abbr>
</p>
<template id="theme-selector">
<form>
<fieldset>
<legend><svg class="icon icon-brightness-contrast">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-brightness-contrast"></use>
</svg> Thème</legend>
<label>
<input type="radio" value="auto" name="chosen-color-scheme" checked> Auto
</label>
<label>
<input type="radio" value="dark" name="chosen-color-scheme"> Foncé
</label>
<label>
<input type="radio" value="light" name="chosen-color-scheme"> Clair
</label>
</fieldset>
</form>
</template>
</footer>
<script src="/static/david/js/instantpage-5.1.0.min.js" type="module"></script>
<script>
function loadThemeForm(templateName) {
const themeSelectorTemplate = document.querySelector(templateName)
const form = themeSelectorTemplate.content.firstElementChild
themeSelectorTemplate.replaceWith(form)

form.addEventListener('change', (e) => {
const chosenColorScheme = e.target.value
localStorage.setItem('theme', chosenColorScheme)
toggleTheme(chosenColorScheme)
})

const selectedTheme = localStorage.getItem('theme')
if (selectedTheme && selectedTheme !== 'undefined') {
form.querySelector(`[value="${selectedTheme}"]`).checked = true
}
}

const prefersColorSchemeDark = '(prefers-color-scheme: dark)'
window.addEventListener('load', () => {
let hasDarkRules = false
for (const styleSheet of Array.from(document.styleSheets)) {
let mediaRules = []
for (const cssRule of styleSheet.cssRules) {
if (cssRule.type !== CSSRule.MEDIA_RULE) {
continue
}
// WARNING: Safari does not have/supports `conditionText`.
if (cssRule.conditionText) {
if (cssRule.conditionText !== prefersColorSchemeDark) {
continue
}
} else {
if (cssRule.cssText.startsWith(prefersColorSchemeDark)) {
continue
}
}
mediaRules = mediaRules.concat(Array.from(cssRule.cssRules))
}

// WARNING: do not try to insert a Rule to a styleSheet you are
// currently iterating on, otherwise the browser will be stuck
// in a infinite loop…
for (const mediaRule of mediaRules) {
styleSheet.insertRule(mediaRule.cssText)
hasDarkRules = true
}
}
if (hasDarkRules) {
loadThemeForm('#theme-selector')
}
})
</script>
</body>
</html>

+ 25
- 0
cache/2024/2b6f113e6c47bf6d2b282fa0a48b48a6/index.md View File

@@ -0,0 +1,25 @@
title: Vibe Driven Development
url: https://robinrendle.com/notes/vibe-driven-development/
hash_url: 2b6f113e6c47bf6d2b282fa0a48b48a6
archive_date: 2024-04-18
og_image:
description: The website of Robin Rendle, a designer and writer from the UK.
favicon: https://robinrendle.com/images/favicons3/favicon-32x32.png
language: en_US

<p>Building a great product is a matter of two questions: How should we measure progress? And what should we build next?</p>
<p>That first question is the most important because how you collect data and what you count as success influences everything else; if your organization is measuring shit then they’ll build nothing but shit. And most product orgs suck and churn out garbage projects because they waste so much time thinking in terms of junk data and half baked user inputs to inform their decisions.</p>
<p>(Show me what your org measures and I’ll show ya the crappy product that comes out the other side.)</p>
<p>The problem underlying all this is that when it comes to building a product, all data is garbage, a lie, or measuring the wrong thing. Folks will be obsessed with clicks and charts and NPS scores—the NFTs of product management—and in this sea of noise they believe they can see the product clearly. There are courses and books and talks all about measuring happiness and growth—surveys! surveys! surveys!—with everyone in the field believing that they’ve built a science when they’ve really built a cult.</p>
<p>(No great product has ever been made because of the answers collected in a dumb user survey.)</p>
<p>So how do you measure progress then?</p>
<p>I guess customers could tell us the answer, right? Well, no. Sure, you can talk to customers to see <em>how</em> they struggle but they cannot tell you <em>why</em> they struggle. They’ll have terrible ideas for improvements like “I really wish AI could show me all the relevant things on this page” or “I want more dashboards” where the answer is always much simpler than that. Customer feedback is a geiger counter: they can tell you about the problem coming your way but not how to prevent it.</p>
<p>(Customers, like data, will always mislead us on what to build next.)</p>
<p>See, I don’t think you can build a great product for customers. Yes, yes, yes; you can make billions of dollars building something for customers and go live on a beach in the south of France. But you’ll have built junk in the process; the product will suffer if you build it for customers. You’ll spend every waking moment trying to measure user happiness and score feelings in a spreadsheet and not improving the product.</p>
<p>In every product org it feels as if folks mistake qualitative data—stuff that can’t be measured like feelings—with quantitative data—stuff that can be measured like numbers or time or temperature. They’ll say “this user is 4.5% happy” and, okay, great. Now what? This numerical value sure is bullshit but it’s not even helpful bullshit because these numbers never explain why things suck.</p>
<p>(Just look at the product and it will tell you why it sucks.)</p>
<p>It comes down to this annoying, upsetting, stupid fact: the only way to build a great product is to use it every day, to stare at it, to hold it in your hands to feel its lumps. The data and customers will lie to you but the product never will. And most product orgs suck because they simply don’t use the products that they’re building; they ship incremental nothings without direction because they’re looking at spreadsheets all day long filled with junk data nothings.</p>
<p>See, I don’t know much about product stuff. I have no experience as a product manager, no experience running teams or building a company. Take everything I say here with an enormous silo of salt. But: I don’t care what the data shows me and I’m not sure I ever will. You can show me charts and spreadsheets all day long and I will not care. Tell me what your gut says instead after relentless experience of the product every day. This is the only way to see the world clearly.</p>
<p>Perhaps arbitrary, perhaps a bit naive, but the answer is <a href="https://weeknotes.buttondown.email/archive/trading-time-for-visibility/">vibe-driven development</a>. If you have good experience of the product, your vibes will lead you down the right path of what to build next. I think this is why small orgs make better things faster than large orgs; they’re all about the vibes. Large orgs are bloated and frozen in place because they spend all their time talking about bullshit numbers instead of looking at the product. Whilst smaller orgs typically aren’t run by the numbers, they’re so focused on the product because they have to.</p>
<p>So when we use data to drive development it always leads us down the wrong path, it forces us to look in all the wrong places for the answers. Customers can tell you what sucks, sure. Dashboards and spreadsheets can show you attrition or whatever, yes. But these inputs can’t build a vision of your product for you. They’re mostly distractions and any moment with your eye off the product is a moment lost to making it better.</p>
<p>You can only build a great product if you care more for the vibes than for the data.</p>

+ 1171
- 0
cache/2024/35c44c8d8b999faa625ed6009da60e99/index.html
File diff suppressed because it is too large
View File


+ 1018
- 0
cache/2024/35c44c8d8b999faa625ed6009da60e99/index.md
File diff suppressed because it is too large
View File


+ 185
- 0
cache/2024/3a28346225751986cc06aaadb8c8bb90/index.html
File diff suppressed because it is too large
View File


+ 18
- 0
cache/2024/3a28346225751986cc06aaadb8c8bb90/index.md
File diff suppressed because it is too large
View File


+ 498
- 0
cache/2024/4bda7c6500950716846bdeb6bddbafed/index.html View File

@@ -0,0 +1,498 @@
<!doctype html><!-- This is a valid HTML5 document. -->
<!-- Screen readers, SEO, extensions and so on. -->
<html lang="en">
<!-- Has to be within the first 1024 bytes, hence before the `title` element
See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
<meta charset="utf-8">
<!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
<!-- The viewport meta is quite crowded and we are responsible for that.
See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
<meta name="viewport" content="width=device-width,initial-scale=1">
<!-- Required to make a valid HTML5 document. -->
<title>Hardest Problem in Computer Science: Centering Things (archive) — David Larlet</title>
<meta name="description" content="Publication mise en cache pour en conserver une trace.">
<!-- That good ol' feed, subscribe :). -->
<link rel="alternate" type="application/atom+xml" title="Feed" href="/david/log/">
<!-- Generated from https://realfavicongenerator.net/ such a mess. -->
<link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons2/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons2/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons2/favicon-16x16.png">
<link rel="manifest" href="/static/david/icons2/site.webmanifest">
<link rel="mask-icon" href="/static/david/icons2/safari-pinned-tab.svg" color="#07486c">
<link rel="shortcut icon" href="/static/david/icons2/favicon.ico">
<meta name="msapplication-TileColor" content="#f7f7f7">
<meta name="msapplication-config" content="/static/david/icons2/browserconfig.xml">
<meta name="theme-color" content="#f7f7f7" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#272727" media="(prefers-color-scheme: dark)">
<!-- Is that even respected? Retrospectively? What a shAItshow…
https://neil-clarke.com/block-the-bots-that-feed-ai-models-by-scraping-your-website/ -->
<meta name="robots" content="noai, noimageai">
<!-- Documented, feel free to shoot an email. -->
<link rel="stylesheet" href="/static/david/css/style_2021-01-20.css">
<!-- See https://www.zachleat.com/web/comprehensive-webfonts/ for the trade-off. -->
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
<script>
function toggleTheme(themeName) {
document.documentElement.classList.toggle(
'forced-dark',
themeName === 'dark'
)
document.documentElement.classList.toggle(
'forced-light',
themeName === 'light'
)
}
const selectedTheme = localStorage.getItem('theme')
if (selectedTheme !== 'undefined') {
toggleTheme(selectedTheme)
}
</script>

<meta name="robots" content="noindex, nofollow">
<meta content="origin-when-cross-origin" name="referrer">
<!-- Canonical URL for SEO purposes -->
<link rel="canonical" href="https://tonsky.me/blog/centering/">

<body class="remarkdown h1-underline h2-underline h3-underline em-underscore hr-center ul-star pre-tick" data-instant-intensity="viewport-all">


<article>
<header>
<h1>Hardest Problem in Computer Science: Centering Things</h1>
</header>
<nav>
<p class="center">
<a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
</svg> Accueil</a> •
<a href="https://tonsky.me/blog/centering/" title="Lien vers le contenu original">Source originale</a>
<br>
Mis en cache le 2024-04-18
</p>
</nav>
<hr>
<p>This is my claim: we, as a civilization, forgot how to center things.</p>
<p>I mean, we know <em>how</em> to do it. It has never been simpler:</p>
<pre><code>display: flex;
justify-content: center; /* Horizontal centering */
align-items: center; /* Vertical centering */</code></pre>
<p>(don’t ask why you need to remember four words instead of just horizontal/vertical, <em>it’s still better than before</em>)</p>
<p>Or you can use grids if you want:</p>
<pre><code>display: grid;
justify-items: center; /* Horizontal centering */
align-items: center; /* Vertical centering */</code></pre>
<p>(also don’t ask why <code>justify-content</code> became <code>justify-items</code>)</p>
<p>If you feel like school today, we can deduce it from the first principles:</p>
<figure>
<img src="https://tonsky.me/blog/centering/formula@2x.png?t=1713455735"> </figure>
<p>Hey, even ChatGPT knows how to center things:</p>
<figure>
<img src="https://tonsky.me/blog/centering/chatgpt@2x.png?t=1713455735"> </figure>
<p>Okay, maybe not right away, but eventually it gets there.</p>
<p>What I’m saying is: everybody knows how to center things. It’s trivial. And if you are lost, the knowledge is right there.</p>
<p>Yet, when we look at actual applications, we see that these methods are not used. We see this:</p>
<figure>
<img src="https://tonsky.me/blog/centering/telegram_date@2x.webp?t=1713455735"> </figure>
<p>or this:</p>
<figure>
<img src="https://tonsky.me/blog/centering/google_maps_cross@2x.webp?t=1713455735"> </figure>
<p>or even this:</p>
<figure>
<img src="https://tonsky.me/blog/centering/feedly_beta@2x.webp?t=1713455735"> </figure>
<p>So something is clearly getting lost between know-how and applying that knowledge.</p>
<figure>
<img src="https://tonsky.me/blog/centering/something.png?t=1713455735"> </figure>
<p>In theory, there’s no difference between theory and practice. Unfortunately, we live in practice.</p>
<p>So what’s happening? Let’s find out.</p>
<h1 id="fonts">Fonts</h1>
<p>Fonts are one of the biggest offenders. You can see poorly aligned text everywhere. Let me showcase.</p>
<p>Apple can’t do it:</p>
<figure>
<img src="https://tonsky.me/blog/centering/apple_buttons_big_sur@2x.png?t=1713455735"> </figure>
<p>Microsoft can’t do it:</p>
<figure>
<img src="https://tonsky.me/blog/centering/windows@2x.webp?t=1713455735"> </figure>
<p>GitHub can’t do it:</p>
<figure>
<img src="https://tonsky.me/blog/centering/github@2x.webp?t=1713455735"> </figure>
<p>Valve can’t do it:</p>
<figure>
<img src="https://tonsky.me/blog/centering/steam@2x.webp?t=1713455735"> </figure>
<p>Slack can’t do it:</p>
<figure>
<img src="https://tonsky.me/blog/centering/slack_button@2x.webp?t=1713455735"> </figure>
<p>Telegram can’t do it:</p>
<figure>
<img src="https://tonsky.me/blog/centering/telegram@2x.webp?t=1713455735"> </figure>
<p>Google Maps can’t do it:</p>
<figure>
<img src="https://tonsky.me/blog/centering/google_maps@2x.webp?t=1713455735"> </figure>
<p>Honestly, I can provide an endless supply of poorly-aligned buttons without even having to look for them:</p>
<figure>
<img src="https://tonsky.me/blog/centering/buttons@2x.png?t=1713455735"> </figure>
<p>I think you get the idea. Myriad companies big and small, native or web, and none are safe from text-centering problems.</p>
<h1 id="line-height">Line height</h1>
<p>If font metrics are not enough, the next problem on our way to perfect centering is line-height.</p>
<p>Line height is... complicated. A canonical article to learn about it is Vincent De Oliveira’s <a href="https://iamvdo.me/en/blog/css-font-metrics-line-height-and-vertical-align" target="_blank">Deep dive CSS: font metrics, line-height and vertical-align</a>.</p>
<p>This is how it looks applied in practice. Slack:</p>
<figure>
<img src="https://tonsky.me/blog/centering/slack@2x.webp?t=1713455735"> </figure>
<p>Notion:</p>
<figure>
<img src="https://tonsky.me/blog/centering/notion@2x.webp?t=1713455735"> </figure>
<p>Airbnb:</p>
<figure>
<img src="https://tonsky.me/blog/centering/airbnb@2x.webp?t=1713455735"> </figure>
<p>YouTube:</p>
<figure>
<img src="https://tonsky.me/blog/centering/youtube@2x.webp?t=1713455735"> </figure>
<p>Aligning two things in different containers is almost impossible:</p>
<figure>
<img src="https://tonsky.me/blog/centering/name@2x.webp?t=1713455735"> </figure>
<p>Although many have tried:</p>
<figure>
<img src="https://tonsky.me/blog/centering/american_airlines@2x.webp?t=1713455735"> </figure>
<p>Not many have succeeded:</p>
<figure>
<img src="https://tonsky.me/blog/centering/addons@2x.webp?t=1713455735"> </figure>
<p>CSS might get in the way (different controls having different defaults which you have to undo before even starting trying to align):</p>
<figure>
<img src="https://tonsky.me/blog/centering/controls@2x.webp?t=1713455735"> </figure>
<p>No easy solution here, just roll up your sleeves and delve into specifications.</p>
<h1 id="icons">Icons</h1>
<p>Icons are like small rectangles put in line with text. Therefore all problems caused by text AND line height apply here. Aligning icons next to text is a notoriously hard task.</p>
<p>Atom:</p>
<figure>
<img src="https://tonsky.me/blog/centering/atom@2x.webp?t=1713455735"> </figure>
<p>Platform formerly known as Twitter:</p>
<figure>
<img src="https://tonsky.me/blog/centering/twitter@2x.webp?t=1713455735"> </figure>
<p>iOS:</p>
<figure>
<img src="https://tonsky.me/blog/centering/ios@2x.webp?t=1713455735"> </figure>
<p>Mozilla:</p>
<figure>
<img src="https://tonsky.me/blog/centering/mozilla@2x.webp?t=1713455735"> </figure>
<p>YouTube:</p>
<figure>
<img src="https://tonsky.me/blog/centering/youtube_likes@2x.webp?t=1713455735"> </figure>
<p>Sometimes icon wins over text:</p>
<figure>
<img src="https://tonsky.me/blog/centering/meet@2x.webp?t=1713455735"> </figure>
<p>Sometimes text wins over icon:</p>
<figure>
<img src="https://tonsky.me/blog/centering/ical@2x.webp?t=1713455735"> </figure>
<p>Sometimes both lose:</p>
<figure>
<img src="https://tonsky.me/blog/centering/name_button@2x.webp?t=1713455735"> </figure>
<p>Some icons are just plain old HTML form controls:</p>
<figure>
<img src="https://tonsky.me/blog/centering/git_butler@2x.webp?t=1713455735"> </figure>
<p>Some are stylized:</p>
<figure>
<img src="https://tonsky.me/blog/centering/by_bee@2x.webp?t=1713455735"><figcaption>Thanks @bee for the picture</figcaption> </figure>
<p>Sometimes people will get creative to achieve perfect alignment:</p>
<figure>
<img src="https://tonsky.me/blog/centering/github_close@2x.webp?t=1713455735"> </figure>
<p>But overall it’s a pretty hopeless game:</p>
<figure>
<img src="https://tonsky.me/blog/centering/apple_id@2x.webp?t=1713455735"> </figure>
<p>The problem is, CSS doesn’t help us either. There are 13 possible values for the <code>vertical-align</code> property, but none would align the icon in a meaningful way:</p>
<figure>
<img src="https://tonsky.me/blog/centering/text_align@2x.png?t=1713455735"> </figure>
<p><code>text-align: middle</code> comes closest, but it aligns by x-height, not cap-height, which still looks unbalanced:</p>
<figure>
<img src="https://tonsky.me/blog/centering/middle@2x.webp?t=1713455735"> </figure>
<p>That’s exactly why people love web programming so much. There’s always a challenge.</p>
<h1 id="icon-fonts">Icon fonts</h1>
<p>Aligning rectangles is relatively easy. Aligning text is hard. Icons are rectangles. So what if we put icons into a font file?</p>
<p>Now we can’t align anything:</p>
<figure>
<img src="https://tonsky.me/blog/centering/icon_fonts@2x.webp?t=1713455735"> </figure>
<p>Neither can we set icon size! In the example above, all icons were set to the same font size and line height. As you can see, all of them come out different sizes, with different paddings, and none were properly aligned.</p>
<p>Despite many shortcomings and almost no upsides, companies rushed to add icon fonts everywhere. The result is this:</p>
<figure>
<img src="https://tonsky.me/blog/centering/calculators@2x.png?t=1713455735"><figcaption>macOS 10.14 → macOS 10.15</figcaption> </figure>
<p>Notice how operators are not vertically aligned anymore and are also blurry. All because of switching to icon font.</p>
<p>Apple was so committed to icon fonts they even ruined the QuickTime record button:</p>
<figure>
<img src="https://tonsky.me/blog/centering/quicktime@2x.webp?t=1713455735"> </figure>
<p>Just look at it:</p>
<figure>
<img src="https://tonsky.me/blog/centering/quicktime_button@2x.webp?t=1713455735"> </figure>
<p>Yes, it actually looks like this to this day. As does the calculator.</p>
<p>But they are far from being the only ones. One:</p>
<figure>
<img src="https://tonsky.me/blog/centering/icon_1@2x.webp?t=1713455735"> </figure>
<p>Two:</p>
<figure>
<img src="https://tonsky.me/blog/centering/icon_3@2x.webp?t=1713455735"> </figure>
<p>Three:</p>
<figure>
<img src="https://tonsky.me/blog/centering/icon_4@2x.webp?t=1713455735"> </figure>
<p>Four:</p>
<figure>
<img src="https://tonsky.me/blog/centering/icon_5@2x.webp?t=1713455735"> </figure>
<p>Five:</p>
<figure>
<img src="https://tonsky.me/blog/centering/icon_6@2x.webp?t=1713455735"> </figure>
<p>Six:</p>
<figure>
<img src="https://tonsky.me/blog/centering/icon_7@2x.webp?t=1713455735"> </figure>
<p>Seven:</p>
<figure>
<img src="https://tonsky.me/blog/centering/icon_8@2x.webp?t=1713455735"> </figure>
<p>Same as with text alignment, there’s an endless supply of poorly aligned icons.</p>
<h1 id="skill-issue">Skill issue</h1>
<p>Not only programmers fail to center things. Designers do it, too:</p>
<figure>
<img src="https://tonsky.me/blog/centering/things@2x.webp?t=1713455735"><figcaption><a href="https://culturedcode.com/things/blog/2024/02/things-for-apple-vision-pro/" target="_blank">Current version</a> / my fix</figcaption> </figure>
<p>The problem with icons is that sometimes you have to take their shape into account for things to look good:</p>
<figure>
<img src="https://tonsky.me/blog/centering/apple_logo@2x.webp?t=1713455735"><figcaption>Bad centering / good centering</figcaption> </figure>
<p>Triangle is notably tricky:</p>
<figure>
<img src="https://tonsky.me/blog/centering/triangle@2x.webp?t=1713455735"> </figure>
<p>Sometimes it is too far to the left:</p>
<figure>
<img src="https://tonsky.me/blog/centering/triangle_left@2x.webp?t=1713455735"> </figure>
<p>Sometimes it’s too far to the right:</p>
<figure>
<img src="https://tonsky.me/blog/centering/triangle_right@2x.webp?t=1713455735"> </figure>
<p>It can even be too high up (line-height strikes again):</p>
<figure>
<img src="https://tonsky.me/blog/centering/triangle_up@2x.webp?t=1713455735"> </figure>
<h1 id="horizontal-centering">Horizontal centering</h1>
<p>You might think that only centering things vertically is hard. Not only! Horizontal might be hard, too:</p>
<figure>
<img src="https://tonsky.me/blog/centering/apple_sign_in_business@2x.webp?t=1713455735"> </figure>
<p>I don’t think there’s a deep reason for these, except for people just being sloppy:</p>
<figure>
<img src="https://tonsky.me/blog/centering/twitter_horizontal@2x.webp?t=1713455735"> </figure>
<p>Just, come on!</p>
<figure>
<img src="https://tonsky.me/blog/centering/android@2x.webp?t=1713455735"> </figure>
<p>Can this be a deliberate decision?</p>
<figure>
<img src="https://tonsky.me/blog/centering/teams@2x.png?t=1713455735"> </figure>
<p>I don’t know. Icons can suffer from it, too:</p>
<figure>
<img src="https://tonsky.me/blog/centering/drive@2x.webp?t=1713455735"> </figure>
<p>As can text:</p>
<figure>
<img src="https://tonsky.me/blog/centering/steam_horizontal@2x.webp?t=1713455735"> </figure>
<h1 id="what-can-be-done-designers">What can be done: designers</h1>
<p>So what <em>is</em> the problem?</p>
<p>It all starts with the font. Right now, the bounding box of a text block looks like this:</p>
<figure>
<img src="https://tonsky.me/blog/centering/text_bounding_box@2x.png?t=1713455735"> </figure>
<p>The problem is, it can also look like this:</p>
<figure>
<img src="https://tonsky.me/blog/centering/text_bounding_box_2@2x.png?t=1713455735"> </figure>
<p>or this:</p>
<figure>
<img src="https://tonsky.me/blog/centering/text_bounding_box_3@2x.png?t=1713455735"> </figure>
<p>Now, what will happen if you try to center text by centering its bounding box?</p>
<figure>
<img src="https://tonsky.me/blog/centering/text_bounding_box_4.png?t=1713455735"> </figure>
<p>The text will be off! Even though rectangles are perfectly centered.</p>
<p>But even if font <em>can</em> have its metrics unbalanced, it doesn’t mean it does. What happens in reality?</p>
<p>In reality, <em>most</em> of the popular fonts have metrics slightly off. Many have it <em>significantly</em> off:</p>
<figure>
<img src="https://tonsky.me/blog/centering/metrics@2x.png?t=1713455735"><figcaption>Percentages are of cap-height</figcaption> </figure>
<p>10% is not a small number. It’s a whole pixel in font size 13! Two, if you have 2× scaling! It’s easily noticeable.</p>
<p>Basically, Segoe UI is the reason why Github on Windows looks like this:</p>
<figure>
<img src="https://tonsky.me/blog/centering/github@2x.webp?t=1713455735"> </figure>
<p>The solution is simple: make tight bounding boxes and centering will become trivial:</p>
<figure>
<img src="https://tonsky.me/blog/centering/text_bounding_box_5.png?t=1713455735"> </figure>
<p>If you use Figma, it already can do this (although it’s not the default):</p>
<figure>
<img src="https://tonsky.me/blog/centering/figma_vertical_trim@2x.png?t=1713455735"> </figure>
<h1 id="what-can-be-done-font-designers">What can be done: font designers</h1>
<p>If you are a font designer, make life easier for everybody by setting your metrics so that <code>ascender − cap-height = descender</code>:</p>
<figure>
<img src="https://tonsky.me/blog/centering/font_metrics_numbers@2x.png?t=1713455735"> </figure>
<p>Or the same idea, visually:</p>
<figure>
<img src="https://tonsky.me/blog/centering/font_metrics@2x.png?t=1713455735"> </figure>
<p>Important! You don’t have to <em>actually</em> extend your ascenders/descenders to these boundaries. As you can see in the picture, my ascender space, for example, is way underutilized. Just make the numbers match.</p>
<p>For both web and native, to avoid headaches, choose a font that already follows this rule. SF Pro Text, Inter, and Martian Mono seem to do this already, so they will center perfectly with no extra effort.</p>
<p>See <a href="https://tonsky.me/blog/font-size/">Font size is useless; let’s fix it</a> for more information.</p>
<h1 id="what-can-be-done-web-developers">What can be done: web developers</h1>
<p>From the developer side, it’s a bit more tricky.</p>
<p>The first thing to understand, you need to know which font you’ll be using. Unfortunately, this doesn’t work if you plan to substitute fonts.</p>
<p>We’ll use IBM Plex Sans, a font used on this very page. IBM Plex Sans has the following metrics:</p>
<figure>
<img src="https://tonsky.me/blog/centering/ibm_plex_sans@2x.png?t=1713455735"> </figure>
<p>When you set <code>font-size</code>, what you set is UPM (this will also be equal to <code>1em</code>). However, the actual space occupied by the text block is the space between the ascender and descender.</p>
<figure>
<img src="https://tonsky.me/blog/centering/ibm_plex_sans_notes@2x.png?t=1713455735"> </figure>
<p>With a few simple calculations, we get that extra <code>padding-bottom: 0.052em</code> should do the trick:</p>
<figure>
<img src="https://tonsky.me/blog/centering/numi@2x.webp?t=1713455735"> </figure>
<p>Should work like this:</p>
<figure>
<img src="https://tonsky.me/blog/centering/ibm_plex_sans_padding@2x.png?t=1713455735"> </figure>
<p>Or in actual CSS (select text to see default text bounding box):</p>
<p>
<span>Andy</span>
</p>
<pre><code>
</code></pre>
<p>You can get the required font metrics for your font from <a href="https://opentype.js.org/font-inspector.html" target="_blank">opentype.js.org/font-inspector.html</a> (ascender, descender, sCapHeight).</p>
<p>Now that we have that sorted, aligning icons is not that hard too. You set <code>vertical-align: baseline</code> and then move them down by <code>(iconHeight - capHeight) / 2</code>:</p>
<figure>
<img src="https://tonsky.me/blog/centering/ibm_plex_sans_icon@2x.png?t=1713455735"> </figure>
<p>This, unfortunately, requires you to know both font metrics and icon size. But hey, at least it works:</p>
<p>
<span>
<span></span>
Andy
</span>
</p>
<pre><code>
</code></pre>
<p>Again, select the text above to see how different the browser’s bounding box is from the correct position.</p>
<h1 id="what-can-be-done-icons-fonts">What can be done: icons fonts</h1>
<p>STOP.</p>
<p>USING.</p>
<p>FONTS.</p>
<p>FOR.</p>
<p>ICONS.</p>
<p>Use normal image format. The one with dimensions, you know? Width and height?</p>
<p>Here, I drew a diagram for you, to help you make a decision:</p>
<figure>
<img src="https://tonsky.me/blog/centering/diagram@2x.png?t=1713455735"> </figure>
<p>Just look at how hard Apple tries to put the checkmark inside the rectangle, and the rectangle next to the text label:</p>
<figure>
<img src="https://tonsky.me/blog/centering/apple_sign_in@2x.webp?t=1713455735"> </figure>
<p>And they still fail!</p>
<p>Nothing is easier than aligning two rectangles. Nothing is harder than trying to align text that has an arbitrary amount of empty space around it.</p>
<p>This is a game that can’t be won.</p>
<h1 id="what-can-be-done-optical-compensations">What can be done: optical compensations</h1>
<p>We, developers, can only mathematically align perfect rectangles. So for anything that requires manual compensation, please wrap it in a big enough rectangle and visually balance your icon inside:</p>
<figure>
<img src="https://tonsky.me/blog/centering/icons_baked@2x.png?t=1713455735"> </figure>
<h1 id="what-can-be-done-everyone">What can be done: everyone</h1>
<p>Please pay attention. Please care. Bad centering can ruin otherwise decent UI:</p>
<figure>
<img src="https://tonsky.me/blog/centering/win@2x.webp?t=1713455735"> </figure>
<p>But a properly aligned text can make your UI sing:</p>
<figure>
<img src="https://tonsky.me/blog/centering/win_fix@2x.webp?t=1713455735"> </figure>
<p>Even if it’s hard. Even if tools make it inconvenient. Even if you have to search for solutions. Together, I trust, we can find our way back to putting one rectangle inside another rectangle without messing it up.</p>
<p>I, for one, want to live in a world of beautiful well-balanced UIs. I trust that you do, too. </p>
<p>It’s all worth it in the end.</p>
<h1 id="honorable-mention">Honorable mention</h1>
<p>Our article would be incomplete without this guy:</p>
<figure>
<video autoplay="" muted="" loop="" preload="auto" playsinline="" controls="">
<source src="https://tonsky.me/blog/centering/spinner.mp4?t=1713455735" type="video/mp4">
</source></video>
</figure>
<p>Take care!</p>
</article>


<hr>

<footer>
<p>
<a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
</svg> Accueil</a> •
<a href="/david/log/" title="Accès au flux RSS"><svg class="icon icon-rss2">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-rss2"></use>
</svg> Suivre</a> •
<a href="http://larlet.com" title="Go to my English profile" data-instant><svg class="icon icon-user-tie">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-user-tie"></use>
</svg> Pro</a> •
<a href="mailto:david%40larlet.fr" title="Envoyer un courriel"><svg class="icon icon-mail">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-mail"></use>
</svg> Email</a> •
<abbr class="nowrap" title="Hébergeur : Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33184162340"><svg class="icon icon-hammer2">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-hammer2"></use>
</svg> Légal</abbr>
</p>
<template id="theme-selector">
<form>
<fieldset>
<legend><svg class="icon icon-brightness-contrast">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-brightness-contrast"></use>
</svg> Thème</legend>
<label>
<input type="radio" value="auto" name="chosen-color-scheme" checked> Auto
</label>
<label>
<input type="radio" value="dark" name="chosen-color-scheme"> Foncé
</label>
<label>
<input type="radio" value="light" name="chosen-color-scheme"> Clair
</label>
</fieldset>
</form>
</template>
</footer>
<script src="/static/david/js/instantpage-5.1.0.min.js" type="module"></script>
<script>
function loadThemeForm(templateName) {
const themeSelectorTemplate = document.querySelector(templateName)
const form = themeSelectorTemplate.content.firstElementChild
themeSelectorTemplate.replaceWith(form)

form.addEventListener('change', (e) => {
const chosenColorScheme = e.target.value
localStorage.setItem('theme', chosenColorScheme)
toggleTheme(chosenColorScheme)
})

const selectedTheme = localStorage.getItem('theme')
if (selectedTheme && selectedTheme !== 'undefined') {
form.querySelector(`[value="${selectedTheme}"]`).checked = true
}
}

const prefersColorSchemeDark = '(prefers-color-scheme: dark)'
window.addEventListener('load', () => {
let hasDarkRules = false
for (const styleSheet of Array.from(document.styleSheets)) {
let mediaRules = []
for (const cssRule of styleSheet.cssRules) {
if (cssRule.type !== CSSRule.MEDIA_RULE) {
continue
}
// WARNING: Safari does not have/supports `conditionText`.
if (cssRule.conditionText) {
if (cssRule.conditionText !== prefersColorSchemeDark) {
continue
}
} else {
if (cssRule.cssText.startsWith(prefersColorSchemeDark)) {
continue
}
}
mediaRules = mediaRules.concat(Array.from(cssRule.cssRules))
}

// WARNING: do not try to insert a Rule to a styleSheet you are
// currently iterating on, otherwise the browser will be stuck
// in a infinite loop…
for (const mediaRule of mediaRules) {
styleSheet.insertRule(mediaRule.cssText)
hasDarkRules = true
}
}
if (hasDarkRules) {
loadThemeForm('#theme-selector')
}
})
</script>
</body>
</html>

+ 325
- 0
cache/2024/4bda7c6500950716846bdeb6bddbafed/index.md View File

@@ -0,0 +1,325 @@
title: Hardest Problem in Computer Science: Centering Things
url: https://tonsky.me/blog/centering/
hash_url: 4bda7c6500950716846bdeb6bddbafed
archive_date: 2024-04-18
og_image: https://dynogee.com/gen?id=24m2qx9uethuw6p&title=Hardest+Problem+in+Computer+Science%3A+Centering+Things
description: Somehow we forgot how to center rectangles and must find our way back
favicon: https://tonsky.me/i/favicon.png
language: en_US

<p>This is my claim: we, as a civilization, forgot how to center things.</p>
<p>I mean, we know <em>how</em> to do it. It has never been simpler:</p>
<pre><code>display: flex;
justify-content: center; /* Horizontal centering */
align-items: center; /* Vertical centering */</code></pre>
<p>(don’t ask why you need to remember four words instead of just horizontal/vertical, <em>it’s still better than before</em>)</p>
<p>Or you can use grids if you want:</p>
<pre><code>display: grid;
justify-items: center; /* Horizontal centering */
align-items: center; /* Vertical centering */</code></pre>
<p>(also don’t ask why <code>justify-content</code> became <code>justify-items</code>)</p>
<p>If you feel like school today, we can deduce it from the first principles:</p>
<figure>
<img src="https://tonsky.me/blog/centering/formula@2x.png?t=1713455735"> </figure>
<p>Hey, even ChatGPT knows how to center things:</p>
<figure>
<img src="https://tonsky.me/blog/centering/chatgpt@2x.png?t=1713455735"> </figure>
<p>Okay, maybe not right away, but eventually it gets there.</p>
<p>What I’m saying is: everybody knows how to center things. It’s trivial. And if you are lost, the knowledge is right there.</p>
<p>Yet, when we look at actual applications, we see that these methods are not used. We see this:</p>
<figure>
<img src="https://tonsky.me/blog/centering/telegram_date@2x.webp?t=1713455735"> </figure>
<p>or this:</p>
<figure>
<img src="https://tonsky.me/blog/centering/google_maps_cross@2x.webp?t=1713455735"> </figure>
<p>or even this:</p>
<figure>
<img src="https://tonsky.me/blog/centering/feedly_beta@2x.webp?t=1713455735"> </figure>
<p>So something is clearly getting lost between know-how and applying that knowledge.</p>
<figure>
<img src="https://tonsky.me/blog/centering/something.png?t=1713455735"> </figure>
<p>In theory, there’s no difference between theory and practice. Unfortunately, we live in practice.</p>
<p>So what’s happening? Let’s find out.</p>
<h1 id="fonts">Fonts</h1>
<p>Fonts are one of the biggest offenders. You can see poorly aligned text everywhere. Let me showcase.</p>
<p>Apple can’t do it:</p>
<figure>
<img src="https://tonsky.me/blog/centering/apple_buttons_big_sur@2x.png?t=1713455735"> </figure>
<p>Microsoft can’t do it:</p>
<figure>
<img src="https://tonsky.me/blog/centering/windows@2x.webp?t=1713455735"> </figure>
<p>GitHub can’t do it:</p>
<figure>
<img src="https://tonsky.me/blog/centering/github@2x.webp?t=1713455735"> </figure>
<p>Valve can’t do it:</p>
<figure>
<img src="https://tonsky.me/blog/centering/steam@2x.webp?t=1713455735"> </figure>
<p>Slack can’t do it:</p>
<figure>
<img src="https://tonsky.me/blog/centering/slack_button@2x.webp?t=1713455735"> </figure>
<p>Telegram can’t do it:</p>
<figure>
<img src="https://tonsky.me/blog/centering/telegram@2x.webp?t=1713455735"> </figure>
<p>Google Maps can’t do it:</p>
<figure>
<img src="https://tonsky.me/blog/centering/google_maps@2x.webp?t=1713455735"> </figure>
<p>Honestly, I can provide an endless supply of poorly-aligned buttons without even having to look for them:</p>
<figure>
<img src="https://tonsky.me/blog/centering/buttons@2x.png?t=1713455735"> </figure>
<p>I think you get the idea. Myriad companies big and small, native or web, and none are safe from text-centering problems.</p>
<h1 id="line-height">Line height</h1>
<p>If font metrics are not enough, the next problem on our way to perfect centering is line-height.</p>
<p>Line height is... complicated. A canonical article to learn about it is Vincent De Oliveira’s <a href="https://iamvdo.me/en/blog/css-font-metrics-line-height-and-vertical-align" target="_blank">Deep dive CSS: font metrics, line-height and vertical-align</a>.</p>
<p>This is how it looks applied in practice. Slack:</p>
<figure>
<img src="https://tonsky.me/blog/centering/slack@2x.webp?t=1713455735"> </figure>
<p>Notion:</p>
<figure>
<img src="https://tonsky.me/blog/centering/notion@2x.webp?t=1713455735"> </figure>
<p>Airbnb:</p>
<figure>
<img src="https://tonsky.me/blog/centering/airbnb@2x.webp?t=1713455735"> </figure>
<p>YouTube:</p>
<figure>
<img src="https://tonsky.me/blog/centering/youtube@2x.webp?t=1713455735"> </figure>
<p>Aligning two things in different containers is almost impossible:</p>
<figure>
<img src="https://tonsky.me/blog/centering/name@2x.webp?t=1713455735"> </figure>
<p>Although many have tried:</p>
<figure>
<img src="https://tonsky.me/blog/centering/american_airlines@2x.webp?t=1713455735"> </figure>
<p>Not many have succeeded:</p>
<figure>
<img src="https://tonsky.me/blog/centering/addons@2x.webp?t=1713455735"> </figure>
<p>CSS might get in the way (different controls having different defaults which you have to undo before even starting trying to align):</p>
<figure>
<img src="https://tonsky.me/blog/centering/controls@2x.webp?t=1713455735"> </figure>
<p>No easy solution here, just roll up your sleeves and delve into specifications.</p>
<h1 id="icons">Icons</h1>
<p>Icons are like small rectangles put in line with text. Therefore all problems caused by text AND line height apply here. Aligning icons next to text is a notoriously hard task.</p>
<p>Atom:</p>
<figure>
<img src="https://tonsky.me/blog/centering/atom@2x.webp?t=1713455735"> </figure>
<p>Platform formerly known as Twitter:</p>
<figure>
<img src="https://tonsky.me/blog/centering/twitter@2x.webp?t=1713455735"> </figure>
<p>iOS:</p>
<figure>
<img src="https://tonsky.me/blog/centering/ios@2x.webp?t=1713455735"> </figure>
<p>Mozilla:</p>
<figure>
<img src="https://tonsky.me/blog/centering/mozilla@2x.webp?t=1713455735"> </figure>
<p>YouTube:</p>
<figure>
<img src="https://tonsky.me/blog/centering/youtube_likes@2x.webp?t=1713455735"> </figure>
<p>Sometimes icon wins over text:</p>
<figure>
<img src="https://tonsky.me/blog/centering/meet@2x.webp?t=1713455735"> </figure>
<p>Sometimes text wins over icon:</p>
<figure>
<img src="https://tonsky.me/blog/centering/ical@2x.webp?t=1713455735"> </figure>
<p>Sometimes both lose:</p>
<figure>
<img src="https://tonsky.me/blog/centering/name_button@2x.webp?t=1713455735"> </figure>
<p>Some icons are just plain old HTML form controls:</p>
<figure>
<img src="https://tonsky.me/blog/centering/git_butler@2x.webp?t=1713455735"> </figure>
<p>Some are stylized:</p>
<figure>
<img src="https://tonsky.me/blog/centering/by_bee@2x.webp?t=1713455735"><figcaption>Thanks @bee for the picture</figcaption> </figure>
<p>Sometimes people will get creative to achieve perfect alignment:</p>
<figure>
<img src="https://tonsky.me/blog/centering/github_close@2x.webp?t=1713455735"> </figure>
<p>But overall it’s a pretty hopeless game:</p>
<figure>
<img src="https://tonsky.me/blog/centering/apple_id@2x.webp?t=1713455735"> </figure>
<p>The problem is, CSS doesn’t help us either. There are 13 possible values for the <code>vertical-align</code> property, but none would align the icon in a meaningful way:</p>
<figure>
<img src="https://tonsky.me/blog/centering/text_align@2x.png?t=1713455735"> </figure>
<p><code>text-align: middle</code> comes closest, but it aligns by x-height, not cap-height, which still looks unbalanced:</p>
<figure>
<img src="https://tonsky.me/blog/centering/middle@2x.webp?t=1713455735"> </figure>
<p>That’s exactly why people love web programming so much. There’s always a challenge.</p>
<h1 id="icon-fonts">Icon fonts</h1>
<p>Aligning rectangles is relatively easy. Aligning text is hard. Icons are rectangles. So what if we put icons into a font file?</p>
<p>Now we can’t align anything:</p>
<figure>
<img src="https://tonsky.me/blog/centering/icon_fonts@2x.webp?t=1713455735"> </figure>
<p>Neither can we set icon size! In the example above, all icons were set to the same font size and line height. As you can see, all of them come out different sizes, with different paddings, and none were properly aligned.</p>
<p>Despite many shortcomings and almost no upsides, companies rushed to add icon fonts everywhere. The result is this:</p>
<figure>
<img src="https://tonsky.me/blog/centering/calculators@2x.png?t=1713455735"><figcaption>macOS 10.14 → macOS 10.15</figcaption> </figure>
<p>Notice how operators are not vertically aligned anymore and are also blurry. All because of switching to icon font.</p>
<p>Apple was so committed to icon fonts they even ruined the QuickTime record button:</p>
<figure>
<img src="https://tonsky.me/blog/centering/quicktime@2x.webp?t=1713455735"> </figure>
<p>Just look at it:</p>
<figure>
<img src="https://tonsky.me/blog/centering/quicktime_button@2x.webp?t=1713455735"> </figure>
<p>Yes, it actually looks like this to this day. As does the calculator.</p>
<p>But they are far from being the only ones. One:</p>
<figure>
<img src="https://tonsky.me/blog/centering/icon_1@2x.webp?t=1713455735"> </figure>
<p>Two:</p>
<figure>
<img src="https://tonsky.me/blog/centering/icon_3@2x.webp?t=1713455735"> </figure>
<p>Three:</p>
<figure>
<img src="https://tonsky.me/blog/centering/icon_4@2x.webp?t=1713455735"> </figure>
<p>Four:</p>
<figure>
<img src="https://tonsky.me/blog/centering/icon_5@2x.webp?t=1713455735"> </figure>
<p>Five:</p>
<figure>
<img src="https://tonsky.me/blog/centering/icon_6@2x.webp?t=1713455735"> </figure>
<p>Six:</p>
<figure>
<img src="https://tonsky.me/blog/centering/icon_7@2x.webp?t=1713455735"> </figure>
<p>Seven:</p>
<figure>
<img src="https://tonsky.me/blog/centering/icon_8@2x.webp?t=1713455735"> </figure>
<p>Same as with text alignment, there’s an endless supply of poorly aligned icons.</p>
<h1 id="skill-issue">Skill issue</h1>
<p>Not only programmers fail to center things. Designers do it, too:</p>
<figure>
<img src="https://tonsky.me/blog/centering/things@2x.webp?t=1713455735"><figcaption><a href="https://culturedcode.com/things/blog/2024/02/things-for-apple-vision-pro/" target="_blank">Current version</a> / my fix</figcaption> </figure>
<p>The problem with icons is that sometimes you have to take their shape into account for things to look good:</p>
<figure>
<img src="https://tonsky.me/blog/centering/apple_logo@2x.webp?t=1713455735"><figcaption>Bad centering / good centering</figcaption> </figure>
<p>Triangle is notably tricky:</p>
<figure>
<img src="https://tonsky.me/blog/centering/triangle@2x.webp?t=1713455735"> </figure>
<p>Sometimes it is too far to the left:</p>
<figure>
<img src="https://tonsky.me/blog/centering/triangle_left@2x.webp?t=1713455735"> </figure>
<p>Sometimes it’s too far to the right:</p>
<figure>
<img src="https://tonsky.me/blog/centering/triangle_right@2x.webp?t=1713455735"> </figure>
<p>It can even be too high up (line-height strikes again):</p>
<figure>
<img src="https://tonsky.me/blog/centering/triangle_up@2x.webp?t=1713455735"> </figure>
<h1 id="horizontal-centering">Horizontal centering</h1>
<p>You might think that only centering things vertically is hard. Not only! Horizontal might be hard, too:</p>
<figure>
<img src="https://tonsky.me/blog/centering/apple_sign_in_business@2x.webp?t=1713455735"> </figure>
<p>I don’t think there’s a deep reason for these, except for people just being sloppy:</p>
<figure>
<img src="https://tonsky.me/blog/centering/twitter_horizontal@2x.webp?t=1713455735"> </figure>
<p>Just, come on!</p>
<figure>
<img src="https://tonsky.me/blog/centering/android@2x.webp?t=1713455735"> </figure>
<p>Can this be a deliberate decision?</p>
<figure>
<img src="https://tonsky.me/blog/centering/teams@2x.png?t=1713455735"> </figure>
<p>I don’t know. Icons can suffer from it, too:</p>
<figure>
<img src="https://tonsky.me/blog/centering/drive@2x.webp?t=1713455735"> </figure>
<p>As can text:</p>
<figure>
<img src="https://tonsky.me/blog/centering/steam_horizontal@2x.webp?t=1713455735"> </figure>
<h1 id="what-can-be-done-designers">What can be done: designers</h1>
<p>So what <em>is</em> the problem?</p>
<p>It all starts with the font. Right now, the bounding box of a text block looks like this:</p>
<figure>
<img src="https://tonsky.me/blog/centering/text_bounding_box@2x.png?t=1713455735"> </figure>
<p>The problem is, it can also look like this:</p>
<figure>
<img src="https://tonsky.me/blog/centering/text_bounding_box_2@2x.png?t=1713455735"> </figure>
<p>or this:</p>
<figure>
<img src="https://tonsky.me/blog/centering/text_bounding_box_3@2x.png?t=1713455735"> </figure>
<p>Now, what will happen if you try to center text by centering its bounding box?</p>
<figure>
<img src="https://tonsky.me/blog/centering/text_bounding_box_4.png?t=1713455735"> </figure>
<p>The text will be off! Even though rectangles are perfectly centered.</p>
<p>But even if font <em>can</em> have its metrics unbalanced, it doesn’t mean it does. What happens in reality?</p>
<p>In reality, <em>most</em> of the popular fonts have metrics slightly off. Many have it <em>significantly</em> off:</p>
<figure>
<img src="https://tonsky.me/blog/centering/metrics@2x.png?t=1713455735"><figcaption>Percentages are of cap-height</figcaption> </figure>
<p>10% is not a small number. It’s a whole pixel in font size 13! Two, if you have 2× scaling! It’s easily noticeable.</p>
<p>Basically, Segoe UI is the reason why Github on Windows looks like this:</p>
<figure>
<img src="https://tonsky.me/blog/centering/github@2x.webp?t=1713455735"> </figure>
<p>The solution is simple: make tight bounding boxes and centering will become trivial:</p>
<figure>
<img src="https://tonsky.me/blog/centering/text_bounding_box_5.png?t=1713455735"> </figure>
<p>If you use Figma, it already can do this (although it’s not the default):</p>
<figure>
<img src="https://tonsky.me/blog/centering/figma_vertical_trim@2x.png?t=1713455735"> </figure>
<h1 id="what-can-be-done-font-designers">What can be done: font designers</h1>
<p>If you are a font designer, make life easier for everybody by setting your metrics so that <code>ascender − cap-height = descender</code>:</p>
<figure>
<img src="https://tonsky.me/blog/centering/font_metrics_numbers@2x.png?t=1713455735"> </figure>
<p>Or the same idea, visually:</p>
<figure>
<img src="https://tonsky.me/blog/centering/font_metrics@2x.png?t=1713455735"> </figure>
<p>Important! You don’t have to <em>actually</em> extend your ascenders/descenders to these boundaries. As you can see in the picture, my ascender space, for example, is way underutilized. Just make the numbers match.</p>
<p>For both web and native, to avoid headaches, choose a font that already follows this rule. SF Pro Text, Inter, and Martian Mono seem to do this already, so they will center perfectly with no extra effort.</p>
<p>See <a href="https://tonsky.me/blog/font-size/">Font size is useless; let’s fix it</a> for more information.</p>
<h1 id="what-can-be-done-web-developers">What can be done: web developers</h1>
<p>From the developer side, it’s a bit more tricky.</p>
<p>The first thing to understand, you need to know which font you’ll be using. Unfortunately, this doesn’t work if you plan to substitute fonts.</p>
<p>We’ll use IBM Plex Sans, a font used on this very page. IBM Plex Sans has the following metrics:</p>
<figure>
<img src="https://tonsky.me/blog/centering/ibm_plex_sans@2x.png?t=1713455735"> </figure>
<p>When you set <code>font-size</code>, what you set is UPM (this will also be equal to <code>1em</code>). However, the actual space occupied by the text block is the space between the ascender and descender.</p>
<figure>
<img src="https://tonsky.me/blog/centering/ibm_plex_sans_notes@2x.png?t=1713455735"> </figure>
<p>With a few simple calculations, we get that extra <code>padding-bottom: 0.052em</code> should do the trick:</p>
<figure>
<img src="https://tonsky.me/blog/centering/numi@2x.webp?t=1713455735"> </figure>
<p>Should work like this:</p>
<figure>
<img src="https://tonsky.me/blog/centering/ibm_plex_sans_padding@2x.png?t=1713455735"> </figure>
<p>Or in actual CSS (select text to see default text bounding box):</p>
<p>
<span>Andy</span>
</p> <p>You can get the required font metrics for your font from <a href="https://opentype.js.org/font-inspector.html" target="_blank">opentype.js.org/font-inspector.html</a> (ascender, descender, sCapHeight).</p>
<p>Now that we have that sorted, aligning icons is not that hard too. You set <code>vertical-align: baseline</code> and then move them down by <code>(iconHeight - capHeight) / 2</code>:</p>
<figure>
<img src="https://tonsky.me/blog/centering/ibm_plex_sans_icon@2x.png?t=1713455735"> </figure>
<p>This, unfortunately, requires you to know both font metrics and icon size. But hey, at least it works:</p>
<p>
<span>
<span></span>
Andy
</span>
</p> <p>Again, select the text above to see how different the browser’s bounding box is from the correct position.</p>
<h1 id="what-can-be-done-icons-fonts">What can be done: icons fonts</h1>
<p>STOP.</p>
<p>USING.</p>
<p>FONTS.</p>
<p>FOR.</p>
<p>ICONS.</p>
<p>Use normal image format. The one with dimensions, you know? Width and height?</p>
<p>Here, I drew a diagram for you, to help you make a decision:</p>
<figure>
<img src="https://tonsky.me/blog/centering/diagram@2x.png?t=1713455735"> </figure>
<p>Just look at how hard Apple tries to put the checkmark inside the rectangle, and the rectangle next to the text label:</p>
<figure>
<img src="https://tonsky.me/blog/centering/apple_sign_in@2x.webp?t=1713455735"> </figure>
<p>And they still fail!</p>
<p>Nothing is easier than aligning two rectangles. Nothing is harder than trying to align text that has an arbitrary amount of empty space around it.</p>
<p>This is a game that can’t be won.</p>
<h1 id="what-can-be-done-optical-compensations">What can be done: optical compensations</h1>
<p>We, developers, can only mathematically align perfect rectangles. So for anything that requires manual compensation, please wrap it in a big enough rectangle and visually balance your icon inside:</p>
<figure>
<img src="https://tonsky.me/blog/centering/icons_baked@2x.png?t=1713455735"> </figure>
<h1 id="what-can-be-done-everyone">What can be done: everyone</h1>
<p>Please pay attention. Please care. Bad centering can ruin otherwise decent UI:</p>
<figure>
<img src="https://tonsky.me/blog/centering/win@2x.webp?t=1713455735"> </figure>
<p>But a properly aligned text can make your UI sing:</p>
<figure>
<img src="https://tonsky.me/blog/centering/win_fix@2x.webp?t=1713455735"> </figure>
<p>Even if it’s hard. Even if tools make it inconvenient. Even if you have to search for solutions. Together, I trust, we can find our way back to putting one rectangle inside another rectangle without messing it up.</p>
<p>I, for one, want to live in a world of beautiful well-balanced UIs. I trust that you do, too. </p>
<p>It’s all worth it in the end.</p>
<h1 id="honorable-mention">Honorable mention</h1>
<p>Our article would be incomplete without this guy:</p>
<figure>
<video autoplay="" muted="" loop="" preload="auto" playsinline="" controls="">
<source src="https://tonsky.me/blog/centering/spinner.mp4?t=1713455735" type="video/mp4">
</source></video>
</figure>
<p>Take care!</p>

+ 209
- 0
cache/2024/944899e3cafa6019bd2f285284a14dd7/index.html View File

@@ -0,0 +1,209 @@
<!doctype html><!-- This is a valid HTML5 document. -->
<!-- Screen readers, SEO, extensions and so on. -->
<html lang="en">
<!-- Has to be within the first 1024 bytes, hence before the `title` element
See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
<meta charset="utf-8">
<!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
<!-- The viewport meta is quite crowded and we are responsible for that.
See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
<meta name="viewport" content="width=device-width,initial-scale=1">
<!-- Required to make a valid HTML5 document. -->
<title>We need more calm companies (archive) — David Larlet</title>
<meta name="description" content="Publication mise en cache pour en conserver une trace.">
<!-- That good ol' feed, subscribe :). -->
<link rel="alternate" type="application/atom+xml" title="Feed" href="/david/log/">
<!-- Generated from https://realfavicongenerator.net/ such a mess. -->
<link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons2/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons2/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons2/favicon-16x16.png">
<link rel="manifest" href="/static/david/icons2/site.webmanifest">
<link rel="mask-icon" href="/static/david/icons2/safari-pinned-tab.svg" color="#07486c">
<link rel="shortcut icon" href="/static/david/icons2/favicon.ico">
<meta name="msapplication-TileColor" content="#f7f7f7">
<meta name="msapplication-config" content="/static/david/icons2/browserconfig.xml">
<meta name="theme-color" content="#f7f7f7" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#272727" media="(prefers-color-scheme: dark)">
<!-- Is that even respected? Retrospectively? What a shAItshow…
https://neil-clarke.com/block-the-bots-that-feed-ai-models-by-scraping-your-website/ -->
<meta name="robots" content="noai, noimageai">
<!-- Documented, feel free to shoot an email. -->
<link rel="stylesheet" href="/static/david/css/style_2021-01-20.css">
<!-- See https://www.zachleat.com/web/comprehensive-webfonts/ for the trade-off. -->
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
<script>
function toggleTheme(themeName) {
document.documentElement.classList.toggle(
'forced-dark',
themeName === 'dark'
)
document.documentElement.classList.toggle(
'forced-light',
themeName === 'light'
)
}
const selectedTheme = localStorage.getItem('theme')
if (selectedTheme !== 'undefined') {
toggleTheme(selectedTheme)
}
</script>

<meta name="robots" content="noindex, nofollow">
<meta content="origin-when-cross-origin" name="referrer">
<!-- Canonical URL for SEO purposes -->
<link rel="canonical" href="https://justinjackson.ca/calm-company">

<body class="remarkdown h1-underline h2-underline h3-underline em-underscore hr-center ul-star pre-tick" data-instant-intensity="viewport-all">


<article>
<header>
<h1>We need more calm companies</h1>
</header>
<nav>
<p class="center">
<a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
</svg> Accueil</a> •
<a href="https://justinjackson.ca/calm-company" title="Lien vers le contenu original">Source originale</a>
<br>
Mis en cache le 2024-04-18
</p>
</nav>
<hr>
<p>It's been hard to watch the recent layoffs in the tech industry. Seeing so many good people being let go has been heartbreaking.</p>
<p>Getting laid off doesn’t just rattle your career; it shakes the very foundation of your life. I've seen friends lose their jobs and go into an existential tailspin. It's not uncommon for them to blame themselves and to feel immense guilt and shame. The experience is especially hard on parents whose families depend on them for income.</p>
<h2>An inevitable outcome of a flawed system</h2>
<p>Layoffs are inevitable during a company's lifespan. But seeing startups cut 20-30% of their workforce feels especially cruel in light of the aggressive hiring they did during the pandemic.</p>
<p>The recent tech layoff spree was a result of following the wrong incentives. Investors had flooded the market with cash and told companies to hire aggressively. Growth took precedence over profitability. Then, capital dried up, and companies started thrashing. Their response was to fire large groups of people in a hasty and unthought-out manner.</p>
<p>This is a sad reality of corporations optimizing for investor returns: the people who work on and buy the product suffer. Good employees are fired, and useful products are shut down.</p>
<p>This chaos has reaffirmed my belief that we need more <strong>calm companies.</strong></p>
<h2>Calm Company definition</h2>
<p>On the <a href="https://lnk.to/73fDM7">​Pathless Path podcast</a>, Paul Millard asked me to define "calm company:"</p>

<p>A calm company's purpose is to provide exceptional service to customers while simultaneously improving the lives of the people who work there.</p>
<p>By default, a calm company is profitable. Those profits give a calm company its resilience: there's no last-minute scramble to meet payroll or earn a last-minute sale to keep the business afloat. The company has enough financial margin to weather economic storms.</p>
<p>Moreover, calm companies are fun to work for. The work is usually interesting and enjoyable. The team has been carefully selected, and there's a good vibe in meetings. </p>
<p>Calm companies provide meaningful work, healthy interactions, and flexibility for people's lives. If your kid is home sick, you can set work aside and take care of them. If it's a beautiful day, you can go for a run on the beach. </p>
<h3>Here is my list of Calm Company attributes:</h3>
<ol><li><p><strong>Profitable</strong>: Calm Companies have a strong financial engine working with <a href="https://justinjackson.ca/margin">good margins</a>. This foundation enables everything else.</p></li><li><p><strong>Purpose</strong>: Calm Companies have a strong sense of purpose: "What are we building this company for?" Generally, there's an internal purpose: "to improve the lives of the people working here," and an external purpose: "to help more creators get their message into the world."</p></li><li><p><strong>Freedom and Flexibility</strong>: As founders, we build businesses to give us more freedom. A Calm Company gives team members the flexibility to live well, pursue hobbies, exercise, take breaks, go on trips, and connect with family and friends.</p></li><li><p><strong>Fun: </strong>Calm Companies produce more opportunities for fun. "What if we ran this event?" "What if we went on a team retreat in the mountains?" "What if we put an easter egg on our website that plays a silly song?" "What if we made some fun stickers?"</p></li><li><p><strong>Mindful: </strong>At a Calm Company, decisions and commitments are made mindfully. We ask: will this decision make our lives worse? More stressful? Does it align with our values? Will this commitment add too much weight to our culture?</p></li><li><p><strong>Sustainable Growth</strong>: Calm Companies want to grow but at a sustainable pace. Growth should serve a higher purpose. Ambition is good, but not at the expense of well-being.</p></li><li><p><strong>Calm Work Environment</strong>: Stress and chaos are replaced with clear work goals, boundaries, and communication.</p></li></ol>
<h2>Contrast: the Frenzied Company</h2>
<p>Let's contrast the idea of a Calm Company with that of a Frenzied Company.</p>
<p>A frenzied company is perpetually in crisis. They have a culture of impossible deadlines and unrealistic expectations.</p>
<p>Managers pressure employees to constantly be "on" and "available," even after work hours.</p>

<p>An implicit—or sometimes explicitly stated—threat hangs over everyone’s head: "If things don’t turn around, jobs will be lost."</p>
<p>Managers frequently dump their stress and anxiety on their staff. Employees regularly find themselves absorbing negative emotions from work. They come home feeling drained and overwhelmed.</p>
<p>Financially, frenzied companies aren't profitable (or have bad margins). They need a constant cash infusion (investment or debt) to keep the lights on. </p>
<p>Founders at frenzied companies believe pushing harder and working longer will lead to success. But often, the business's underlying structure is rotten. The fundamentals are bad and unlikely to improve. They can't overcome the weight of a bad business model, poor margins, and out-of-control spending.</p>
<p>The frenzied company is a cautionary tale. The path to success isn't to "grind harder," but rather to:</p>
<h2>My story of calm</h2>
<p>When Jon and I started <a href="https://transistor.fm/?via=justin">​Transistor</a>, we were in our late thirties. We'd worked for venture-funded companies, experienced layoffs, and felt the squeeze. We asked each other: "Why are we building this business? What's this in service of?" Our conclusion was to <strong>design the company to improve our lives</strong>.</p>

<p>To do that, we needed a strong <a href="https://justinjackson.ca/the-market/">financial engine</a>. That's not easy to find (and it takes a bit of luck). But once we had one, we had the power to shape our lives through our decisions: How much <a href="https://justinjackson.ca/margin">financial margin</a> would we keep in the business? How much margin for our time? What about leaving margin for our physical and emotional health?</p>
<p>Building a calm company has meant saying no to opportunities that don't align with our values. We've purposefully declined complicated enterprise contracts that would require us to staff up, add more compliance, and sign complicated legal contracts. We try not to commit to projects that burn us out. We've prioritized doing work that we enjoy and that brings customers value.</p>
<p>The world needs more indie entrepreneurs building calm businesses. We won't get more calm from publicly traded companies or the over-funded venture-backed class. It's going to come from the next crop of small, purposefully built, indie startups with healthy margins.</p>
<p>Cheers,<br>Justin Jackson<br><br>Connect with me on:<br>💼 <a href="https://www.linkedin.com/in/justinijackson/">LinkedIn</a><br>🐘 <a href="https://mastodon.social/@mijustin">Mastodon</a><br>🧵 <a href="https://www.threads.net/@mijustin">Threads</a><br>🐦 <a href="https://twitter.com/mijustin">Twitter</a></p>
</article>


<hr>

<footer>
<p>
<a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
</svg> Accueil</a> •
<a href="/david/log/" title="Accès au flux RSS"><svg class="icon icon-rss2">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-rss2"></use>
</svg> Suivre</a> •
<a href="http://larlet.com" title="Go to my English profile" data-instant><svg class="icon icon-user-tie">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-user-tie"></use>
</svg> Pro</a> •
<a href="mailto:david%40larlet.fr" title="Envoyer un courriel"><svg class="icon icon-mail">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-mail"></use>
</svg> Email</a> •
<abbr class="nowrap" title="Hébergeur : Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33184162340"><svg class="icon icon-hammer2">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-hammer2"></use>
</svg> Légal</abbr>
</p>
<template id="theme-selector">
<form>
<fieldset>
<legend><svg class="icon icon-brightness-contrast">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-brightness-contrast"></use>
</svg> Thème</legend>
<label>
<input type="radio" value="auto" name="chosen-color-scheme" checked> Auto
</label>
<label>
<input type="radio" value="dark" name="chosen-color-scheme"> Foncé
</label>
<label>
<input type="radio" value="light" name="chosen-color-scheme"> Clair
</label>
</fieldset>
</form>
</template>
</footer>
<script src="/static/david/js/instantpage-5.1.0.min.js" type="module"></script>
<script>
function loadThemeForm(templateName) {
const themeSelectorTemplate = document.querySelector(templateName)
const form = themeSelectorTemplate.content.firstElementChild
themeSelectorTemplate.replaceWith(form)

form.addEventListener('change', (e) => {
const chosenColorScheme = e.target.value
localStorage.setItem('theme', chosenColorScheme)
toggleTheme(chosenColorScheme)
})

const selectedTheme = localStorage.getItem('theme')
if (selectedTheme && selectedTheme !== 'undefined') {
form.querySelector(`[value="${selectedTheme}"]`).checked = true
}
}

const prefersColorSchemeDark = '(prefers-color-scheme: dark)'
window.addEventListener('load', () => {
let hasDarkRules = false
for (const styleSheet of Array.from(document.styleSheets)) {
let mediaRules = []
for (const cssRule of styleSheet.cssRules) {
if (cssRule.type !== CSSRule.MEDIA_RULE) {
continue
}
// WARNING: Safari does not have/supports `conditionText`.
if (cssRule.conditionText) {
if (cssRule.conditionText !== prefersColorSchemeDark) {
continue
}
} else {
if (cssRule.cssText.startsWith(prefersColorSchemeDark)) {
continue
}
}
mediaRules = mediaRules.concat(Array.from(cssRule.cssRules))
}

// WARNING: do not try to insert a Rule to a styleSheet you are
// currently iterating on, otherwise the browser will be stuck
// in a infinite loop…
for (const mediaRule of mediaRules) {
styleSheet.insertRule(mediaRule.cssText)
hasDarkRules = true
}
}
if (hasDarkRules) {
loadThemeForm('#theme-selector')
}
})
</script>
</body>
</html>

+ 16
- 0
cache/2024/944899e3cafa6019bd2f285284a14dd7/index.md View File

@@ -0,0 +1,16 @@
title: We need more calm companies
url: https://justinjackson.ca/calm-company
hash_url: 944899e3cafa6019bd2f285284a14dd7
archive_date: 2024-04-18
og_image: https://justinjackson.ca/assets/calm-companies.png
description: Calm companies are profitable, value freedom, have a purpose, and improve the team's lives. Frenzied companies are crisis-driven.
favicon: https://justinjackson.ca/img/asset/bWFpbi9icnV0YWwvanVzdGluLWphY2tzb24tZG90cy1jaXJjbGUucG5n?w=32&h=32&s=84e63e02be5161c4fdb75e2f8304ae35
language: en

<p>It's been hard to watch the recent layoffs in the tech industry. Seeing so many good people being let go has been heartbreaking.</p><p>Getting laid off doesn’t just rattle your career; it shakes the very foundation of your life. I've seen friends lose their jobs and go into an existential tailspin. It's not uncommon for them to blame themselves and to feel immense guilt and shame. The experience is especially hard on parents whose families depend on them for income.</p><h2>An inevitable outcome of a flawed system</h2><p>Layoffs are inevitable during a company's lifespan. But seeing startups cut 20-30% of their workforce feels especially cruel in light of the aggressive hiring they did during the pandemic.</p><p>The recent tech layoff spree was a result of following the wrong incentives. Investors had flooded the market with cash and told companies to hire aggressively. Growth took precedence over profitability. Then, capital dried up, and companies started thrashing. Their response was to fire large groups of people in a hasty and unthought-out manner.</p><p>This is a sad reality of corporations optimizing for investor returns: the people who work on and buy the product suffer. Good employees are fired, and useful products are shut down.</p><p>This chaos has reaffirmed my belief that we need more <strong>calm companies.</strong></p><h2>Calm Company definition</h2><p>On the <a href="https://lnk.to/73fDM7">​Pathless Path podcast</a>, Paul Millard asked me to define "calm company:"</p>

<p>A calm company's purpose is to provide exceptional service to customers while simultaneously improving the lives of the people who work there.</p><p>By default, a calm company is profitable. Those profits give a calm company its resilience: there's no last-minute scramble to meet payroll or earn a last-minute sale to keep the business afloat. The company has enough financial margin to weather economic storms.</p><p>Moreover, calm companies are fun to work for. The work is usually interesting and enjoyable. The team has been carefully selected, and there's a good vibe in meetings. </p><p>Calm companies provide meaningful work, healthy interactions, and flexibility for people's lives. If your kid is home sick, you can set work aside and take care of them. If it's a beautiful day, you can go for a run on the beach. </p><h3>Here is my list of Calm Company attributes:</h3><ol><li><p><strong>Profitable</strong>: Calm Companies have a strong financial engine working with <a href="https://justinjackson.ca/margin">good margins</a>. This foundation enables everything else.</p></li><li><p><strong>Purpose</strong>: Calm Companies have a strong sense of purpose: "What are we building this company for?" Generally, there's an internal purpose: "to improve the lives of the people working here," and an external purpose: "to help more creators get their message into the world."</p></li><li><p><strong>Freedom and Flexibility</strong>: As founders, we build businesses to give us more freedom. A Calm Company gives team members the flexibility to live well, pursue hobbies, exercise, take breaks, go on trips, and connect with family and friends.</p></li><li><p><strong>Fun: </strong>Calm Companies produce more opportunities for fun. "What if we ran this event?" "What if we went on a team retreat in the mountains?" "What if we put an easter egg on our website that plays a silly song?" "What if we made some fun stickers?"</p></li><li><p><strong>Mindful: </strong>At a Calm Company, decisions and commitments are made mindfully. We ask: will this decision make our lives worse? More stressful? Does it align with our values? Will this commitment add too much weight to our culture?</p></li><li><p><strong>Sustainable Growth</strong>: Calm Companies want to grow but at a sustainable pace. Growth should serve a higher purpose. Ambition is good, but not at the expense of well-being.</p></li><li><p><strong>Calm Work Environment</strong>: Stress and chaos are replaced with clear work goals, boundaries, and communication.</p></li></ol><h2>Contrast: the Frenzied Company</h2><p>Let's contrast the idea of a Calm Company with that of a Frenzied Company.</p><p>A frenzied company is perpetually in crisis. They have a culture of impossible deadlines and unrealistic expectations.</p><p>Managers pressure employees to constantly be "on" and "available," even after work hours.</p>

<p>An implicit—or sometimes explicitly stated—threat hangs over everyone’s head: "If things don’t turn around, jobs will be lost."</p><p>Managers frequently dump their stress and anxiety on their staff. Employees regularly find themselves absorbing negative emotions from work. They come home feeling drained and overwhelmed.</p><p>Financially, frenzied companies aren't profitable (or have bad margins). They need a constant cash infusion (investment or debt) to keep the lights on. </p><p>Founders at frenzied companies believe pushing harder and working longer will lead to success. But often, the business's underlying structure is rotten. The fundamentals are bad and unlikely to improve. They can't overcome the weight of a bad business model, poor margins, and out-of-control spending.</p><p>The frenzied company is a cautionary tale. The path to success isn't to "grind harder," but rather to:</p><h2>My story of calm</h2><p>When Jon and I started <a href="https://transistor.fm/?via=justin">​Transistor</a>, we were in our late thirties. We'd worked for venture-funded companies, experienced layoffs, and felt the squeeze. We asked each other: "Why are we building this business? What's this in service of?" Our conclusion was to <strong>design the company to improve our lives</strong>.</p>

<p>To do that, we needed a strong <a href="https://justinjackson.ca/the-market/">financial engine</a>. That's not easy to find (and it takes a bit of luck). But once we had one, we had the power to shape our lives through our decisions: How much <a href="https://justinjackson.ca/margin">financial margin</a> would we keep in the business? How much margin for our time? What about leaving margin for our physical and emotional health?</p><p>Building a calm company has meant saying no to opportunities that don't align with our values. We've purposefully declined complicated enterprise contracts that would require us to staff up, add more compliance, and sign complicated legal contracts. We try not to commit to projects that burn us out. We've prioritized doing work that we enjoy and that brings customers value.</p><p>The world needs more indie entrepreneurs building calm businesses. We won't get more calm from publicly traded companies or the over-funded venture-backed class. It's going to come from the next crop of small, purposefully built, indie startups with healthy margins.</p><p>Cheers,<br>Justin Jackson<br><br>Connect with me on:<br>💼 <a href="https://www.linkedin.com/in/justinijackson/">LinkedIn</a><br>🐘 <a href="https://mastodon.social/@mijustin">Mastodon</a><br>🧵 <a href="https://www.threads.net/@mijustin">Threads</a><br>🐦 <a href="https://twitter.com/mijustin">Twitter</a></p>

+ 191
- 0
cache/2024/a20d47394f5c790270fd5af6faa58651/index.html View File

@@ -0,0 +1,191 @@
<!doctype html><!-- This is a valid HTML5 document. -->
<!-- Screen readers, SEO, extensions and so on. -->
<html lang="en">
<!-- Has to be within the first 1024 bytes, hence before the `title` element
See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
<meta charset="utf-8">
<!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
<!-- The viewport meta is quite crowded and we are responsible for that.
See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
<meta name="viewport" content="width=device-width,initial-scale=1">
<!-- Required to make a valid HTML5 document. -->
<title>Just normal web things. (archive) — David Larlet</title>
<meta name="description" content="Publication mise en cache pour en conserver une trace.">
<!-- That good ol' feed, subscribe :). -->
<link rel="alternate" type="application/atom+xml" title="Feed" href="/david/log/">
<!-- Generated from https://realfavicongenerator.net/ such a mess. -->
<link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons2/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons2/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons2/favicon-16x16.png">
<link rel="manifest" href="/static/david/icons2/site.webmanifest">
<link rel="mask-icon" href="/static/david/icons2/safari-pinned-tab.svg" color="#07486c">
<link rel="shortcut icon" href="/static/david/icons2/favicon.ico">
<meta name="msapplication-TileColor" content="#f7f7f7">
<meta name="msapplication-config" content="/static/david/icons2/browserconfig.xml">
<meta name="theme-color" content="#f7f7f7" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#272727" media="(prefers-color-scheme: dark)">
<!-- Is that even respected? Retrospectively? What a shAItshow…
https://neil-clarke.com/block-the-bots-that-feed-ai-models-by-scraping-your-website/ -->
<meta name="robots" content="noai, noimageai">
<!-- Documented, feel free to shoot an email. -->
<link rel="stylesheet" href="/static/david/css/style_2021-01-20.css">
<!-- See https://www.zachleat.com/web/comprehensive-webfonts/ for the trade-off. -->
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
<script>
function toggleTheme(themeName) {
document.documentElement.classList.toggle(
'forced-dark',
themeName === 'dark'
)
document.documentElement.classList.toggle(
'forced-light',
themeName === 'light'
)
}
const selectedTheme = localStorage.getItem('theme')
if (selectedTheme !== 'undefined') {
toggleTheme(selectedTheme)
}
</script>

<meta name="robots" content="noindex, nofollow">
<meta content="origin-when-cross-origin" name="referrer">
<!-- Canonical URL for SEO purposes -->
<link rel="canonical" href="https://heather-buchel.com/blog/2023/07/just-normal-web-things/">

<body class="remarkdown h1-underline h2-underline h3-underline em-underscore hr-center ul-star pre-tick" data-instant-intensity="viewport-all">


<article>
<header>
<h1>Just normal web things.</h1>
</header>
<nav>
<p class="center">
<a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
</svg> Accueil</a> •
<a href="https://heather-buchel.com/blog/2023/07/just-normal-web-things/" title="Lien vers le contenu original">Source originale</a>
<br>
Mis en cache le 2024-04-18
</p>
</nav>
<hr>
<p>We've let ourselves get away from building websites that can do normal web things. I've noticed this a lot recently due to a surge in new social media platforms springing up. Everyone is building new clients, apps, and in some cases, like React Native, attempting to share code across platforms. It's definitely exciting, and I'm actually thrilled that people are building these things.</p>
<blockquote class="bq bq--right"> In the end, it's usually because we've JavaScript'ed our way out of these things.</blockquote>
<p>What is less thrilling is that, nevermind the basic accessibility requirements that are often missing like alt text on images, we stopped letting people do very normal web things. There are a number of avenues to route the blame to: rushing to release something midly usable for testing protocols in the wild, not having a UI engineer on the project, building things in a mobile "touch first" experience and ignoring other inputs or devices; the list goes on. In the end, it's usually because we've JavaScript'ed our way out of these things.</p>
<p>Here are some things I wish people allowed to continue to work in their web projects:</p>
<ul>
<li><strong>Let me copy text so I can paste it.</strong> Please. This is often cause by removing pointer-events or layering elements on eachother that are meant to be clickable. This happens a lot with clickable "card" components.</li>
<li><strong>If something navigates like a link, let me do link things.</strong> Let me right click on the link without it navigating so I can open the context menu that lets me do other link things (like copying link text, copying link address, etc.) Let me use usual link keyboard shortcuts (like <code>ctrl + click</code> on Windows) to open in a new tab. Just normal link things. This is that dreaded thing that us front-end folks are always harping on about: using a div with an on-click to navigate instead of an anchor element.</li>
<li><strong>Let me zoom in on my browser without the website getting all out of whack</strong>. I just want to be able to read.</li>
<li><strong>Do responsive things</strong> I didn't spend most of my early career convincing clients to let us do a responsive website just for you to serve me up a boring layout that kicks down to your mobile layout as soon as you are less than 1200px. I actually think Mastodon and Twitter do a good job at this. The UI feels familiar if you drag your browser down to a small narrow viewport. The space mostly gets used.</li>
<li><strong>Let me have hover styles</strong> I've seen too many React Native ports to the web that have zero :hover or :focus styles beause "you can't hover or tab on mobile, right?" (wrong) or weird disabled looking :active styles. Do normal interaction state things for the web in your web app. This issue in particular almost always makes it glaringly obvious when some poor soul ported their RN app to the web to save time.</li>
<li><strong>If the UI completely changes when I click on something, as if I've navigated to a new page, give me a browser history update and a new url</strong> It's annoying not being able to link to a state in the UI that appears to be it's own page and that is lost to the void if I navigate away.</li>
<li><strong>Let me see scroll bars</strong> Please don't hide them for the sake of your "slick" ui. Sometimes I want to click on the scrollbar and drag it. Just a normal web thing.</li>
<li><strong>Stop hijacking my typical browser shortcuts for use in your own app</strong> I've seen this happen with <code>ctrl + f</code> for opening a custom in-app search bar. I don't want that. It doesn't always search the page as usual.</li>
</ul>
<p>I stopped myself from adding a lot of things that would usually fall under accessibility violations though there is definitely a lot of crossover in the list above. What did I miss? <a href="https://hachyderm.io/@hbuchel/110669996408706858">Let me know on Mastodon.</a></p>
</article>


<hr>

<footer>
<p>
<a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
</svg> Accueil</a> •
<a href="/david/log/" title="Accès au flux RSS"><svg class="icon icon-rss2">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-rss2"></use>
</svg> Suivre</a> •
<a href="http://larlet.com" title="Go to my English profile" data-instant><svg class="icon icon-user-tie">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-user-tie"></use>
</svg> Pro</a> •
<a href="mailto:david%40larlet.fr" title="Envoyer un courriel"><svg class="icon icon-mail">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-mail"></use>
</svg> Email</a> •
<abbr class="nowrap" title="Hébergeur : Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33184162340"><svg class="icon icon-hammer2">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-hammer2"></use>
</svg> Légal</abbr>
</p>
<template id="theme-selector">
<form>
<fieldset>
<legend><svg class="icon icon-brightness-contrast">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-brightness-contrast"></use>
</svg> Thème</legend>
<label>
<input type="radio" value="auto" name="chosen-color-scheme" checked> Auto
</label>
<label>
<input type="radio" value="dark" name="chosen-color-scheme"> Foncé
</label>
<label>
<input type="radio" value="light" name="chosen-color-scheme"> Clair
</label>
</fieldset>
</form>
</template>
</footer>
<script src="/static/david/js/instantpage-5.1.0.min.js" type="module"></script>
<script>
function loadThemeForm(templateName) {
const themeSelectorTemplate = document.querySelector(templateName)
const form = themeSelectorTemplate.content.firstElementChild
themeSelectorTemplate.replaceWith(form)

form.addEventListener('change', (e) => {
const chosenColorScheme = e.target.value
localStorage.setItem('theme', chosenColorScheme)
toggleTheme(chosenColorScheme)
})

const selectedTheme = localStorage.getItem('theme')
if (selectedTheme && selectedTheme !== 'undefined') {
form.querySelector(`[value="${selectedTheme}"]`).checked = true
}
}

const prefersColorSchemeDark = '(prefers-color-scheme: dark)'
window.addEventListener('load', () => {
let hasDarkRules = false
for (const styleSheet of Array.from(document.styleSheets)) {
let mediaRules = []
for (const cssRule of styleSheet.cssRules) {
if (cssRule.type !== CSSRule.MEDIA_RULE) {
continue
}
// WARNING: Safari does not have/supports `conditionText`.
if (cssRule.conditionText) {
if (cssRule.conditionText !== prefersColorSchemeDark) {
continue
}
} else {
if (cssRule.cssText.startsWith(prefersColorSchemeDark)) {
continue
}
}
mediaRules = mediaRules.concat(Array.from(cssRule.cssRules))
}

// WARNING: do not try to insert a Rule to a styleSheet you are
// currently iterating on, otherwise the browser will be stuck
// in a infinite loop…
for (const mediaRule of mediaRules) {
styleSheet.insertRule(mediaRule.cssText)
hasDarkRules = true
}
}
if (hasDarkRules) {
loadThemeForm('#theme-selector')
}
})
</script>
</body>
</html>

+ 24
- 0
cache/2024/a20d47394f5c790270fd5af6faa58651/index.md View File

@@ -0,0 +1,24 @@
title: Just normal web things.
url: https://heather-buchel.com/blog/2023/07/just-normal-web-things/
hash_url: a20d47394f5c790270fd5af6faa58651
archive_date: 2024-04-18
og_image: https://heather-buchel.com/social-images/default.png
description: A plea for us to get back to building websites that can do normal website things.
favicon: https://heather-buchel.com/icon.svg
language: en_US

<p>We've let ourselves get away from building websites that can do normal web things. I've noticed this a lot recently due to a surge in new social media platforms springing up. Everyone is building new clients, apps, and in some cases, like React Native, attempting to share code across platforms. It's definitely exciting, and I'm actually thrilled that people are building these things.</p>
<blockquote class="bq bq--right"> In the end, it's usually because we've JavaScript'ed our way out of these things.</blockquote>
<p>What is less thrilling is that, nevermind the basic accessibility requirements that are often missing like alt text on images, we stopped letting people do very normal web things. There are a number of avenues to route the blame to: rushing to release something midly usable for testing protocols in the wild, not having a UI engineer on the project, building things in a mobile "touch first" experience and ignoring other inputs or devices; the list goes on. In the end, it's usually because we've JavaScript'ed our way out of these things.</p>
<p>Here are some things I wish people allowed to continue to work in their web projects:</p>
<ul>
<li><strong>Let me copy text so I can paste it.</strong> Please. This is often cause by removing pointer-events or layering elements on eachother that are meant to be clickable. This happens a lot with clickable "card" components.</li>
<li><strong>If something navigates like a link, let me do link things.</strong> Let me right click on the link without it navigating so I can open the context menu that lets me do other link things (like copying link text, copying link address, etc.) Let me use usual link keyboard shortcuts (like <code>ctrl + click</code> on Windows) to open in a new tab. Just normal link things. This is that dreaded thing that us front-end folks are always harping on about: using a div with an on-click to navigate instead of an anchor element.</li>
<li><strong>Let me zoom in on my browser without the website getting all out of whack</strong>. I just want to be able to read.</li>
<li><strong>Do responsive things</strong> I didn't spend most of my early career convincing clients to let us do a responsive website just for you to serve me up a boring layout that kicks down to your mobile layout as soon as you are less than 1200px. I actually think Mastodon and Twitter do a good job at this. The UI feels familiar if you drag your browser down to a small narrow viewport. The space mostly gets used.</li>
<li><strong>Let me have hover styles</strong> I've seen too many React Native ports to the web that have zero :hover or :focus styles beause "you can't hover or tab on mobile, right?" (wrong) or weird disabled looking :active styles. Do normal interaction state things for the web in your web app. This issue in particular almost always makes it glaringly obvious when some poor soul ported their RN app to the web to save time.</li>
<li><strong>If the UI completely changes when I click on something, as if I've navigated to a new page, give me a browser history update and a new url</strong> It's annoying not being able to link to a state in the UI that appears to be it's own page and that is lost to the void if I navigate away.</li>
<li><strong>Let me see scroll bars</strong> Please don't hide them for the sake of your "slick" ui. Sometimes I want to click on the scrollbar and drag it. Just a normal web thing.</li>
<li><strong>Stop hijacking my typical browser shortcuts for use in your own app</strong> I've seen this happen with <code>ctrl + f</code> for opening a custom in-app search bar. I don't want that. It doesn't always search the page as usual.</li>
</ul>
<p>I stopped myself from adding a lot of things that would usually fall under accessibility violations though there is definitely a lot of crossover in the list above. What did I miss? <a href="https://hachyderm.io/@hbuchel/110669996408706858">Let me know on Mastodon.</a></p>

+ 191
- 0
cache/2024/c224a3174ad7a76fae1aaa8d174ec791/index.html View File

@@ -0,0 +1,191 @@
<!doctype html><!-- This is a valid HTML5 document. -->
<!-- Screen readers, SEO, extensions and so on. -->
<html lang="en">
<!-- Has to be within the first 1024 bytes, hence before the `title` element
See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
<meta charset="utf-8">
<!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
<!-- The viewport meta is quite crowded and we are responsible for that.
See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
<meta name="viewport" content="width=device-width,initial-scale=1">
<!-- Required to make a valid HTML5 document. -->
<title>Vertical rhythm using CSS lh and rlh units (archive) — David Larlet</title>
<meta name="description" content="Publication mise en cache pour en conserver une trace.">
<!-- That good ol' feed, subscribe :). -->
<link rel="alternate" type="application/atom+xml" title="Feed" href="/david/log/">
<!-- Generated from https://realfavicongenerator.net/ such a mess. -->
<link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons2/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons2/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons2/favicon-16x16.png">
<link rel="manifest" href="/static/david/icons2/site.webmanifest">
<link rel="mask-icon" href="/static/david/icons2/safari-pinned-tab.svg" color="#07486c">
<link rel="shortcut icon" href="/static/david/icons2/favicon.ico">
<meta name="msapplication-TileColor" content="#f7f7f7">
<meta name="msapplication-config" content="/static/david/icons2/browserconfig.xml">
<meta name="theme-color" content="#f7f7f7" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#272727" media="(prefers-color-scheme: dark)">
<!-- Is that even respected? Retrospectively? What a shAItshow…
https://neil-clarke.com/block-the-bots-that-feed-ai-models-by-scraping-your-website/ -->
<meta name="robots" content="noai, noimageai">
<!-- Documented, feel free to shoot an email. -->
<link rel="stylesheet" href="/static/david/css/style_2021-01-20.css">
<!-- See https://www.zachleat.com/web/comprehensive-webfonts/ for the trade-off. -->
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
<script>
function toggleTheme(themeName) {
document.documentElement.classList.toggle(
'forced-dark',
themeName === 'dark'
)
document.documentElement.classList.toggle(
'forced-light',
themeName === 'light'
)
}
const selectedTheme = localStorage.getItem('theme')
if (selectedTheme !== 'undefined') {
toggleTheme(selectedTheme)
}
</script>

<meta name="robots" content="noindex, nofollow">
<meta content="origin-when-cross-origin" name="referrer">
<!-- Canonical URL for SEO purposes -->
<link rel="canonical" href="https://pawelgrzybek.com/vertical-rhythm-using-css-lh-and-rlh-units/">

<body class="remarkdown h1-underline h2-underline h3-underline em-underscore hr-center ul-star pre-tick" data-instant-intensity="viewport-all">


<article>
<header>
<h1>Vertical rhythm using CSS lh and rlh units</h1>
</header>
<nav>
<p class="center">
<a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
</svg> Accueil</a> •
<a href="https://pawelgrzybek.com/vertical-rhythm-using-css-lh-and-rlh-units/" title="Lien vers le contenu original">Source originale</a>
<br>
Mis en cache le 2024-04-18
</p>
</nav>
<hr>
<p>Vertical rhythm is a design concept that helps to create a harmonious layout by following consistent spacing between elements, typically using the height of a line as a base. I learned it in my design days when printed media was still a thing<sup id="fnref:1"></sup>. How much the same concept applies to the Web’s fluid nature is debatable, but this post is not about that. To fulfil your curiosity — yes, the primitively simple design of my website follows the vertical rhythm.</p>
<p><picture><source srcset="2023-05-16-1.avif" type="image/avif"><source srcset="2023-05-16-1.webp" type="image/webp"><img src="https://pawelgrzybek.com/vertical-rhythm-using-css-lh-and-rlh-units/2023-05-16-1.jpg" alt="Vertical rhythm grid on my website" loading="lazy" decoding="async"></source></source></picture>
<p>Correctly implementing vertical rhythm is a tedious task. Luckily, the <a href="https://www.w3.org/TR/css-values-4/">CSS Values and Units Module Level 4</a> defines two helpful units that make the job a breeze: <code>lh</code> and <code>rlh</code>, which are equal to the computed <code>line-height</code> of the element on which it is used and the root element, respectively.</p></p>
<p>At the time of writing this article, <a href="https://caniuse.com/?search=lh%20unit">the browser support</a> is limited to Safari and Chromium-based browsers. Have a look at this simple example.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="cl"><span class="nt">html</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="k">font-size</span><span class="p">:</span> <span class="mi">100</span><span class="kt">%</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"> <span class="k">line-height</span><span class="p">:</span> <span class="mf">1.5</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nt">main</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="k">padding</span><span class="p">:</span> <span class="mi">1</span><span class="n">rlh</span><span class="p">;</span> <span class="c">/* 🫶 */</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div>
<p>This post is not a tutorial, just a quick appreciation of modern CSS features. Expect more of these because CSS tricks worth writing about are bombarding me daily. Also, it is the first article where I embedded <a href="https://stackblitz.com">StackBlitz</a> demo — it is a great tool. Have a great day, folks 😙</p>
<div class="share" id="share"><p>Did you like it? Please share it with your friends. Thanks!</p>
</article>


<hr>

<footer>
<p>
<a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
</svg> Accueil</a> •
<a href="/david/log/" title="Accès au flux RSS"><svg class="icon icon-rss2">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-rss2"></use>
</svg> Suivre</a> •
<a href="http://larlet.com" title="Go to my English profile" data-instant><svg class="icon icon-user-tie">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-user-tie"></use>
</svg> Pro</a> •
<a href="mailto:david%40larlet.fr" title="Envoyer un courriel"><svg class="icon icon-mail">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-mail"></use>
</svg> Email</a> •
<abbr class="nowrap" title="Hébergeur : Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33184162340"><svg class="icon icon-hammer2">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-hammer2"></use>
</svg> Légal</abbr>
</p>
<template id="theme-selector">
<form>
<fieldset>
<legend><svg class="icon icon-brightness-contrast">
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-brightness-contrast"></use>
</svg> Thème</legend>
<label>
<input type="radio" value="auto" name="chosen-color-scheme" checked> Auto
</label>
<label>
<input type="radio" value="dark" name="chosen-color-scheme"> Foncé
</label>
<label>
<input type="radio" value="light" name="chosen-color-scheme"> Clair
</label>
</fieldset>
</form>
</template>
</footer>
<script src="/static/david/js/instantpage-5.1.0.min.js" type="module"></script>
<script>
function loadThemeForm(templateName) {
const themeSelectorTemplate = document.querySelector(templateName)
const form = themeSelectorTemplate.content.firstElementChild
themeSelectorTemplate.replaceWith(form)

form.addEventListener('change', (e) => {
const chosenColorScheme = e.target.value
localStorage.setItem('theme', chosenColorScheme)
toggleTheme(chosenColorScheme)
})

const selectedTheme = localStorage.getItem('theme')
if (selectedTheme && selectedTheme !== 'undefined') {
form.querySelector(`[value="${selectedTheme}"]`).checked = true
}
}

const prefersColorSchemeDark = '(prefers-color-scheme: dark)'
window.addEventListener('load', () => {
let hasDarkRules = false
for (const styleSheet of Array.from(document.styleSheets)) {
let mediaRules = []
for (const cssRule of styleSheet.cssRules) {
if (cssRule.type !== CSSRule.MEDIA_RULE) {
continue
}
// WARNING: Safari does not have/supports `conditionText`.
if (cssRule.conditionText) {
if (cssRule.conditionText !== prefersColorSchemeDark) {
continue
}
} else {
if (cssRule.cssText.startsWith(prefersColorSchemeDark)) {
continue
}
}
mediaRules = mediaRules.concat(Array.from(cssRule.cssRules))
}

// WARNING: do not try to insert a Rule to a styleSheet you are
// currently iterating on, otherwise the browser will be stuck
// in a infinite loop…
for (const mediaRule of mediaRules) {
styleSheet.insertRule(mediaRule.cssText)
hasDarkRules = true
}
}
if (hasDarkRules) {
loadThemeForm('#theme-selector')
}
})
</script>
</body>
</html>

+ 18
- 0
cache/2024/c224a3174ad7a76fae1aaa8d174ec791/index.md View File

@@ -0,0 +1,18 @@
title: Vertical rhythm using CSS lh and rlh units
url: https://pawelgrzybek.com/vertical-rhythm-using-css-lh-and-rlh-units/
hash_url: c224a3174ad7a76fae1aaa8d174ec791
archive_date: 2024-04-18
og_image: https://pawelgrzybek.com/vertical-rhythm-using-css-lh-and-rlh-units/og.jpg
description: Vertical rhythm is a design concept that helps to create a harmonious layout by following consistent spacing between elements, typically using the height of a line as a base.
favicon: https://pawelgrzybek.com/icon.svg
language: en_US

<p>Vertical rhythm is a design concept that helps to create a harmonious layout by following consistent spacing between elements, typically using the height of a line as a base. I learned it in my design days when printed media was still a thing<sup id="fnref:1"></sup>. How much the same concept applies to the Web’s fluid nature is debatable, but this post is not about that. To fulfil your curiosity — yes, the primitively simple design of my website follows the vertical rhythm.</p><picture><source srcset="2023-05-16-1.avif" type="image/avif"><source srcset="2023-05-16-1.webp" type="image/webp"><img src="https://pawelgrzybek.com/vertical-rhythm-using-css-lh-and-rlh-units/2023-05-16-1.jpg" alt="Vertical rhythm grid on my website" loading="lazy" decoding="async"></source></source></picture><p>Correctly implementing vertical rhythm is a tedious task. Luckily, the <a href="https://www.w3.org/TR/css-values-4/">CSS Values and Units Module Level 4</a> defines two helpful units that make the job a breeze: <code>lh</code> and <code>rlh</code>, which are equal to the computed <code>line-height</code> of the element on which it is used and the root element, respectively.</p><p>At the time of writing this article, <a href="https://caniuse.com/?search=lh%20unit">the browser support</a> is limited to Safari and Chromium-based browsers. Have a look at this simple example.</p><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="cl"><span class="nt">html</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="k">font-size</span><span class="p">:</span> <span class="mi">100</span><span class="kt">%</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"> <span class="k">line-height</span><span class="p">:</span> <span class="mf">1.5</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nt">main</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="k">padding</span><span class="p">:</span> <span class="mi">1</span><span class="n">rlh</span><span class="p">;</span> <span class="c">/* 🫶 */</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>This post is not a tutorial, just a quick appreciation of modern CSS features. Expect more of these because CSS tricks worth writing about are bombarding me daily. Also, it is the first article where I embedded <a href="https://stackblitz.com">StackBlitz</a> demo — it is a great tool. Have a great day, folks 😙</p><div class="share" id="share"><p>Did you like it? Please share it with your friends. Thanks!</p>

+ 18
- 0
cache/2024/index.html View File

@@ -128,12 +128,18 @@
<li><a href="/david/cache/2024/f1041ff1c0dfabc719d64a4a5bcb0c10/" title="Accès à l’article dans le cache local : Fracas, collectif queer et féministe d'aide à la gestion de conflits">Fracas, collectif queer et féministe d'aide à la gestion de conflits</a> (<a href="https://www.collectif-fracas.com/" title="Accès à l’article original distant : Fracas, collectif queer et féministe d'aide à la gestion de conflits">original</a>)</li>
<li><a href="/david/cache/2024/944899e3cafa6019bd2f285284a14dd7/" title="Accès à l’article dans le cache local : We need more calm companies">We need more calm companies</a> (<a href="https://justinjackson.ca/calm-company" title="Accès à l’article original distant : We need more calm companies">original</a>)</li>
<li><a href="/david/cache/2024/ba977526c7a8cab6935708b2cdba5c0c/" title="Accès à l’article dans le cache local : Aging programmer">Aging programmer</a> (<a href="https://world.hey.com/jorge/aging-programmer-d448bdec" title="Accès à l’article original distant : Aging programmer">original</a>)</li>
<li><a href="/david/cache/2024/1137631455ddd7b2acdd1f4071756ba6/" title="Accès à l’article dans le cache local : How cheap, outsourced labour in Africa is shaping AI English">How cheap, outsourced labour in Africa is shaping AI English</a> (<a href="https://simonwillison.net/2024/Apr/18/delve/" title="Accès à l’article original distant : How cheap, outsourced labour in Africa is shaping AI English">original</a>)</li>
<li><a href="/david/cache/2024/0cc2e9c6b29f8326b2ff628f64e22888/" title="Accès à l’article dans le cache local : Proposal: CSS Variable Groups">Proposal: CSS Variable Groups</a> (<a href="https://lea.verou.me/docs/var-groups/" title="Accès à l’article original distant : Proposal: CSS Variable Groups">original</a>)</li>
<li><a href="/david/cache/2024/ce5fdc61fd66cdb9ce548fb543eba986/" title="Accès à l’article dans le cache local : Unsigned Commits">Unsigned Commits</a> (<a href="https://blog.glyph.im/2024/01/unsigned-commits.html" title="Accès à l’article original distant : Unsigned Commits">original</a>)</li>
<li><a href="/david/cache/2024/2b6f113e6c47bf6d2b282fa0a48b48a6/" title="Accès à l’article dans le cache local : Vibe Driven Development">Vibe Driven Development</a> (<a href="https://robinrendle.com/notes/vibe-driven-development/" title="Accès à l’article original distant : Vibe Driven Development">original</a>)</li>
<li><a href="/david/cache/2024/5030196507bcf3e06162e9eaed40abbe/" title="Accès à l’article dans le cache local : Blogging and Composting">Blogging and Composting</a> (<a href="https://blog.jim-nielsen.com/2023/blogging-and-compositing/" title="Accès à l’article original distant : Blogging and Composting">original</a>)</li>
<li><a href="/david/cache/2024/ff566a58892db07815a327802fea66d3/" title="Accès à l’article dans le cache local : In Loving Memory of Square Checkbox">In Loving Memory of Square Checkbox</a> (<a href="https://tonsky.me/blog/checkbox/" title="Accès à l’article original distant : In Loving Memory of Square Checkbox">original</a>)</li>
@@ -188,10 +194,14 @@
<li><a href="/david/cache/2024/faa1d8cae94da6838ff9351e5df791ca/" title="Accès à l’article dans le cache local : Make the indie web easier">Make the indie web easier</a> (<a href="https://gilest.org/indie-easy.html" title="Accès à l’article original distant : Make the indie web easier">original</a>)</li>
<li><a href="/david/cache/2024/157e744e8062e5495ba700566e99f8f2/" title="Accès à l’article dans le cache local : Deep dive CSS: font metrics, line-height and vertical-align">Deep dive CSS: font metrics, line-height and vertical-align</a> (<a href="https://iamvdo.me/en/blog/css-font-metrics-line-height-and-vertical-align" title="Accès à l’article original distant : Deep dive CSS: font metrics, line-height and vertical-align">original</a>)</li>
<li><a href="/david/cache/2024/140458968f9f7da7d14e181d0a80e799/" title="Accès à l’article dans le cache local : The Expanding Dark Forest and Generative AI">The Expanding Dark Forest and Generative AI</a> (<a href="https://maggieappleton.com/ai-dark-forest" title="Accès à l’article original distant : The Expanding Dark Forest and Generative AI">original</a>)</li>
<li><a href="/david/cache/2024/3debc675a055d691b32c7d6904531eb4/" title="Accès à l’article dans le cache local : How Google perfected the web">How Google perfected the web</a> (<a href="https://www.theverge.com/c/23998379/google-search-seo-algorithm-webpage-optimization" title="Accès à l’article original distant : How Google perfected the web">original</a>)</li>
<li><a href="/david/cache/2024/35c44c8d8b999faa625ed6009da60e99/" title="Accès à l’article dans le cache local : Modern CSS For Dynamic Component-Based Architecture">Modern CSS For Dynamic Component-Based Architecture</a> (<a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/" title="Accès à l’article original distant : Modern CSS For Dynamic Component-Based Architecture">original</a>)</li>
<li><a href="/david/cache/2024/865257313ae3d6aca13284128d6523d5/" title="Accès à l’article dans le cache local : Les Coopitalistes">Les Coopitalistes</a> (<a href="https://www.ouvre-boites.coop/les-coopitalistes" title="Accès à l’article original distant : Les Coopitalistes">original</a>)</li>
<li><a href="/david/cache/2024/036789c955419215be9d88c6823b55aa/" title="Accès à l’article dans le cache local : The fundamentals of the AGPLv3">The fundamentals of the AGPLv3</a> (<a href="https://www.fsf.org/bulletin/2021/fall/the-fundamentals-of-the-agplv3" title="Accès à l’article original distant : The fundamentals of the AGPLv3">original</a>)</li>
@@ -230,6 +240,8 @@
<li><a href="/david/cache/2024/529fce4c2b7c378f07aead94e62d3923/" title="Accès à l’article dans le cache local : Some little ways I’m using CSS :has() in the real world">Some little ways I’m using CSS :has() in the real world</a> (<a href="https://piccalil.li/blog/some-little-ways-im-using-css-has-in-the-real-world/" title="Accès à l’article original distant : Some little ways I’m using CSS :has() in the real world">original</a>)</li>
<li><a href="/david/cache/2024/c224a3174ad7a76fae1aaa8d174ec791/" title="Accès à l’article dans le cache local : Vertical rhythm using CSS lh and rlh units">Vertical rhythm using CSS lh and rlh units</a> (<a href="https://pawelgrzybek.com/vertical-rhythm-using-css-lh-and-rlh-units/" title="Accès à l’article original distant : Vertical rhythm using CSS lh and rlh units">original</a>)</li>
<li><a href="/david/cache/2024/89dbef9daef24f311b6401cef62f5855/" title="Accès à l’article dans le cache local : Des Oloés">Des Oloés</a> (<a href="https://tw5.immateriel.fr/wiki/immateriel/b/YXGEDFB" title="Accès à l’article original distant : Des Oloés">original</a>)</li>
<li><a href="/david/cache/2024/1f40a33f9c57a16d420eb0868a129e96/" title="Accès à l’article dans le cache local : Notes from “An approach to computing and sustainability inspired from permaculture” by Devine LuLinvega">Notes from “An approach to computing and sustainability inspired from permaculture” by Devine LuLinvega</a> (<a href="https://blog.jim-nielsen.com/2024/notes-from-computing-sustainably/" title="Accès à l’article original distant : Notes from “An approach to computing and sustainability inspired from permaculture” by Devine LuLinvega">original</a>)</li>
@@ -250,6 +262,8 @@
<li><a href="/david/cache/2024/821fa933883f080d23c0a6d9d0b3721a/" title="Accès à l’article dans le cache local : Make">Make</a> (<a href="https://www.arthurperret.fr/cours/make.html" title="Accès à l’article original distant : Make">original</a>)</li>
<li><a href="/david/cache/2024/3a28346225751986cc06aaadb8c8bb90/" title="Accès à l’article dans le cache local : TechScape: How cheap, outsourced labour in Africa is shaping AI English">TechScape: How cheap, outsourced labour in Africa is shaping AI English</a> (<a href="https://www.theguardian.com/technology/2024/apr/16/techscape-ai-gadgest-humane-ai-pin-chatgpt" title="Accès à l’article original distant : TechScape: How cheap, outsourced labour in Africa is shaping AI English">original</a>)</li>
<li><a href="/david/cache/2024/85b765a918ef094a5a2dd13a1ff5dd7d/" title="Accès à l’article dans le cache local : RSS 2.0 Specification">RSS 2.0 Specification</a> (<a href="https://www.rssboard.org/rss-specification#extendingRss" title="Accès à l’article original distant : RSS 2.0 Specification">original</a>)</li>
<li><a href="/david/cache/2024/ad911ebf7ba5523ef0be1bdd599f7623/" title="Accès à l’article dans le cache local : JavaScript Bloat in 2024">JavaScript Bloat in 2024</a> (<a href="https://tonsky.me/blog/js-bloat/" title="Accès à l’article original distant : JavaScript Bloat in 2024">original</a>)</li>
@@ -258,6 +272,8 @@
<li><a href="/david/cache/2024/3ea27fca4fabb81676fc1b98264f3bd8/" title="Accès à l’article dans le cache local : It’s OK to call it Artificial Intelligence">It’s OK to call it Artificial Intelligence</a> (<a href="https://simonwillison.net/2024/Jan/7/call-it-ai/" title="Accès à l’article original distant : It’s OK to call it Artificial Intelligence">original</a>)</li>
<li><a href="/david/cache/2024/a20d47394f5c790270fd5af6faa58651/" title="Accès à l’article dans le cache local : Just normal web things.">Just normal web things.</a> (<a href="https://heather-buchel.com/blog/2023/07/just-normal-web-things/" title="Accès à l’article original distant : Just normal web things.">original</a>)</li>
<li><a href="/david/cache/2024/62bf3ce6ef66e39b7f250a6123d92e66/" title="Accès à l’article dans le cache local : Tomorrow & Tomorrow & Tomorrow">Tomorrow & Tomorrow & Tomorrow</a> (<a href="https://erinkissane.com/tomorrow-and-tomorrow-and-tomorrow" title="Accès à l’article original distant : Tomorrow & Tomorrow & Tomorrow">original</a>)</li>
<li><a href="/david/cache/2024/291cddda62f18ec9355ec98761b7e9d9/" title="Accès à l’article dans le cache local : Writing with AI">Writing with AI</a> (<a href="https://ia.net/topics/writing-with-ai" title="Accès à l’article original distant : Writing with AI">original</a>)</li>
@@ -414,6 +430,8 @@
<li><a href="/david/cache/2024/c26881e908632b460cfd93fe61cc0466/" title="Accès à l’article dans le cache local : Following Links - Jim Nielsen’s Blog">Following Links - Jim Nielsen’s Blog</a> (<a href="https://blog.jim-nielsen.com/2024/following-links/" title="Accès à l’article original distant : Following Links - Jim Nielsen’s Blog">original</a>)</li>
<li><a href="/david/cache/2024/4bda7c6500950716846bdeb6bddbafed/" title="Accès à l’article dans le cache local : Hardest Problem in Computer Science: Centering Things">Hardest Problem in Computer Science: Centering Things</a> (<a href="https://tonsky.me/blog/centering/" title="Accès à l’article original distant : Hardest Problem in Computer Science: Centering Things">original</a>)</li>
<li><a href="/david/cache/2024/cd2fda3dae5d89990f73fbdaa1c3b491/" title="Accès à l’article dans le cache local : build a world, not an audience">build a world, not an audience</a> (<a href="https://keningzhu.com/journal/build-a-world-not-an-audience" title="Accès à l’article original distant : build a world, not an audience">original</a>)</li>
</ul>

Loading…
Cancel
Save