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.

index.html 48KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533
  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>Write CSS. Not too much. Mostly scoped. (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://www.leereamsnyder.com/write-css-not-too-much-mostly-scoped">
  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>Write CSS. Not too much. Mostly scoped.</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://www.leereamsnyder.com/write-css-not-too-much-mostly-scoped" title="Lien vers le contenu original">Source originale</a>
  70. <br>
  71. Mis en cache le 2024-03-23
  72. </p>
  73. </nav>
  74. <hr>
  75. <details id="table-of-contents"><summary data-svelte-h="svelte-nbhsu6"><span class="sc">Table of contents</span></summary>
  76. </details>
  77. <p class="arbitrary-details-space svelte-1tvynej"></p>
  78. <p>I’ve been a web developer of various stripes for over 15 years now. At roughly the same time that I was starting to sling <span class="sc">HTML</span> and <span class="sc">CSS</span> onto the internet, the author and journalist Michael Pollan attempted to answer the <a href="https://www.npr.org/2008/01/01/17725932/in-defense-of-food-author-offers-advice-for-health">“supposedly incredibly complicated and confusing question of what we humans should eat in order to be maximally healthy”</a>. He came up with some simple guidelines that could be boiled down to seven little words:</p>
  79. <blockquote>
  80. <p>Eat food. Not too much. Mostly plants.</p>
  81. </blockquote>
  82. <p>You should eat stuff that is <em>recognizably food</em> instead of hyper-processed garbage. Keep your portions reasonable. Prefer fruits and vegetables, which are overall better for you and the environment.</p>
  83. <p>It was simple, sensible, and backed by science. With that, we never argued about what we should be putting in our bodies ever again.</p>
  84. <p>Just kidding! Flash forward to today and we spend our time <a href="https://www.healthline.com/nutrition/atkins-vs-keto#keto">rehashing dietary trends from last century</a>, <a href="https://www.garbageday.email/p/lifehack-your-water">arguing about water</a>, <a href="https://en.wikipedia.org/wiki/Consumption_of_Tide_Pods#Tide_Pod_Challenge">urging people not to eat Tide Pods</a>, and fighting over <a href="https://www.youtube.com/watch?v=RADEfBJmtk4">appropriate vessels to contain water</a>.</p>
  85. <p>Perhaps Pollan underestimated how much we like to argue online.</p>
  86. <p>I see that same love of arguing playing out in web development with the endless discussion over <a href="https://tailwindcss.com">Tailwind, the utility-first <span class="sc">CSS</span> framework and toolset</a> that hit the scene in 2017.</p>
  87. <p>If you’re somehow unfamiliar with Tailwind, the idea is the framework provides a gigantic pile of utility classes. Take some meat-and-potatoes markup like this (an abridged sample <a href="https://tailwindcss.com/docs/utility-first">from their docs</a>):</p>
  88. <pre><code class="language-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"chat-notification"</span>&gt;</span>
  89. <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"chat-notification-content"</span>&gt;</span>
  90. <span class="hljs-tag">&lt;<span class="hljs-name">h4</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"chat-notification-title"</span>&gt;</span>ChitChat<span class="hljs-tag">&lt;/<span class="hljs-name">h4</span>&gt;</span>
  91. <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  92. <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  93. <span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="language-css">
  94. <span class="hljs-selector-class">.chat-notification</span> {
  95. <span class="hljs-attribute">display</span>: flex;
  96. <span class="hljs-attribute">max-width</span>: <span class="hljs-number">24rem</span>;
  97. <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span> auto;
  98. <span class="hljs-attribute">padding</span>: <span class="hljs-number">1.5rem</span>;
  99. <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">0.5rem</span>;
  100. <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#fff</span>;
  101. <span class="hljs-attribute">box-shadow</span>:
  102. <span class="hljs-number">0</span> <span class="hljs-number">20px</span> <span class="hljs-number">25px</span> -<span class="hljs-number">5px</span> <span class="hljs-built_in">rgba</span>(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0.1</span>),
  103. <span class="hljs-number">0</span> <span class="hljs-number">10px</span> <span class="hljs-number">10px</span> -<span class="hljs-number">5px</span> <span class="hljs-built_in">rgba</span>(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0.04</span>);
  104. }
  105. <span class="hljs-selector-class">.chat-notification-content</span> {
  106. <span class="hljs-attribute">margin-left</span>: <span class="hljs-number">1.5rem</span>;
  107. <span class="hljs-attribute">padding-top</span>: <span class="hljs-number">0.25rem</span>;
  108. }
  109. <span class="hljs-selector-class">.chat-notification-title</span> {
  110. <span class="hljs-attribute">color</span>: <span class="hljs-number">#1a202c</span>;
  111. <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1.25rem</span>;
  112. <span class="hljs-attribute">line-height</span>: <span class="hljs-number">1.25</span>;
  113. }
  114. </span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>
  115. </code></pre>
  116. <p>With Tailwind, you bring in their huge stylesheet or (very preferably) use their build tooling and build the same component like so, using only the library’s classes that often apply a single styling property like font size or color or background color:</p>
  117. <pre><code class="language-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"p-6 max-w-sm mx-auto bg-white rounded-xl shadow-lg flex items-center space-x-4"</span>&gt;</span>
  118. <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
  119. <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"text-xl font-medium text-black"</span>&gt;</span>ChitChat<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  120. <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  121. <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  122. </code></pre>
  123. <p>Take a beat and examine your feelings about those code samples. Do either of them raise your hackles or make you want to yell at strangers online?</p>
  124. <p>You might think that using Tailwind and coding in this style would be a personal preference, but it <em>clearly</em> hits something deeper. Tailwind is possibly the most divisive thing I’ve seen in my career as a developer.</p>
  125. <p>Here’s a small list of articles. First, some <strong>pro</strong> ones:</p>
  126. <p>And some <strong>con</strong> ones:</p>
  127. <p>Most recently, here’s <a href="https://heydonworks.com/article/what-is-utility-first-css/">What is Utility-First CSS?</a> by Heydon Pickering, which is very spicy:</p>
  128. <blockquote>
  129. <p>So why is this utility-first approach so popular at the moment? Partly because the designs we’re charged with coding often are f**ked and we need equally f**ked tools to wrangle them. Partly it’s because the f**ked tools we’ve adopted to write f**ked JavaScript don’t play so well with CSS or, for that matter, HTML. Mostly, it’s because developer insecurity and neophilia are easily exploited: “Have you ever written CSS you weren’t quite happy with? Well here’s a radical, paradigm-shifting, quasi-proprietary solution! You’ll never embarrass yourself again!”</p>
  130. <p>It turns out, people in tech are particularly bad at distinguishing between paradigm shifts and paradigm sharts. That’s why we have nose-diving cryptocurrencies, dust-collecting monkey JPEG portfolios, and AI-generated children’s books teaching kids about pink, two-headed dinosaurs that never existed.</p>
  131. </blockquote>
  132. <p>I mean c’mon that’s pretty funny.</p>
  133. <p>We can’t stop talking about it. Here I am talking about it more.</p>
  134. <p>I recommend that you read all of those articles, as both sides make plenty of interesting points. But in the interest of your time, if I had to <em>greatly</em> reduce their arguments, the <strong>Yay-Tailwind</strong> folks are saying:</p>
  135. <ul>
  136. <li>Tailwind’s utility-first, just-use-these-classes philosophy clicks for me in a way that the <a href="http://smacss.com">many</a>, <a href="https://getbem.com">many</a>, <a href="https://technotif.com/manage-large-css-projects-with-itcss/">many</a> mental models for managing <span class="sc">CSS</span> haven’t.</li>
  137. <li>Managing <span class="sc">CSS</span> at scale is very hard, and I mostly don’t have to do that with Tailwind.</li>
  138. <li>Tailwind makes it easy to put together something that looks OK quickly. (This partially confuses the usefulness of a utility-first philosophy with the usefulness of a whole-ass design system, but, sure, OK. You like it.)</li>
  139. </ul>
  140. <p>Meanwhile, the <strong>Down-With-Tailwind</strong> folks say:</p>
  141. <ul>
  142. <li>You’re adding a dependency on Tailwind’s build tooling for customization and (especially) performance. Are you really looking for more complexity in your tech stack?</li>
  143. <li>Because Tailwind is theoretically replacing all your <span class="sc">CSS</span>, their surface area is huge. If you’re going to <a href="https://v2.tailwindcss.com/docs">learn a bunch of Tailwind</a> to do box models, margin, padding, borders, typography, sizing, spacing, box-shadows, backgrounds, colors, fonts, animations, <a href="https://tailwindcss.com/docs/backdrop-hue-rotate">backdrop hue rotations</a>(???) etc etc, why not just learn <span class="sc">CSS</span>?</li>
  144. <li>If you are choosing to not learn <span class="sc">CSS</span>, you might regret that choice when you have to troubleshoot your Tailwind code, which is inevitably just <span class="sc">CSS</span>. (I personally don’t see this brought up often enough.)</li>
  145. <li>The markup is ugly!</li>
  146. </ul>
  147. <p>So, <strong>where do I stand</strong>?</p>
  148. <p>I have built stuff with and without Tailwind professionally, and I see the merits of both sides.</p>
  149. <p>The way Tailwind <a href="https://dev.to/cher/sexism-racism-toxic-positivity-and-tailwindcss-cil">actively pushes against making hasty abstractions</a> is — really — the smartest thing about it. In my experience, when you’re building something new you’re better off making something functional quickly and worrying about code elegance and deduplication and abstractions later, when you’re hopefully still in business. With a little practice, in a Tailwind project it’s relatively easy to get into a just-building-the-thing flow state. I get to shove the part of me that frets about good naming and specificity and leaking styles and efficient reuse into a drawer for a bit. It’s kinda nice.</p>
  150. <p>And, sure, the output looks decent, if you accept that I will be able to spot your Tailwind-powered dashboard from <a href="https://twitter.com/chriscoyier/status/1331303400835837953?lang=en">a mile away</a> just like the (approx.) 7 quadrillion <a href="https://getbootstrap.com/docs/5.0/getting-started/introduction/">Bootstrap</a>-powered dashboards I’ve seen. (“You can customize it!” “You didn’t, though.”)</p>
  151. <p>On the other hand, the pleasant-in-use simplicity of Tailwind falls apart when you’re doing something of even moderate complexity:</p>
  152. <pre><code class="language-html"><span class="hljs-tag">&lt;<span class="hljs-name">button</span>
  153. <span class="hljs-attr">class</span>=<span class="hljs-string">"
  154. [<span class="hljs-symbol">&amp;amp;</span>&gt;[data-slot=icon]]:-mx-0.5
  155. [<span class="hljs-symbol">&amp;amp;</span>&gt;[data-slot=icon]]:my-0.5
  156. [<span class="hljs-symbol">&amp;amp;</span>&gt;[data-slot=icon]]:shrink-0
  157. [<span class="hljs-symbol">&amp;amp;</span>&gt;[data-slot=icon]]:size-5
  158. [<span class="hljs-symbol">&amp;amp;</span>&gt;[data-slot=icon]]:sm:my-1
  159. [<span class="hljs-symbol">&amp;amp;</span>&gt;[data-slot=icon]]:sm:size-4
  160. [<span class="hljs-symbol">&amp;amp;</span>&gt;[data-slot=icon]]:text-[--btn-icon]
  161. [--btn-bg:theme(colors.zinc.900)]
  162. [--btn-border:theme(colors.zinc.950/90%)]
  163. [--btn-hover-overlay:theme(colors.white/10%)]
  164. [--btn-icon:theme(colors.zinc.400)]
  165. after:-z-10
  166. after:absolute
  167. after:data-[active]:bg-[--btn-hover-overlay]
  168. after:data-[disabled]:shadow-none
  169. after:data-[hover]:bg-[--btn-hover-overlay]
  170. after:inset-0
  171. after:rounded-[calc(theme(borderRadius.lg)-1px)]
  172. after:shadow-[shadow:inset_0_1px_theme(colors.white/15%)]
  173. before:-z-10
  174. before:absolute
  175. before:bg-[--btn-bg]
  176. before:data-[disabled]:shadow-none
  177. before:inset-0
  178. before:rounded-[calc(theme(borderRadius.lg)-1px)]
  179. before:shadow
  180. bg-[--btn-border]
  181. border
  182. border-transparent
  183. dark:[--btn-bg:theme(colors.zinc.600)]
  184. dark:[--btn-hover-overlay:theme(colors.white/5%)]
  185. dark:after:-inset-px
  186. dark:after:rounded-lg
  187. dark:before:hidden
  188. dark:bg-[--btn-bg]
  189. dark:border-white/5
  190. dark:text-white
  191. data-[active]:[--btn-icon:theme(colors.zinc.300)]
  192. data-[disabled]:opacity-50
  193. data-[focus]:outline
  194. data-[focus]:outline-2
  195. data-[focus]:outline-blue-500
  196. data-[focus]:outline-offset-2
  197. data-[hover]:[--btn-icon:theme(colors.zinc.300)]
  198. focus:outline-none
  199. font-semibold
  200. forced-colors:[--btn-icon:ButtonText]
  201. forced-colors:data-[hover]:[--btn-icon:ButtonText]
  202. gap-x-2
  203. inline-flex
  204. isolate
  205. items-center
  206. justify-center
  207. px-[calc(theme(spacing[3.5])-1px)]
  208. py-[calc(theme(spacing[2.5])-1px)]
  209. relative
  210. rounded-lg
  211. sm:px-[calc(theme(spacing.3)-1px)]
  212. sm:py-[calc(theme(spacing[1.5])-1px)]
  213. sm:text-sm/6
  214. text-base/6
  215. text-white"</span>
  216. &gt;</span>
  217. Button
  218. <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
  219. </code></pre>
  220. <p>That’s for <em>one</em> (1) button. The chat notification sample above didn’t make me retch, but this? 🤢</p>
  221. <p>Ok, look, I’m not a purist about pretty code and one look at my website or wardrobe should tell you that I have questionable taste. But I would think that both sides of this debate would say that code looks <em>absolutely bananas</em>. But no, because that’s one of the buttons from the <a href="https://catalyst.tailwindui.com/">Catalyst framework</a>, built by the Tailwind team.</p>
  222. <p>Sure, fine, buttons can be deceptively complex and Catalyst is still in development, so maybe this changes in the future. But code like this — especially interactive elements or things that need media queries — is not atypical.</p>
  223. <p>First, Tailwind’s build tooling <a href="https://tailwindcss.com/docs/adding-custom-styles#using-arbitrary-values">lets you define new classes on the fly in <span class="sc">HTML</span></a>. This can be relatively harmless like defining a one-off margin length. Or it could be like above with, <code>sm:py-[calc(theme(spacing[1.5])-1px)]</code> where you’re involving media queries, accessing Tailwind’s theme values, then doing math to make a one-off length and OK now admit we’re just writing <span class="sc">CSS</span> but doing so very awkwardly. They’re committed to the bit, though, I’ll give them that. If you were hoping Tailwind would force your developers to stick to the design system, they won’t. (I didn’t.)</p>
  224. <p>Second, Tailwind’s built-in solution to address the class landfill and compact your giant class heaps into something easier to consume (<a href="https://tailwindcss.com/docs/reusing-styles#extracting-classes-with-apply"><code>@apply</code></a>) is both <a href="https://tailwindcss.com/docs/reusing-styles#avoiding-premature-abstraction">clearly not recommended</a> and also a frankly weird mishmash of nonstandard <span class="sc">CSS</span> and Tailwind-ese. It reeks of “we added this to address complaints from the haterz, but only under duress.” I wouldn’t touch it.</p>
  225. <p>So, yeah, Tailwind: it’s kinda pleasant, while occasionally being an extraordinary pain in the ass.</p>
  226. <p>Meanwhile, in the past couple years since Tailwind rolled around, I’d say writing <span class="sc">CSS</span> is now also kinda pleasant, and while it also can occasionally be a pain in the ass, it’s far less frequent than it used to be.</p>
  227. <p>So to tie things back to Michael Pollan, here’s where I land on the incredibly complicated and confusing question of how we humans should style documents and applications on the web at scale:</p>
  228. <p><strong>Write CSS. Not too much. Mostly scoped.</strong></p>
  229. <p>I’ll break that down.</p>
  230. <p>For styling, you should strive to write code that is recognizably <span class="sc">CSS</span>.</p>
  231. <p>You don’t need to know everything there is to know about <span class="sc">CSS</span>, but what you do learn will be portable and future-proof.</p>
  232. <p>If you’re currently using Tailwind and like it, that’s really fine. I get it.</p>
  233. <p>However, you might be kinda shocked at what <span class="sc">CSS</span> looks like these days.</p>
  234. <p>For pete’s sake, <a href="https://www.joshwcomeau.com/css/center-a-div/">centering things is easy</a>. Has been for years.</p>
  235. <p><a href="https://moderncss.dev/equal-height-elements-flexbox-vs-grid/">You have multiple ways to equalize the heights of items in a row</a>.</p>
  236. <p><a href="https://dev.to/akhilarjun/one-line-sticky-header-using-css-5gp3">Elements that stay “stuck” to the viewport take two lines of code</a>.</p>
  237. <p>Hell, <a href="https://rachelandrew.co.uk/archives/2023/12/19/align-content-in-block-layout/">real soon now you won’t even need grid or flexbox to center things vertically</a>.</p>
  238. <p>Card demo authors rejoice, <a href="https://developer.mozilla.org/en-US/blog/getting-started-with-css-container-queries/">because you can respond to an element’s container, not just the viewport</a>.</p>
  239. <p>Really, skim through all of <a href="https://moderncss.dev">these modern solutions to old problems</a>. I have personally had to work around literally all of them, so it struck a lot of nerves.</p>
  240. <p>CSS variables, <span class="sc">AKA</span> custom properties, are universally supported. You can <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties">learn the basic syntax</a> in like 5 minutes and <a href="https://moderncss.dev/how-custom-property-values-are-computed/">learn some of the subtleties</a> in under an hour and then you’re set for life.</p>
  241. <p>They’re great for consuming design system tokens, and with inheritance and the cascade and the ability to use them in functions and shorthands you can really cut down on the amount of new <span class="sc">CSS</span> you have to write.</p>
  242. <pre><code class="language-css"><span class="hljs-selector-pseudo">:root</span> {
  243. <span class="hljs-attr">--page-background-color</span>: white;
  244. <span class="hljs-attr">--text-color</span>: black;
  245. <span class="hljs-attr">--page-margin</span>: <span class="hljs-number">0.5rem</span>;
  246. }
  247. <span class="hljs-comment">/* adjust colors based on dark/light preference */</span>
  248. <span class="hljs-keyword">@media</span> (<span class="hljs-attribute">prefers-color-scheme</span>: dark) {
  249. <span class="hljs-selector-pseudo">:root</span> {
  250. <span class="hljs-attr">--page-background-color</span>: black;
  251. <span class="hljs-attr">--text-color</span>: white;
  252. }
  253. }
  254. <span class="hljs-keyword">@media</span> screen <span class="hljs-keyword">and</span> (<span class="hljs-attribute">min-width</span>: <span class="hljs-number">50rem</span>) {
  255. <span class="hljs-comment">/* increased margins on wider screens */</span>
  256. <span class="hljs-selector-pseudo">:root</span> {
  257. <span class="hljs-attr">--page-margin</span>: <span class="hljs-number">2rem</span>;
  258. }
  259. }
  260. <span class="hljs-comment">/* use the properties once! */</span>
  261. <span class="hljs-selector-tag">body</span> {
  262. <span class="hljs-attribute">background-color</span>: <span class="hljs-built_in">var</span>(--page-background);
  263. <span class="hljs-attribute">color</span>: <span class="hljs-built_in">var</span>(--text-color);
  264. <span class="hljs-attribute">margin</span>: <span class="hljs-built_in">var</span>(--page-margin);
  265. }
  266. </code></pre>
  267. <p>This is just scratching the surface. Check out <a href="https://css-irl.info/7-uses-for-css-custom-properties/">Michelle Barker’s article for more uses for custom properties</a>, <a href="https://ishadeed.com/article/practical-css-variables/">other ideas from Ahmad Shadeed</a>, or a <a href="https://lea0.verou.me/tag/css-variables/">whole pile of articles from Lea Verou</a>.</p>
  268. <p><a href="https://css-irl.info/css-nesting-is-here/">For real</a>, you can just write this in regular <span class="sc">CSS</span> and it works.</p>
  269. <pre><code class="language-css"><span class="hljs-selector-class">.body-copy</span> {
  270. <span class="hljs-attribute">font-family</span>: <span class="hljs-built_in">var</span>(--font-family, serif);
  271. <span class="hljs-comment">/*
  272. equivalent to .body-copy &gt; * + *
  273. */</span>
  274. &gt; * + * {
  275. <span class="hljs-attribute">margin-block-start</span>: <span class="hljs-number">1em</span>;
  276. <span class="hljs-attribute">max-width</span>: <span class="hljs-number">80ch</span>;
  277. }
  278. }
  279. </code></pre>
  280. <p>Length math methods like <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/min"><code>min()</code></a>, <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/max"><code>max()</code></a>, <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/clamp"><code>clamp()</code></a> let you implement sophisticated constraints using just <span class="sc">CSS</span>. Check out how <a href="https://utopia.fyi">Utopia</a> lets you build a fluid type and sizing scale with <code>clamp()</code> and zero media queries.</p>
  281. <p>And, while we’re at it, it’s beyond safe to do some math in your stylesheets. The <code>calc()</code> function has been fully supported for years now.</p>
  282. <p>Color, too, has gotten real neat in the past few years with <a href="https://chriscoyier.net/2023/06/06/modern-css-in-real-life/#new-colors">new color spaces</a> and the <code>color-mix()</code> function. <a href="https://www.abeautifulsite.net/posts/better-buttons-with-color-mix-and-custom-properties/">Check out how Cory LaViska builds a button</a> that will automatically compute reasonable hover and active colors from its current color in just a few lines of code.</p>
  283. <p>Finally! You can say “style this, but only if it contains that” with <code>:has()</code> and <a href="https://webkit.org/blog/13096/css-has-pseudo-class/">it is glorious</a>.</p>
  284. <p>Check out <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#component-buttons">how Stephanie Eckles builds a button</a> component that can change its skin to accommodate text, icons, or any combination by checking its own internals with <code>:has()</code>. 👨‍🍳♥️</p>
  285. <p>I have <a href="#use-tooling-that-generates-scoped-css">one major exception</a> which I’ll get in to later.</p>
  286. <p>But in the year two thousand and twenty four you maybe don’t need some of the classic make-<span class="sc">CSS</span>-better tooling anymore. Native support for nesting and variables probably covers most of what you’re doing with SASS or PostCSS, and browsers have moved past prefixed experimental properties, so you can drop Autoprefixer.</p>
  287. <p>Finally, you might not need something to concatenate all your <span class="sc">CSS</span> files if you’re writing way less <span class="sc">CSS</span> in general. Speaking of…</p>
  288. <p>People generally have problems with <span class="sc">CSS</span> when there’s a shitload of it. I like <span class="sc">CSS</span>, but I really do think the key to long-term happiness in a project is to avoid writing more of it — if you can.</p>
  289. <p>Thankfully, new features and modern thinking about utilities and layout have you covered.</p>
  290. <p>The best way I’ve found to avoid writing tons of <span class="sc">CSS</span> is to have a small arsenal of reusable, battle-hardened layout helpers at the ready.</p>
  291. <p>Ideally you should not be writing more <span class="sc">CSS</span> when you have place some elements horizontally. You should instead have a utility class or a component that <em>puts things side-by-side for you</em>, and you should reach for that first.</p>
  292. <p>The actual implementations will depend on your needs, your tooling, and probably on your design system. <a href="/row-stack-space-layout-components">I have an article about thinking about layout in rows, stacks, and spaces</a> that might illuminate things. Or look at <a href="https://every-layout.dev/"><em>Every Layout</em></a> by Heydon Pickering and Andy Bell. Or <a href="https://smolcss.dev">SmolCSS</a> by Stephanie Eckles for some other layout primitives.</p>
  293. <p>Any system!</p>
  294. <p>It’s very easy to rabbit-hole on this, but even a small selection of “approved” fonts, text sizes, spacing sizes, and colors will go a long way towards improving visual consistency and discouraging weird-looking one-off pages or components.</p>
  295. <p>Slinging them around as <a href="#use-css-variables"><span class="sc">CSS</span> variables</a> makes a ton of sense to me.</p>
  296. <pre><code class="language-css"><span class="hljs-selector-pseudo">:root</span> {
  297. <span class="hljs-comment">/* system goes here */</span>
  298. <span class="hljs-attr">--heading-font-family</span>: sans-serif;
  299. <span class="hljs-attr">--heading-line-height</span>: <span class="hljs-number">1.1</span>;
  300. <span class="hljs-attr">--heading-color</span>: green;
  301. }
  302. <span class="hljs-selector-tag">h1</span>,
  303. <span class="hljs-selector-tag">h2</span>,
  304. <span class="hljs-selector-tag">h3</span>,
  305. <span class="hljs-selector-tag">h4</span> {
  306. <span class="hljs-attribute">font-family</span>: <span class="hljs-built_in">var</span>(--heading-font-family);
  307. <span class="hljs-attribute">line-height</span>: <span class="hljs-built_in">var</span>(--heading-line-height);
  308. <span class="hljs-attribute">color</span>: <span class="hljs-built_in">var</span>(--heading-color);
  309. }
  310. </code></pre>
  311. <p>Now, <em>sticking to a system</em> requires actual discipline from both designers and developers, but that’s a whole different problem.</p>
  312. <p>Tailwind proponents do get this right, to a point: it <em>is goddamned delightful</em> to slap together a simple component or page with a few utility classes.</p>
  313. <p>They’re undeniably handy, but don’t write one for every single <span class="sc">CSS</span> property.</p>
  314. <p>Here’s my personal guidelines for when to write a utility class. First, <strong>is it some styling you’re writing all the time</strong>? Second, <strong>is the class name extremely obvious</strong>? If so, go for it. Otherwise, tread carefully.</p>
  315. <p>As an example, you’re probably making components that have a wrapper element, but all that wrapper element is really doing is serving as a <code>position: relative</code> container for children.</p>
  316. <pre><code class="language-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"some-dropdown-wrapper-with-relative-positioning"</span>&gt;</span>
  317. <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"absolutely-positioned-dropdown"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  318. <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  319. </code></pre>
  320. <p>This is a spot where some single-property utility classes let you apply common properties quickly, spare you from having to think of a good class name for the wrapper, and the names are <span class="sc">IMO</span> obvious because they’re discrete keywords of a property:</p>
  321. <pre><code class="language-html"><span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="language-css">
  322. <span class="hljs-comment">/* in your global stylesheet */</span>
  323. <span class="hljs-selector-class">.relative</span> {
  324. <span class="hljs-attribute">position</span>: relative;
  325. }
  326. <span class="hljs-selector-class">.absolute</span> {
  327. <span class="hljs-attribute">position</span>: absolute;
  328. }
  329. </span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>
  330. <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"relative"</span>&gt;</span>
  331. <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"absolute"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  332. <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  333. </code></pre>
  334. <p>I also have utilities for accessibility or layout annoyances that I end up using on every project ever, like these:</p>
  335. <pre><code class="language-css"><span class="hljs-selector-class">.nowrap</span>,
  336. <span class="hljs-selector-class">.no-wrap</span> <span class="hljs-comment">/* lol I can never keep this straight */</span> {
  337. <span class="hljs-attribute">white-space</span>: nowrap;
  338. }
  339. <span class="hljs-selector-class">.screen-reader-only</span> {
  340. <span class="hljs-attribute">border</span>: <span class="hljs-number">0</span>;
  341. <span class="hljs-attribute">clip</span>: <span class="hljs-built_in">rect</span>(<span class="hljs-number">0</span> <span class="hljs-number">0</span> <span class="hljs-number">0</span> <span class="hljs-number">0</span>);
  342. <span class="hljs-attribute">height</span>: <span class="hljs-number">1px</span>;
  343. <span class="hljs-attribute">margin</span>: -<span class="hljs-number">1px</span>;
  344. <span class="hljs-attribute">overflow</span>: hidden;
  345. <span class="hljs-attribute">padding</span>: <span class="hljs-number">0</span>;
  346. <span class="hljs-attribute">position</span>: absolute;
  347. <span class="hljs-attribute">width</span>: <span class="hljs-number">1px</span>;
  348. }
  349. </code></pre>
  350. <p>Or properties derived from your design system, but now names are harder:</p>
  351. <pre><code class="language-css"><span class="hljs-selector-class">.font-primary</span> {
  352. <span class="hljs-attribute">font-family</span>: <span class="hljs-string">'Heliotrope'</span>, Optima, sans-serif;
  353. }
  354. <span class="hljs-selector-class">.font-monospace</span> {
  355. <span class="hljs-attribute">font-family</span>: <span class="hljs-string">'Mono Lisa'</span>, monospace;
  356. }
  357. <span class="hljs-selector-class">.fs-0</span> {
  358. <span class="hljs-attribute">font-size</span>: <span class="hljs-built_in">var</span>(--step-<span class="hljs-number">0</span>);
  359. }
  360. <span class="hljs-selector-class">.fs-1</span> {
  361. <span class="hljs-attribute">font-size</span>: <span class="hljs-built_in">var</span>(--step-<span class="hljs-number">1</span>);
  362. }
  363. <span class="hljs-selector-class">.fs-2</span> {
  364. <span class="hljs-attribute">font-size</span>: <span class="hljs-built_in">var</span>(--step-<span class="hljs-number">2</span>);
  365. }
  366. <span class="hljs-comment">/* and so on */</span>
  367. </code></pre>
  368. <p>I do like to have resets like <code>.p-0</code> or <code>.m-0</code> to zero out padding and margin in a pinch, but not more than that. With layout utilities, I try to avoid thinking about padding and margin directly.</p>
  369. <p>I get antsy writing utility classes for colors. Which color? Background? Text? Border? Outline? Are they also going to handle changes like hover, or dark mode? Should they? Are you going to account for tints and shades? Are there going to be so many that it’s hard to keep them straight? At that point, it’s easier to just write the <span class="sc">CSS</span> for whatever component I’m dealing with.</p>
  370. <p>All this to say: keep the selection not-too-big: utility classes lose a lot of their power if you can’t keep most of the ones available to you in your noggin <span class="sc">RAM</span>.</p>
  371. <p>But you would be stunned at how much you can build with a small handful of <a href="#build-some-layout-helpers">layout components</a> and a couple of classes that let you access bits of your design system. That’s really the sweet spot you should be aiming for.</p>
  372. <p>When one-offs or deviations from the system arise — which they absolutely will — there are a couple more tricks to keep new <span class="sc">CSS</span> to a minimum…</p>
  373. <p>All browsers now support the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:is"><code>:is</code></a> and <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:where"><code>:where</code></a> pseudo-classes.</p>
  374. <p>Both are great for writing less verbose <span class="sc">CSS</span>. The classic example is combining <code>:hover</code>/<code>:focus</code> selectors into single selectors like so:</p>
  375. <pre><code class="language-css"><span class="hljs-selector-tag">a</span> {
  376. <span class="hljs-attribute">color</span>: purple;
  377. }
  378. <span class="hljs-selector-tag">a</span><span class="hljs-selector-pseudo">:is</span>(<span class="hljs-selector-pseudo">:hover</span>, <span class="hljs-selector-pseudo">:focus</span>) {
  379. <span class="hljs-attribute">color</span>: blue;
  380. }
  381. </code></pre>
  382. <p>The zero specificity of <code>:where()</code> is amazing for keeping the specificity of your selectors as low as possible, which makes overrides — should you need them — a lot easier. But if you’re having specificity problems, <a href="#mostly-scoped">you might want to look at scoping</a>.</p>
  383. <p>It’s not your enemy! Learn <a href="https://web.dev/learn/css/inheritance#which_properties_are_inherited_by_default">what properties inherit</a>, and set them as high as you possibly can.</p>
  384. <p>One classic example is font-family. If you’ve got a primary font, set that on the <code>body</code> and everything will inherit it, and you don’t write <code>font-family</code> ever again.</p>
  385. <pre><code class="language-css"><span class="hljs-selector-tag">body</span> {
  386. <span class="hljs-attribute">font-family</span>: sans-serif;
  387. }
  388. <span class="hljs-comment">/* ahem these don't inherit by default */</span>
  389. <span class="hljs-selector-tag">button</span>,
  390. <span class="hljs-selector-tag">input</span>,
  391. select,
  392. <span class="hljs-selector-tag">textarea</span> {
  393. <span class="hljs-attribute">font</span>: inherit;
  394. }
  395. </code></pre>
  396. <p>You might not have to think of more classes if you’re adding additional markup for accessibility. You can latch on to those attributes that you’re adding anyway. <a href="https://adrianroselli.com/2021/06/using-css-to-enforce-accessibility.html">Adrian Roselli has a big roundup of ideas</a>.</p>
  397. <pre><code class="language-css"><span class="hljs-comment">/* good if you're doing this anyway */</span>
  398. <span class="hljs-selector-tag">textarea</span><span class="hljs-selector-attr">[aria-invalid=<span class="hljs-string">'true'</span>]</span> {
  399. <span class="hljs-attr">--border-color</span>: red;
  400. }
  401. </code></pre>
  402. <p>If you’re up to speed with modern <span class="sc">CSS</span> and writing way less of it and you’re still struggling, I’m willing to bet that your issue is managing the scope or reach of your selectors.</p>
  403. <p>Maybe your selectors are targeting more elements than you intended, or you have conflicts when you combine components. This is where you really start fighting with specificity and selector ordering and get to thinking that <span class="sc">CSS</span> is stupid as hell.</p>
  404. <p>Scoping your <span class="sc">CSS</span> more-or-less solves all these problems for you, and modern browsers and tooling make this increasingly accessible.</p>
  405. <p>Although I <a href="#see-if-you-can-go-without-build-tooling">mentioned above</a> that you can live without a lot of <span class="sc">CSS</span> tooling, if you can take advantage of something that automatically writes styles scoped to a component’s markup, do it.</p>
  406. <p>Because these work by slightly altering and (typically) increasing the specificity of your selectors, there’s <em>sometimes</em> subtleties and gotchas, but these things help you sidestep so many other <span class="sc">CSS</span> authoring issues that they’re absolutely worth it: they let you write simple selectors with very low specificity, they keep your styles from “leaking” out of the component, and they’re easier to work with than all the subtleties of the Shadow <span class="sc">DOM</span>.</p>
  407. <p>The lightest possible thing I know of is <a href="https://www.11ty.dev/docs/languages/webc/">the WebC template language</a> and <a href="https://www.11ty.dev/docs/languages/webc/#webcscoped">its <code>webc:scoped</code> mechanism</a>. The <a href="https://svelte.dev">Svelte</a> framework also has <a href="https://geoffrich.net/posts/svelte-scoped-styles/">built-in, default support for scoped styles</a>, as does <a href="https://vuejs.org/api/sfc-css-features.html">Vue</a>.</p>
  408. <p>If you’re using React or some other web framework that forgot that styles are a thing, CSS-in-JS libraries like <a href="https://styled-components.com/"><code>styled-components</code></a> or <a href="https://emotion.sh/docs/introduction"><code>emotion</code></a> generally output scoped styles.</p>
  409. <p>Or look into <a href="https://github.com/css-modules/css-modules?tab=readme-ov-file"><span class="sc">CSS</span> modules</a>, where you import <code>.css</code> files in your scripts. It’s not a standard, but <a href="https://github.com/css-modules/css-modules/blob/master/docs/get-started.md">lots of build pipelines and frameworks</a> let you do it.</p>
  410. <p>What if I told you that <span class="sc">CSS</span> now has a native, globally supported way to <em>reduce</em> specificity and effectively <em>reorder</em> entire blocks of code or imported files? <a href="https://12daysofweb.dev/2022/cascade-layers/">Meet cascade layers</a>. These still scramble my brain a little bit, but theme and library authors in particular should be looking real hard at this right now. Shipped in all browsers two years ago.</p>
  411. <p>If you’re working in web components <span class="sc">aka</span> custom elements, the Shadow <span class="sc">DOM</span> lets you write styles that are scoped to the component’s generated, isolated markup. This is a whole thing and comes <a href="https://www.matuzo.at/blog/2023/pros-and-cons-of-shadow-dom/">with a whole heap of caveats</a>, but it’s standardized and available without extra tooling. <a href="https://blog.openreplay.com/shadow-dom--the-ultimate-guide/">This introduction to the shadow <span class="sc">DOM</span> is pretty solid.</a> If you’re really interested in web components, <a href="https://thomaswilburn.github.io/wc-book/index.html">Thomas Wilburn’s book about them</a> is the best practical overview I’ve read.</p>
  412. <p>This is Chromium-only right now, but <code>@scope</code> lets you write boundaries for selectors, either <a href="https://developer.chrome.com/docs/css-ui/at-scope#introducing_scope">in code</a> or <a href="https://developer.chrome.com/docs/css-ui/at-scope#prelude-less_scope">based on the parent of the style tag</a>. <a href="https://fullystacked.net/scope-in-css/">Ollie Williams</a> and <a href="https://keithjgrant.com/posts/2023/04/scoped-css-is-back/">Keith J. Grant</a> have good introductions here.</p>
  413. <p>If you’re not ready for things like cascade layers or you can’t adopt additional tooling, the battle-proven mental frameworks like <a href="https://getbem.com/introduction/">BEM</a> are and were popular for a reason. You’ve gotta get everyone on board and (ugh) think of names for things, but the broad guidelines are sound: writing modular code with low specificity class selectors can definitely help you avoid big swaths of <span class="sc">CSS</span>-at-scale problems.</p>
  414. <p><a href="https://twitter.com/aardrian/status/1164956147499053056">Maybe do your best to keep the class names shortish, though, mmm-kay?</a> 😉</p>
  415. <p>So, yeah, I’ve been at this for a while, and this is the answer: <strong>Write <span class="sc">CSS</span>. Not too much. Mostly scoped.</strong></p>
  416. <p>I’m sure we’ll never argue about how to manage <span class="sc">CSS</span> ever again.</p>
  417. </article>
  418. <hr>
  419. <footer>
  420. <p>
  421. <a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
  422. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
  423. </svg> Accueil</a> •
  424. <a href="/david/log/" title="Accès au flux RSS"><svg class="icon icon-rss2">
  425. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-rss2"></use>
  426. </svg> Suivre</a> •
  427. <a href="http://larlet.com" title="Go to my English profile" data-instant><svg class="icon icon-user-tie">
  428. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-user-tie"></use>
  429. </svg> Pro</a> •
  430. <a href="mailto:david%40larlet.fr" title="Envoyer un courriel"><svg class="icon icon-mail">
  431. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-mail"></use>
  432. </svg> Email</a> •
  433. <abbr class="nowrap" title="Hébergeur : Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33184162340"><svg class="icon icon-hammer2">
  434. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-hammer2"></use>
  435. </svg> Légal</abbr>
  436. </p>
  437. <template id="theme-selector">
  438. <form>
  439. <fieldset>
  440. <legend><svg class="icon icon-brightness-contrast">
  441. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-brightness-contrast"></use>
  442. </svg> Thème</legend>
  443. <label>
  444. <input type="radio" value="auto" name="chosen-color-scheme" checked> Auto
  445. </label>
  446. <label>
  447. <input type="radio" value="dark" name="chosen-color-scheme"> Foncé
  448. </label>
  449. <label>
  450. <input type="radio" value="light" name="chosen-color-scheme"> Clair
  451. </label>
  452. </fieldset>
  453. </form>
  454. </template>
  455. </footer>
  456. <script src="/static/david/js/instantpage-5.1.0.min.js" type="module"></script>
  457. <script>
  458. function loadThemeForm(templateName) {
  459. const themeSelectorTemplate = document.querySelector(templateName)
  460. const form = themeSelectorTemplate.content.firstElementChild
  461. themeSelectorTemplate.replaceWith(form)
  462. form.addEventListener('change', (e) => {
  463. const chosenColorScheme = e.target.value
  464. localStorage.setItem('theme', chosenColorScheme)
  465. toggleTheme(chosenColorScheme)
  466. })
  467. const selectedTheme = localStorage.getItem('theme')
  468. if (selectedTheme && selectedTheme !== 'undefined') {
  469. form.querySelector(`[value="${selectedTheme}"]`).checked = true
  470. }
  471. }
  472. const prefersColorSchemeDark = '(prefers-color-scheme: dark)'
  473. window.addEventListener('load', () => {
  474. let hasDarkRules = false
  475. for (const styleSheet of Array.from(document.styleSheets)) {
  476. let mediaRules = []
  477. for (const cssRule of styleSheet.cssRules) {
  478. if (cssRule.type !== CSSRule.MEDIA_RULE) {
  479. continue
  480. }
  481. // WARNING: Safari does not have/supports `conditionText`.
  482. if (cssRule.conditionText) {
  483. if (cssRule.conditionText !== prefersColorSchemeDark) {
  484. continue
  485. }
  486. } else {
  487. if (cssRule.cssText.startsWith(prefersColorSchemeDark)) {
  488. continue
  489. }
  490. }
  491. mediaRules = mediaRules.concat(Array.from(cssRule.cssRules))
  492. }
  493. // WARNING: do not try to insert a Rule to a styleSheet you are
  494. // currently iterating on, otherwise the browser will be stuck
  495. // in a infinite loop…
  496. for (const mediaRule of mediaRules) {
  497. styleSheet.insertRule(mediaRule.cssText)
  498. hasDarkRules = true
  499. }
  500. }
  501. if (hasDarkRules) {
  502. loadThemeForm('#theme-selector')
  503. }
  504. })
  505. </script>
  506. </body>
  507. </html>