A place to cache linked articles (think custom and personal wayback machine)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498
  1. <!doctype html><!-- This is a valid HTML5 document. -->
  2. <!-- Screen readers, SEO, extensions and so on. -->
  3. <html lang="en">
  4. <!-- Has to be within the first 1024 bytes, hence before the `title` element
  5. See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
  6. <meta charset="utf-8">
  7. <!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
  8. <!-- The viewport meta is quite crowded and we are responsible for that.
  9. See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
  10. <meta name="viewport" content="width=device-width,initial-scale=1">
  11. <!-- Required to make a valid HTML5 document. -->
  12. <title>Hardest Problem in Computer Science: Centering Things (archive) — David Larlet</title>
  13. <meta name="description" content="Publication mise en cache pour en conserver une trace.">
  14. <!-- That good ol' feed, subscribe :). -->
  15. <link rel="alternate" type="application/atom+xml" title="Feed" href="/david/log/">
  16. <!-- Generated from https://realfavicongenerator.net/ such a mess. -->
  17. <link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons2/apple-touch-icon.png">
  18. <link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons2/favicon-32x32.png">
  19. <link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons2/favicon-16x16.png">
  20. <link rel="manifest" href="/static/david/icons2/site.webmanifest">
  21. <link rel="mask-icon" href="/static/david/icons2/safari-pinned-tab.svg" color="#07486c">
  22. <link rel="shortcut icon" href="/static/david/icons2/favicon.ico">
  23. <meta name="msapplication-TileColor" content="#f7f7f7">
  24. <meta name="msapplication-config" content="/static/david/icons2/browserconfig.xml">
  25. <meta name="theme-color" content="#f7f7f7" media="(prefers-color-scheme: light)">
  26. <meta name="theme-color" content="#272727" media="(prefers-color-scheme: dark)">
  27. <!-- Is that even respected? Retrospectively? What a shAItshow…
  28. https://neil-clarke.com/block-the-bots-that-feed-ai-models-by-scraping-your-website/ -->
  29. <meta name="robots" content="noai, noimageai">
  30. <!-- Documented, feel free to shoot an email. -->
  31. <link rel="stylesheet" href="/static/david/css/style_2021-01-20.css">
  32. <!-- See https://www.zachleat.com/web/comprehensive-webfonts/ for the trade-off. -->
  33. <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>
  34. <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>
  35. <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>
  36. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  37. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  38. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  39. <script>
  40. function toggleTheme(themeName) {
  41. document.documentElement.classList.toggle(
  42. 'forced-dark',
  43. themeName === 'dark'
  44. )
  45. document.documentElement.classList.toggle(
  46. 'forced-light',
  47. themeName === 'light'
  48. )
  49. }
  50. const selectedTheme = localStorage.getItem('theme')
  51. if (selectedTheme !== 'undefined') {
  52. toggleTheme(selectedTheme)
  53. }
  54. </script>
  55. <meta name="robots" content="noindex, nofollow">
  56. <meta content="origin-when-cross-origin" name="referrer">
  57. <!-- Canonical URL for SEO purposes -->
  58. <link rel="canonical" href="https://tonsky.me/blog/centering/">
  59. <body class="remarkdown h1-underline h2-underline h3-underline em-underscore hr-center ul-star pre-tick" data-instant-intensity="viewport-all">
  60. <article>
  61. <header>
  62. <h1>Hardest Problem in Computer Science: Centering Things</h1>
  63. </header>
  64. <nav>
  65. <p class="center">
  66. <a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
  67. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
  68. </svg> Accueil</a> •
  69. <a href="https://tonsky.me/blog/centering/" title="Lien vers le contenu original">Source originale</a>
  70. <br>
  71. Mis en cache le 2024-04-18
  72. </p>
  73. </nav>
  74. <hr>
  75. <p>This is my claim: we, as a civilization, forgot how to center things.</p>
  76. <p>I mean, we know <em>how</em> to do it. It has never been simpler:</p>
  77. <pre><code>display: flex;
  78. justify-content: center; /* Horizontal centering */
  79. align-items: center; /* Vertical centering */</code></pre>
  80. <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>
  81. <p>Or you can use grids if you want:</p>
  82. <pre><code>display: grid;
  83. justify-items: center; /* Horizontal centering */
  84. align-items: center; /* Vertical centering */</code></pre>
  85. <p>(also don’t ask why <code>justify-content</code> became <code>justify-items</code>)</p>
  86. <p>If you feel like school today, we can deduce it from the first principles:</p>
  87. <figure>
  88. <img src="https://tonsky.me/blog/centering/formula@2x.png?t=1713455735"> </figure>
  89. <p>Hey, even ChatGPT knows how to center things:</p>
  90. <figure>
  91. <img src="https://tonsky.me/blog/centering/chatgpt@2x.png?t=1713455735"> </figure>
  92. <p>Okay, maybe not right away, but eventually it gets there.</p>
  93. <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>
  94. <p>Yet, when we look at actual applications, we see that these methods are not used. We see this:</p>
  95. <figure>
  96. <img src="https://tonsky.me/blog/centering/telegram_date@2x.webp?t=1713455735"> </figure>
  97. <p>or this:</p>
  98. <figure>
  99. <img src="https://tonsky.me/blog/centering/google_maps_cross@2x.webp?t=1713455735"> </figure>
  100. <p>or even this:</p>
  101. <figure>
  102. <img src="https://tonsky.me/blog/centering/feedly_beta@2x.webp?t=1713455735"> </figure>
  103. <p>So something is clearly getting lost between know-how and applying that knowledge.</p>
  104. <figure>
  105. <img src="https://tonsky.me/blog/centering/something.png?t=1713455735"> </figure>
  106. <p>In theory, there’s no difference between theory and practice. Unfortunately, we live in practice.</p>
  107. <p>So what’s happening? Let’s find out.</p>
  108. <h1 id="fonts">Fonts</h1>
  109. <p>Fonts are one of the biggest offenders. You can see poorly aligned text everywhere. Let me showcase.</p>
  110. <p>Apple can’t do it:</p>
  111. <figure>
  112. <img src="https://tonsky.me/blog/centering/apple_buttons_big_sur@2x.png?t=1713455735"> </figure>
  113. <p>Microsoft can’t do it:</p>
  114. <figure>
  115. <img src="https://tonsky.me/blog/centering/windows@2x.webp?t=1713455735"> </figure>
  116. <p>GitHub can’t do it:</p>
  117. <figure>
  118. <img src="https://tonsky.me/blog/centering/github@2x.webp?t=1713455735"> </figure>
  119. <p>Valve can’t do it:</p>
  120. <figure>
  121. <img src="https://tonsky.me/blog/centering/steam@2x.webp?t=1713455735"> </figure>
  122. <p>Slack can’t do it:</p>
  123. <figure>
  124. <img src="https://tonsky.me/blog/centering/slack_button@2x.webp?t=1713455735"> </figure>
  125. <p>Telegram can’t do it:</p>
  126. <figure>
  127. <img src="https://tonsky.me/blog/centering/telegram@2x.webp?t=1713455735"> </figure>
  128. <p>Google Maps can’t do it:</p>
  129. <figure>
  130. <img src="https://tonsky.me/blog/centering/google_maps@2x.webp?t=1713455735"> </figure>
  131. <p>Honestly, I can provide an endless supply of poorly-aligned buttons without even having to look for them:</p>
  132. <figure>
  133. <img src="https://tonsky.me/blog/centering/buttons@2x.png?t=1713455735"> </figure>
  134. <p>I think you get the idea. Myriad companies big and small, native or web, and none are safe from text-centering problems.</p>
  135. <h1 id="line-height">Line height</h1>
  136. <p>If font metrics are not enough, the next problem on our way to perfect centering is line-height.</p>
  137. <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>
  138. <p>This is how it looks applied in practice. Slack:</p>
  139. <figure>
  140. <img src="https://tonsky.me/blog/centering/slack@2x.webp?t=1713455735"> </figure>
  141. <p>Notion:</p>
  142. <figure>
  143. <img src="https://tonsky.me/blog/centering/notion@2x.webp?t=1713455735"> </figure>
  144. <p>Airbnb:</p>
  145. <figure>
  146. <img src="https://tonsky.me/blog/centering/airbnb@2x.webp?t=1713455735"> </figure>
  147. <p>YouTube:</p>
  148. <figure>
  149. <img src="https://tonsky.me/blog/centering/youtube@2x.webp?t=1713455735"> </figure>
  150. <p>Aligning two things in different containers is almost impossible:</p>
  151. <figure>
  152. <img src="https://tonsky.me/blog/centering/name@2x.webp?t=1713455735"> </figure>
  153. <p>Although many have tried:</p>
  154. <figure>
  155. <img src="https://tonsky.me/blog/centering/american_airlines@2x.webp?t=1713455735"> </figure>
  156. <p>Not many have succeeded:</p>
  157. <figure>
  158. <img src="https://tonsky.me/blog/centering/addons@2x.webp?t=1713455735"> </figure>
  159. <p>CSS might get in the way (different controls having different defaults which you have to undo before even starting trying to align):</p>
  160. <figure>
  161. <img src="https://tonsky.me/blog/centering/controls@2x.webp?t=1713455735"> </figure>
  162. <p>No easy solution here, just roll up your sleeves and delve into specifications.</p>
  163. <h1 id="icons">Icons</h1>
  164. <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>
  165. <p>Atom:</p>
  166. <figure>
  167. <img src="https://tonsky.me/blog/centering/atom@2x.webp?t=1713455735"> </figure>
  168. <p>Platform formerly known as Twitter:</p>
  169. <figure>
  170. <img src="https://tonsky.me/blog/centering/twitter@2x.webp?t=1713455735"> </figure>
  171. <p>iOS:</p>
  172. <figure>
  173. <img src="https://tonsky.me/blog/centering/ios@2x.webp?t=1713455735"> </figure>
  174. <p>Mozilla:</p>
  175. <figure>
  176. <img src="https://tonsky.me/blog/centering/mozilla@2x.webp?t=1713455735"> </figure>
  177. <p>YouTube:</p>
  178. <figure>
  179. <img src="https://tonsky.me/blog/centering/youtube_likes@2x.webp?t=1713455735"> </figure>
  180. <p>Sometimes icon wins over text:</p>
  181. <figure>
  182. <img src="https://tonsky.me/blog/centering/meet@2x.webp?t=1713455735"> </figure>
  183. <p>Sometimes text wins over icon:</p>
  184. <figure>
  185. <img src="https://tonsky.me/blog/centering/ical@2x.webp?t=1713455735"> </figure>
  186. <p>Sometimes both lose:</p>
  187. <figure>
  188. <img src="https://tonsky.me/blog/centering/name_button@2x.webp?t=1713455735"> </figure>
  189. <p>Some icons are just plain old HTML form controls:</p>
  190. <figure>
  191. <img src="https://tonsky.me/blog/centering/git_butler@2x.webp?t=1713455735"> </figure>
  192. <p>Some are stylized:</p>
  193. <figure>
  194. <img src="https://tonsky.me/blog/centering/by_bee@2x.webp?t=1713455735"><figcaption>Thanks @bee for the picture</figcaption> </figure>
  195. <p>Sometimes people will get creative to achieve perfect alignment:</p>
  196. <figure>
  197. <img src="https://tonsky.me/blog/centering/github_close@2x.webp?t=1713455735"> </figure>
  198. <p>But overall it’s a pretty hopeless game:</p>
  199. <figure>
  200. <img src="https://tonsky.me/blog/centering/apple_id@2x.webp?t=1713455735"> </figure>
  201. <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>
  202. <figure>
  203. <img src="https://tonsky.me/blog/centering/text_align@2x.png?t=1713455735"> </figure>
  204. <p><code>text-align: middle</code> comes closest, but it aligns by x-height, not cap-height, which still looks unbalanced:</p>
  205. <figure>
  206. <img src="https://tonsky.me/blog/centering/middle@2x.webp?t=1713455735"> </figure>
  207. <p>That’s exactly why people love web programming so much. There’s always a challenge.</p>
  208. <h1 id="icon-fonts">Icon fonts</h1>
  209. <p>Aligning rectangles is relatively easy. Aligning text is hard. Icons are rectangles. So what if we put icons into a font file?</p>
  210. <p>Now we can’t align anything:</p>
  211. <figure>
  212. <img src="https://tonsky.me/blog/centering/icon_fonts@2x.webp?t=1713455735"> </figure>
  213. <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>
  214. <p>Despite many shortcomings and almost no upsides, companies rushed to add icon fonts everywhere. The result is this:</p>
  215. <figure>
  216. <img src="https://tonsky.me/blog/centering/calculators@2x.png?t=1713455735"><figcaption>macOS 10.14 → macOS 10.15</figcaption> </figure>
  217. <p>Notice how operators are not vertically aligned anymore and are also blurry. All because of switching to icon font.</p>
  218. <p>Apple was so committed to icon fonts they even ruined the QuickTime record button:</p>
  219. <figure>
  220. <img src="https://tonsky.me/blog/centering/quicktime@2x.webp?t=1713455735"> </figure>
  221. <p>Just look at it:</p>
  222. <figure>
  223. <img src="https://tonsky.me/blog/centering/quicktime_button@2x.webp?t=1713455735"> </figure>
  224. <p>Yes, it actually looks like this to this day. As does the calculator.</p>
  225. <p>But they are far from being the only ones. One:</p>
  226. <figure>
  227. <img src="https://tonsky.me/blog/centering/icon_1@2x.webp?t=1713455735"> </figure>
  228. <p>Two:</p>
  229. <figure>
  230. <img src="https://tonsky.me/blog/centering/icon_3@2x.webp?t=1713455735"> </figure>
  231. <p>Three:</p>
  232. <figure>
  233. <img src="https://tonsky.me/blog/centering/icon_4@2x.webp?t=1713455735"> </figure>
  234. <p>Four:</p>
  235. <figure>
  236. <img src="https://tonsky.me/blog/centering/icon_5@2x.webp?t=1713455735"> </figure>
  237. <p>Five:</p>
  238. <figure>
  239. <img src="https://tonsky.me/blog/centering/icon_6@2x.webp?t=1713455735"> </figure>
  240. <p>Six:</p>
  241. <figure>
  242. <img src="https://tonsky.me/blog/centering/icon_7@2x.webp?t=1713455735"> </figure>
  243. <p>Seven:</p>
  244. <figure>
  245. <img src="https://tonsky.me/blog/centering/icon_8@2x.webp?t=1713455735"> </figure>
  246. <p>Same as with text alignment, there’s an endless supply of poorly aligned icons.</p>
  247. <h1 id="skill-issue">Skill issue</h1>
  248. <p>Not only programmers fail to center things. Designers do it, too:</p>
  249. <figure>
  250. <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>
  251. <p>The problem with icons is that sometimes you have to take their shape into account for things to look good:</p>
  252. <figure>
  253. <img src="https://tonsky.me/blog/centering/apple_logo@2x.webp?t=1713455735"><figcaption>Bad centering / good centering</figcaption> </figure>
  254. <p>Triangle is notably tricky:</p>
  255. <figure>
  256. <img src="https://tonsky.me/blog/centering/triangle@2x.webp?t=1713455735"> </figure>
  257. <p>Sometimes it is too far to the left:</p>
  258. <figure>
  259. <img src="https://tonsky.me/blog/centering/triangle_left@2x.webp?t=1713455735"> </figure>
  260. <p>Sometimes it’s too far to the right:</p>
  261. <figure>
  262. <img src="https://tonsky.me/blog/centering/triangle_right@2x.webp?t=1713455735"> </figure>
  263. <p>It can even be too high up (line-height strikes again):</p>
  264. <figure>
  265. <img src="https://tonsky.me/blog/centering/triangle_up@2x.webp?t=1713455735"> </figure>
  266. <h1 id="horizontal-centering">Horizontal centering</h1>
  267. <p>You might think that only centering things vertically is hard. Not only! Horizontal might be hard, too:</p>
  268. <figure>
  269. <img src="https://tonsky.me/blog/centering/apple_sign_in_business@2x.webp?t=1713455735"> </figure>
  270. <p>I don’t think there’s a deep reason for these, except for people just being sloppy:</p>
  271. <figure>
  272. <img src="https://tonsky.me/blog/centering/twitter_horizontal@2x.webp?t=1713455735"> </figure>
  273. <p>Just, come on!</p>
  274. <figure>
  275. <img src="https://tonsky.me/blog/centering/android@2x.webp?t=1713455735"> </figure>
  276. <p>Can this be a deliberate decision?</p>
  277. <figure>
  278. <img src="https://tonsky.me/blog/centering/teams@2x.png?t=1713455735"> </figure>
  279. <p>I don’t know. Icons can suffer from it, too:</p>
  280. <figure>
  281. <img src="https://tonsky.me/blog/centering/drive@2x.webp?t=1713455735"> </figure>
  282. <p>As can text:</p>
  283. <figure>
  284. <img src="https://tonsky.me/blog/centering/steam_horizontal@2x.webp?t=1713455735"> </figure>
  285. <h1 id="what-can-be-done-designers">What can be done: designers</h1>
  286. <p>So what <em>is</em> the problem?</p>
  287. <p>It all starts with the font. Right now, the bounding box of a text block looks like this:</p>
  288. <figure>
  289. <img src="https://tonsky.me/blog/centering/text_bounding_box@2x.png?t=1713455735"> </figure>
  290. <p>The problem is, it can also look like this:</p>
  291. <figure>
  292. <img src="https://tonsky.me/blog/centering/text_bounding_box_2@2x.png?t=1713455735"> </figure>
  293. <p>or this:</p>
  294. <figure>
  295. <img src="https://tonsky.me/blog/centering/text_bounding_box_3@2x.png?t=1713455735"> </figure>
  296. <p>Now, what will happen if you try to center text by centering its bounding box?</p>
  297. <figure>
  298. <img src="https://tonsky.me/blog/centering/text_bounding_box_4.png?t=1713455735"> </figure>
  299. <p>The text will be off! Even though rectangles are perfectly centered.</p>
  300. <p>But even if font <em>can</em> have its metrics unbalanced, it doesn’t mean it does. What happens in reality?</p>
  301. <p>In reality, <em>most</em> of the popular fonts have metrics slightly off. Many have it <em>significantly</em> off:</p>
  302. <figure>
  303. <img src="https://tonsky.me/blog/centering/metrics@2x.png?t=1713455735"><figcaption>Percentages are of cap-height</figcaption> </figure>
  304. <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>
  305. <p>Basically, Segoe UI is the reason why Github on Windows looks like this:</p>
  306. <figure>
  307. <img src="https://tonsky.me/blog/centering/github@2x.webp?t=1713455735"> </figure>
  308. <p>The solution is simple: make tight bounding boxes and centering will become trivial:</p>
  309. <figure>
  310. <img src="https://tonsky.me/blog/centering/text_bounding_box_5.png?t=1713455735"> </figure>
  311. <p>If you use Figma, it already can do this (although it’s not the default):</p>
  312. <figure>
  313. <img src="https://tonsky.me/blog/centering/figma_vertical_trim@2x.png?t=1713455735"> </figure>
  314. <h1 id="what-can-be-done-font-designers">What can be done: font designers</h1>
  315. <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>
  316. <figure>
  317. <img src="https://tonsky.me/blog/centering/font_metrics_numbers@2x.png?t=1713455735"> </figure>
  318. <p>Or the same idea, visually:</p>
  319. <figure>
  320. <img src="https://tonsky.me/blog/centering/font_metrics@2x.png?t=1713455735"> </figure>
  321. <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>
  322. <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>
  323. <p>See <a href="https://tonsky.me/blog/font-size/">Font size is useless; let’s fix it</a> for more information.</p>
  324. <h1 id="what-can-be-done-web-developers">What can be done: web developers</h1>
  325. <p>From the developer side, it’s a bit more tricky.</p>
  326. <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>
  327. <p>We’ll use IBM Plex Sans, a font used on this very page. IBM Plex Sans has the following metrics:</p>
  328. <figure>
  329. <img src="https://tonsky.me/blog/centering/ibm_plex_sans@2x.png?t=1713455735"> </figure>
  330. <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>
  331. <figure>
  332. <img src="https://tonsky.me/blog/centering/ibm_plex_sans_notes@2x.png?t=1713455735"> </figure>
  333. <p>With a few simple calculations, we get that extra <code>padding-bottom: 0.052em</code> should do the trick:</p>
  334. <figure>
  335. <img src="https://tonsky.me/blog/centering/numi@2x.webp?t=1713455735"> </figure>
  336. <p>Should work like this:</p>
  337. <figure>
  338. <img src="https://tonsky.me/blog/centering/ibm_plex_sans_padding@2x.png?t=1713455735"> </figure>
  339. <p>Or in actual CSS (select text to see default text bounding box):</p>
  340. <p>
  341. <span>Andy</span>
  342. </p>
  343. <pre><code>
  344. </code></pre>
  345. <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>
  346. <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>
  347. <figure>
  348. <img src="https://tonsky.me/blog/centering/ibm_plex_sans_icon@2x.png?t=1713455735"> </figure>
  349. <p>This, unfortunately, requires you to know both font metrics and icon size. But hey, at least it works:</p>
  350. <p>
  351. <span>
  352. <span></span>
  353. Andy
  354. </span>
  355. </p>
  356. <pre><code>
  357. </code></pre>
  358. <p>Again, select the text above to see how different the browser’s bounding box is from the correct position.</p>
  359. <h1 id="what-can-be-done-icons-fonts">What can be done: icons fonts</h1>
  360. <p>STOP.</p>
  361. <p>USING.</p>
  362. <p>FONTS.</p>
  363. <p>FOR.</p>
  364. <p>ICONS.</p>
  365. <p>Use normal image format. The one with dimensions, you know? Width and height?</p>
  366. <p>Here, I drew a diagram for you, to help you make a decision:</p>
  367. <figure>
  368. <img src="https://tonsky.me/blog/centering/diagram@2x.png?t=1713455735"> </figure>
  369. <p>Just look at how hard Apple tries to put the checkmark inside the rectangle, and the rectangle next to the text label:</p>
  370. <figure>
  371. <img src="https://tonsky.me/blog/centering/apple_sign_in@2x.webp?t=1713455735"> </figure>
  372. <p>And they still fail!</p>
  373. <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>
  374. <p>This is a game that can’t be won.</p>
  375. <h1 id="what-can-be-done-optical-compensations">What can be done: optical compensations</h1>
  376. <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>
  377. <figure>
  378. <img src="https://tonsky.me/blog/centering/icons_baked@2x.png?t=1713455735"> </figure>
  379. <h1 id="what-can-be-done-everyone">What can be done: everyone</h1>
  380. <p>Please pay attention. Please care. Bad centering can ruin otherwise decent UI:</p>
  381. <figure>
  382. <img src="https://tonsky.me/blog/centering/win@2x.webp?t=1713455735"> </figure>
  383. <p>But a properly aligned text can make your UI sing:</p>
  384. <figure>
  385. <img src="https://tonsky.me/blog/centering/win_fix@2x.webp?t=1713455735"> </figure>
  386. <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>
  387. <p>I, for one, want to live in a world of beautiful well-balanced UIs. I trust that you do, too. </p>
  388. <p>It’s all worth it in the end.</p>
  389. <h1 id="honorable-mention">Honorable mention</h1>
  390. <p>Our article would be incomplete without this guy:</p>
  391. <figure>
  392. <video autoplay="" muted="" loop="" preload="auto" playsinline="" controls="">
  393. <source src="https://tonsky.me/blog/centering/spinner.mp4?t=1713455735" type="video/mp4">
  394. </source></video>
  395. </figure>
  396. <p>Take care!</p>
  397. </article>
  398. <hr>
  399. <footer>
  400. <p>
  401. <a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
  402. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
  403. </svg> Accueil</a> •
  404. <a href="/david/log/" title="Accès au flux RSS"><svg class="icon icon-rss2">
  405. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-rss2"></use>
  406. </svg> Suivre</a> •
  407. <a href="http://larlet.com" title="Go to my English profile" data-instant><svg class="icon icon-user-tie">
  408. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-user-tie"></use>
  409. </svg> Pro</a> •
  410. <a href="mailto:david%40larlet.fr" title="Envoyer un courriel"><svg class="icon icon-mail">
  411. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-mail"></use>
  412. </svg> Email</a> •
  413. <abbr class="nowrap" title="Hébergeur : Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33184162340"><svg class="icon icon-hammer2">
  414. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-hammer2"></use>
  415. </svg> Légal</abbr>
  416. </p>
  417. <template id="theme-selector">
  418. <form>
  419. <fieldset>
  420. <legend><svg class="icon icon-brightness-contrast">
  421. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-brightness-contrast"></use>
  422. </svg> Thème</legend>
  423. <label>
  424. <input type="radio" value="auto" name="chosen-color-scheme" checked> Auto
  425. </label>
  426. <label>
  427. <input type="radio" value="dark" name="chosen-color-scheme"> Foncé
  428. </label>
  429. <label>
  430. <input type="radio" value="light" name="chosen-color-scheme"> Clair
  431. </label>
  432. </fieldset>
  433. </form>
  434. </template>
  435. </footer>
  436. <script src="/static/david/js/instantpage-5.1.0.min.js" type="module"></script>
  437. <script>
  438. function loadThemeForm(templateName) {
  439. const themeSelectorTemplate = document.querySelector(templateName)
  440. const form = themeSelectorTemplate.content.firstElementChild
  441. themeSelectorTemplate.replaceWith(form)
  442. form.addEventListener('change', (e) => {
  443. const chosenColorScheme = e.target.value
  444. localStorage.setItem('theme', chosenColorScheme)
  445. toggleTheme(chosenColorScheme)
  446. })
  447. const selectedTheme = localStorage.getItem('theme')
  448. if (selectedTheme && selectedTheme !== 'undefined') {
  449. form.querySelector(`[value="${selectedTheme}"]`).checked = true
  450. }
  451. }
  452. const prefersColorSchemeDark = '(prefers-color-scheme: dark)'
  453. window.addEventListener('load', () => {
  454. let hasDarkRules = false
  455. for (const styleSheet of Array.from(document.styleSheets)) {
  456. let mediaRules = []
  457. for (const cssRule of styleSheet.cssRules) {
  458. if (cssRule.type !== CSSRule.MEDIA_RULE) {
  459. continue
  460. }
  461. // WARNING: Safari does not have/supports `conditionText`.
  462. if (cssRule.conditionText) {
  463. if (cssRule.conditionText !== prefersColorSchemeDark) {
  464. continue
  465. }
  466. } else {
  467. if (cssRule.cssText.startsWith(prefersColorSchemeDark)) {
  468. continue
  469. }
  470. }
  471. mediaRules = mediaRules.concat(Array.from(cssRule.cssRules))
  472. }
  473. // WARNING: do not try to insert a Rule to a styleSheet you are
  474. // currently iterating on, otherwise the browser will be stuck
  475. // in a infinite loop…
  476. for (const mediaRule of mediaRules) {
  477. styleSheet.insertRule(mediaRule.cssText)
  478. hasDarkRules = true
  479. }
  480. }
  481. if (hasDarkRules) {
  482. loadThemeForm('#theme-selector')
  483. }
  484. })
  485. </script>
  486. </body>
  487. </html>