<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>lowmess</title><description>My name is Alec Lomas, and I make websites</description><link>https://lowmess.com/</link><item><title>My Interop 2027 Picks</title><link>https://lowmess.com/blog/interop-2027-picks/</link><guid isPermaLink="true">https://lowmess.com/blog/interop-2027-picks/</guid><description>Each year, the The Interop Project asks developers which features to focus on. Here are my picks for the coming year.</description><pubDate>Sat, 12 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Each year, &lt;a href=&quot;https://github.com/web-platform-tests/interop&quot;&gt;The Interop Project&lt;/a&gt; chooses web features and APIs to focus on delivering complete implementations of over the coming year. The goal is to create a stable, targetable platform that developers can be confident in regardless of user browser choice. For the last few years, the team has asked the broader community to weigh in on which features would make the most impact for them. Inspired by &lt;a href=&quot;https://www.alwaystwisted.com/articles/and-thats-my-rank-2027-edition&quot;&gt;Stuart Robson’s&lt;/a&gt; blog post doing the same, I’m sharing the features I picked for next year in &lt;a href=&quot;https://developer-signals.rviscomi.workers.dev/&quot;&gt;the developer survey&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;temporal&quot;&gt;&lt;code&gt;Temporal&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Date&lt;/code&gt; sucks. We all know it. It’s a giant pain to work with, and causes untold headaches when you can’t be certain where it will be created, manipulated, or formatted for display. Thankfully, &lt;code&gt;Temporal&lt;/code&gt; fixes all that &lt;a href=&quot;https://tc39.es/proposal-temporal/docs/cookbook.html&quot;&gt;while adding functionality typically provided by expensive third-party libraries&lt;/a&gt;. Unfortunately, it is not currently supported in Safari – though it is already in the Technical Preview build. This might have been a “wasted” vote given that, but I need this to land as soon as possible for mental health reasons.&lt;/p&gt;
&lt;p&gt;Recommended reading: &lt;a href=&quot;https://piccalil.li/blog/date-is-out-and-temporal-is-in/&quot;&gt;“Date is out, Temporal is in” by Mat Marquis&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;customizable-select&quot;&gt;Customizable &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;There is probably no individual HTML element I have argued about more than &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt;. I’m of the opinion that the thousands and thousands of hours that have been poured into creating JavaScript-based &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt;-like elements just to have a slightly more visually cohesive dropdown (often at the expense of usability) were a waste, to say the least. So fully stylable &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt;s were already going to be a win, even before they tacked on even more incredible functionality. Bring that same love to &lt;code&gt;&amp;lt;datalist&amp;gt;&lt;/code&gt; and we could eliminate entire UI libraries.&lt;/p&gt;
&lt;p&gt;Recommended reading: &lt;a href=&quot;https://css-tricks.com/abusing-customizable-selects/&quot;&gt;“Abusing Customizable Selects” by Patrick Brosset&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;attr&quot;&gt;&lt;code&gt;attr()&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;attr()&lt;/code&gt; has long been a handy way to pass content from HTML to CSS psuedo-elements. But there’s a whole new slate of functionality coming soon: it can be used in all properties (not just &lt;code&gt;content&lt;/code&gt;), typed for its use, and have fallback values. This opens up all sorts of possibilities, but for me this is primarily an avenue towards removing tons of inline styles used only to set custom properties to achieve this same purpose.&lt;/p&gt;
&lt;p&gt;Recommended reading: &lt;a href=&quot;https://una.im/advanced-attr&quot;&gt;“New capabilities for &lt;code&gt;attr()&lt;/code&gt;” by Una Kravets&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;interpolate-size--calc-size&quot;&gt;&lt;code&gt;interpolate-size&lt;/code&gt; &amp;amp; &lt;code&gt;calc-size()&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;My favorite part of delivering any feature is the last 5% of polish to make it really feel satisfying to use. A lot of times, that comes down to subtle animations for state changes. &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@starting-style&quot;&gt;&lt;code&gt;@starting-style&lt;/code&gt;&lt;/a&gt; and &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/sibling-index&quot;&gt;&lt;code&gt;sibling-index&lt;/code&gt;&lt;/a&gt; have already gone a long way to removing some of the plumbing, but I am very excited to never again write JavaScript to calculate an element’s expanded size before animating it in.&lt;/p&gt;
&lt;p&gt;Recommended reading: &lt;a href=&quot;https://developer.chrome.com/docs/css-ui/animate-to-height-auto&quot;&gt;“Animate to &lt;code&gt;height: auto;&lt;/code&gt; (and other intrinsic sizing keywords) in CSS” by Bramus Van Damme&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;function&quot;&gt;&lt;code&gt;@function&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Mentally I am including a bevy of new built-in support functions here: &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/if&quot;&gt;&lt;code&gt;if()&lt;/code&gt;&lt;/a&gt;, &lt;code&gt;style()&lt;/code&gt;, and &lt;code&gt;color-scheme()&lt;/code&gt; to name a few. Combining these with custom functions, I think the way we write CSS is going to dramatically change in the next few years. I am looking forward to never writing &lt;code&gt;calc(&amp;lt;value&amp;gt; * -1);&lt;/code&gt; more than once per project ever again.&lt;/p&gt;
&lt;p&gt;Recommended reading: &lt;a href=&quot;https://www.bram.us/2025/09/30/css-custom-light-dark/&quot;&gt;“A custom &lt;code&gt;--light-dark()&lt;/code&gt; function in CSS that works with any type of value &lt;em&gt;(not just colors!)&lt;/em&gt; in just 3 LOC” by Bramus Van Damme&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;interest-invokers&quot;&gt;Interest Invokers&lt;/h2&gt;
&lt;p&gt;I’m always excited to ship less JavaScript, especially without a degredation of functionality or customizability. Interest invokers allow us to create popovers (tooltips, those sweet preview cards on Wikipedia you get when you hover over a link, those sorts of things) and display them to the user with no JavaScript involved whatsoever. Even more they allow us to delay the entry and exit of those popovers for graceful recovery.&lt;/p&gt;
&lt;p&gt;Recommended reading: &lt;a href=&quot;https://css-tricks.com/a-first-look-at-the-interest-invoker-api-for-hover-triggered-popovers/&quot;&gt;“A First Look at the Interest Invoker API (for Hover-Triggered Popovers)” by Daniel Schwarz&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;container-scroll-state-queries&quot;&gt;Container &lt;code&gt;scroll-state()&lt;/code&gt; Queries&lt;/h2&gt;
&lt;p&gt;I have had the unfortunate experience of creating several monstrous CSS classes that hack together a “hey this container has scrollable content” affordance. They work, but are never very adaptable and are always extremely hard to reason about. &lt;code&gt;@container scroll-state(scrollable: block-end)&lt;/code&gt; is much clearer. The number of times I’ve wished for &lt;code&gt;scroll-state(stuck)&lt;/code&gt; is also immeasurable.&lt;/p&gt;
&lt;p&gt;Recommended reading: &lt;a href=&quot;https://utilitybend.com/blog/is-it-scrolled-is-it-not-lets-find-out-with-css-container-scroll-state-queries&quot;&gt;“Is it scrolled? Is it not? Let’s find out with CSS &lt;code&gt;@container scroll-state()&lt;/code&gt; queries” by Brecht De Ruyte&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;gap-decorations&quot;&gt;&lt;code&gt;gap&lt;/code&gt; Decorations&lt;/h2&gt;
&lt;p&gt;While there are lots of applications for &lt;code&gt;gap&lt;/code&gt; decorations, I’m most excited for them to share one specific bugbear: ghost separators for wrapped &lt;code&gt;flex&lt;/code&gt; children. Right now, there is no way to tell if an item is the first or last child of a row when it has wrapped, so if you are rendering an HTML element (psuedo- or no) to separate items, it will always show. Once we’re able to style the space between &lt;em&gt;rows&lt;/em&gt; rather than the space between &lt;em&gt;elements&lt;/em&gt;, this problem disappears entirely.&lt;/p&gt;
&lt;p&gt;Recommended reading: &lt;a href=&quot;https://blogs.windows.com/msedgedev/2025/03/19/minding-the-gaps-a-new-way-to-draw-separators-in-css/&quot;&gt;“Minding the gaps: A new way to draw separators in CSS” by Kevin Babbitt &amp;amp; Patrick Brosset&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;anchor-positioning&quot;&gt;Anchor Positioning&lt;/h2&gt;
&lt;p&gt;Gotta be honest: this one is purely selfish. The little underline in the header navigation on this site is a single anchor-positioned element where the anchor scope changes based on &lt;code&gt;:hover&lt;/code&gt;/active state. And it technically works in all major browser engines…but Chromium is the only one that will animate it. Other than that I have not found a single issue with anchor positioning between browsers. But my animation!&lt;/p&gt;
&lt;p&gt;Recommended reading: &lt;a href=&quot;https://bsky.app/profile/kevinpowell.co/post/3mu5l7cav4s2a&quot;&gt;this Bluesky post from Kevin Powell that I &lt;del&gt;stole the technique from&lt;/del&gt; used as reference&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>Generating Open Graph Images With Astro &amp; Satori</title><link>https://lowmess.com/blog/og-images-astro-satori/</link><guid isPermaLink="true">https://lowmess.com/blog/og-images-astro-satori/</guid><description>Open Graph images are a great way to make your post stand out on social media, and generating them has never been easier.</description><pubDate>Wed, 28 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Several eons ago, when I cared about these things, I wanted to update all of the headers across my various social media accounts to use the same design. Because each platform wanted slightly different sizes this was quite annoying to do manually in design software. Luckily I already knew a tool that could adapt to the various sizes each platform demanded: HTML &amp;amp; CSS. So I hacked together &lt;a href=&quot;https://github.com/lowmess/herohero&quot;&gt;a tool&lt;/a&gt; to abuse the then-new Puppeteer library to take screenshots of a given HTML page at the required sizes. And it worked great!&lt;/p&gt;
&lt;p&gt;I’m far from the only person to think the flexibility of these technologies makes them perfect for dynamic imaging. When Vercel ran into similar issues generating Open Graph images at scale &lt;a href=&quot;https://vercel.com/blog/introducing-vercel-og-image-generation-fast-dynamic-social-card-images&quot;&gt;they created a whole library about it&lt;/a&gt;. When I was looking to do the same for this site, combining Astro’s &lt;a href=&quot;https://docs.astro.build/en/guides/endpoints/&quot;&gt;endpoints&lt;/a&gt; feature with &lt;a href=&quot;https://github.com/vercel/satori&quot;&gt;that library&lt;/a&gt; seemed like an ergonomic option. It turned out to be even better than I thought. (And, before we get too far, some folks do still go the &lt;a href=&quot;https://www.emgoto.com/astro-social-card/&quot;&gt;Puppeteer&lt;/a&gt; &lt;a href=&quot;https://cassidoo.co/post/og-image-gen-astro/&quot;&gt;route&lt;/a&gt;!)&lt;/p&gt;
&lt;h2 id=&quot;first-off-whats-an-open-graph-image&quot;&gt;First Off, What’s an Open Graph Image?&lt;/h2&gt;
&lt;p&gt;You know when you paste a link into some app and seems to magically pull in a description and image for that page? That’s all powered by something called the &lt;a href=&quot;https://ogp.me/&quot;&gt;Open Graph protocol&lt;/a&gt;. Originally developed by Meta to enhance links shared to (and from) Facebook, it has since become the defacto standard for displaying information in link previews.&lt;/p&gt;
&lt;h2 id=&quot;and-were-generating-these-images-how&quot;&gt;And We’re Generating These Images How?&lt;/h2&gt;
&lt;p&gt;First things first, I am going to assume you already have an Astro site set up. Second things second, we need to install some dependencies to get this to work:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes Alabaster Rubber&quot; style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF;--shiki-light-bg:#F7F7F7;--shiki-dark-bg:#21252B;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;npm&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt; i&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt; satori&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt; react&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt; sharp&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Satori renders JSX to SVG. React is needed for Satori’s layout engine. Sharp is used to take the resulting SVG and transform it into a PNG. You could also use &lt;a href=&quot;https://github.com/thx/resvg-js&quot;&gt;&lt;code&gt;resvg-js&lt;/code&gt;&lt;/a&gt; for this last part, but I already had Sharp installed, so that’s what my examples will use. If you use &lt;a href=&quot;https://docs.astro.build/en/guides/images/#astro-components-for-images&quot;&gt;Astro’s image components&lt;/a&gt; you very likely already have Sharp installed as well. Because this is Astro, unless you explicitly set out to make a React component that uses Satori and Sharp on the client for some reason, none of these dependencies will be shipped to the client.&lt;/p&gt;
&lt;h3 id=&quot;making-a-helper&quot;&gt;Making a Helper&lt;/h3&gt;
&lt;p&gt;On this site I have two templates for images I want to show. For blog posts, I want to include the title and date for the post. For every other page I want to include a generic image that has the same general vibe, but with the site’s tagline. That being the case it makes sense to separate template rendering from template definitions.&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes Alabaster Rubber&quot; style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF;--shiki-light-bg:#F7F7F7;--shiki-dark-bg:#21252B;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;tsx&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;import &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; type ReactNode &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; from &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;react&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;import satori from &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;satori&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;import sharp from &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;sharp&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;export async function &lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;generateOgImage&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;template: &lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;ReactNode&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;	const svg = await &lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;satori&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;(template&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;		width&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#7A3E9D;--shiki-dark:#BF79C3&quot;&gt; 1200&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;		height&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#7A3E9D;--shiki-dark:#BF79C3&quot;&gt; 630&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;		fonts&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; []&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;	}&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;	return await &lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;sharp&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;(Buffer&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;(svg))&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;png&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;toBuffer&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A few questions might pop out here.&lt;/p&gt;
&lt;h4 id=&quot;why-1200x630&quot;&gt;Why 1200x630?&lt;/h4&gt;
&lt;p&gt;Very good question. I do not know. But it is &lt;a href=&quot;https://ogpreview.app/guides/og-image-sizes&quot;&gt;the most common recommendation&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id=&quot;why-return-a-buffer&quot;&gt;Why Return a &lt;code&gt;Buffer&lt;/code&gt;?&lt;/h4&gt;
&lt;p&gt;Since we will be &lt;a href=&quot;#okay-but-how-do-i-put-these-on-my-website&quot;&gt;returning these from an endpoint&lt;/a&gt;, we need to encode the information in a way that will fit into a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Response&quot;&gt;&lt;code&gt;Response&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id=&quot;why-is-fonts-empty&quot;&gt;Why is &lt;code&gt;fonts&lt;/code&gt; Empty?&lt;/h4&gt;
&lt;p&gt;We actually need to add some stuff there, but it depends on whether you are using third-party or local fonts (or, I suppose, some combination of the two). If you are loading your fonts from Google or another third-party source, you should &lt;code&gt;fetch&lt;/code&gt; the file and turn it into an &lt;code&gt;ArrayBuffer&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes Alabaster Rubber has-diff&quot; style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF;--shiki-light-bg:#F7F7F7;--shiki-dark-bg:#21252B;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;tsx&quot;&gt;&lt;code&gt;&lt;span class=&quot;line diff add&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;const myFetchedFont = await &lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;fetch&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;https://example.com/font-file.woff&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line diff add&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;const myFetchedFontData = await myFetchedFont&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;arrayBuffer&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;export async function &lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;generateOgImage&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;template: &lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;ReactNode&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;	const svg = await &lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;satori&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;(template&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;		width&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#7A3E9D;--shiki-dark:#BF79C3&quot;&gt; 1200&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;		height&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#7A3E9D;--shiki-dark:#BF79C3&quot;&gt; 630&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line diff add&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;		fonts&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; [ &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line diff add&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;			{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line diff add&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;				name&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;MyFetchedFont&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line diff add&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;				data&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; myFetchedFontData&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line diff add&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;			},&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line diff add&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;		]&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;	}&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;	return await &lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;sharp&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;(Buffer&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;(svg))&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;png&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;toBuffer&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For local fonts, &lt;code&gt;fs.readFileSync&lt;/code&gt; is your friend. Note that the path for the font file should be from the project root, not relative to the helper.&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes Alabaster Rubber has-diff&quot; style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF;--shiki-light-bg:#F7F7F7;--shiki-dark-bg:#21252B;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;tsx&quot;&gt;&lt;code&gt;&lt;span class=&quot;line diff add&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;import fs from &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;fs&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line diff add&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;const myLocalFont = fs&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;readFileSync&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;./src/fonts/my-local-font.woff&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;export async function &lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;generateOgImage&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;template: &lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;ReactNode&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;	const svg = await &lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;satori&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;(template&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;		width&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#7A3E9D;--shiki-dark:#BF79C3&quot;&gt; 1200&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;		height&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#7A3E9D;--shiki-dark:#BF79C3&quot;&gt; 630&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line diff add&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;		fonts&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; [ &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line diff add&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;			{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line diff add&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;				name&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;MyLocalFont&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line diff add&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;				data&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; myLocalFont&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line diff add&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;			},&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line diff add&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;		]&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;	}&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;	return await &lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;sharp&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;(Buffer&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;(svg))&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;png&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;toBuffer&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;cookie-cutting&quot;&gt;Cookie Cutting&lt;/h3&gt;
&lt;p&gt;With the helper in place, we need some templates to render. As I mentioned, I have two, but they share a layout so I actually have three components. This will vary site by site but the general idea is we’re creating functions that return JSX for Satori to render.&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes Alabaster Rubber&quot; style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF;--shiki-light-bg:#F7F7F7;--shiki-dark-bg:#21252B;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;tsx&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;export function &lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;BlogPostOgImage&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;({&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; post &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; post: &lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;BlogPost&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; })&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;	return (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;		&amp;lt;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;div&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;			style=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;{{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;				display&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;flex&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;				width&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;100%&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;				height&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;100%&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;			}}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;		&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;			My post is called &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;post&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;title&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;		&amp;lt;/&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;	)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/vercel/satori#css&quot;&gt;Satori only supports a subset of CSS&lt;/a&gt; and even then only through inline style objects. It’s not the best experience in the world, but it’s also important to remember that these images can be rendered quite small. If it gets cumbersome to edit the template or you’re having issues with your design, it’s usually a sign to simplify.&lt;/p&gt;
&lt;h2 id=&quot;okay-but-how-do-i-put-these-on-my-website&quot;&gt;Okay But How Do I Put These On My Website?&lt;/h2&gt;
&lt;p&gt;Astro is most commonly used to generate HTML pages and their associated CSS and JavaScript. But you can use &lt;a href=&quot;https://docs.astro.build/en/guides/endpoints/&quot;&gt;endpoints&lt;/a&gt; to return any type of data we want. In this case, we want to create an image for each blog post. Let’s create a new route at &lt;code&gt;pages/blog/[slug]/og-image.png.ts&lt;/code&gt; to serve up these images.&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes Alabaster Rubber&quot; style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF;--shiki-light-bg:#F7F7F7;--shiki-dark-bg:#21252B;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;ts&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;import &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; type APIContext &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; from &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;astro&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;import &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; generateOgImage&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; OgBlogPostImage &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; from &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;#utils/og-image.tsx&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;const allPosts = await &lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;getAllPosts&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#AA3731;--shiki-dark:#E06C75&quot;&gt;// Generate a path &amp;amp; return a `GET` endpoint for each post&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;export async function &lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;getStaticPaths&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;	return allPosts&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;map&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;post&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; =&amp;gt; (&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;		params&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; slug&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; post&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;slug &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;},&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;	}&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;))&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;export async function &lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;GET&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;({&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; params &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;APIContext&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;	const &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; slug &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; = params&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;	const post = allPosts&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;find&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;post&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; =&amp;gt; post&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;slug === slug)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#AA3731;--shiki-dark:#E06C75&quot;&gt;	// If we don&amp;#39;t have a post, we don&amp;#39;t have an image&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;	if (!post) &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;		return new &lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;Response&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#7A3E9D;--shiki-dark:#BF79C3&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;			status&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#7A3E9D;--shiki-dark:#BF79C3&quot;&gt; 404&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;			statusText&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;Not found&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;		}&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#AA3731;--shiki-dark:#E06C75&quot;&gt;	// React isn&amp;#39;t in context, so call the template as a normal function&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;	const png = await &lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;generateOgImage&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;OgBlogPostImage&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; post &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;))&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#AA3731;--shiki-dark:#E06C75&quot;&gt;	// Cast from `Buffer&amp;lt;ArrayBufferLike&amp;gt;` to `Uint8Array&amp;lt;ArrayBuffer&amp;gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;	return new &lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;Response&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;(png as &lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;Uint8Array&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;ArrayBuffer&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;gt;,&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;		headers&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;Content-Type&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;image/png&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;	}&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Of course, this will change depending on how you get your posts into your site – I use a &lt;a href=&quot;https://docs.astro.build/en/guides/content-collections/&quot;&gt;content collection&lt;/a&gt; – but the core idea is the same. Fetch your posts and create a static &lt;code&gt;GET&lt;/code&gt; endpoint for each one. And if you’re in SSR mode, you can just remove the &lt;code&gt;getStaticPaths&lt;/code&gt; and generate the image on the fly! You’d probably want to add some caching in that case, though.&lt;/p&gt;
&lt;h3 id=&quot;its-all-in-your-head&quot;&gt;It’s All In Your &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Once the the images are being generated, you have tell other sites where to find them. If you clicked on the Open Graph protocol link above you probably spotted the secret sauce. Even if you didn’t I’ll still tell you: we need to add a tag to the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; of our pages. This probably means adding a prop to your layout component (with a default value if you have a generic image).&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes Alabaster Rubber&quot; style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF;--shiki-light-bg:#F7F7F7;--shiki-dark-bg:#21252B;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;astro&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#AA3731;--shiki-dark:#E06C75&quot;&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;type &lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;Props&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;	ogImageUrl?: string&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;const &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; ogImageUrl = &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;`${&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;Astro&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;origin&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;/og-image.png&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; }&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; = Astro&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;props&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#AA3731;--shiki-dark:#E06C75&quot;&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;doctype&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; html&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;html&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;	&amp;lt;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;head&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;		&amp;lt;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;meta&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; property&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;og:image&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; content&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;={&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;ogImageUrl&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#AA3731;--shiki-dark:#E06C75&quot;&gt;		&amp;lt;!-- the rest of your site metadata, probably including other Open Graph tags --&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;	&amp;lt;/&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;head&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;	&amp;lt;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#AA3731;--shiki-dark:#E06C75&quot;&gt;&amp;lt;!-- your site content --&amp;gt;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;html&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we update our blog post page:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes Alabaster Rubber&quot; style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF;--shiki-light-bg:#F7F7F7;--shiki-dark-bg:#21252B;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;astro&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#AA3731;--shiki-dark:#E06C75&quot;&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;import Layout from &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;#layouts/global.astro&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#AA3731;--shiki-dark:#E06C75&quot;&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;Layout ogImageUrl&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;={&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;`${&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;Astro&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;og-image.png&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;}&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#AA3731;--shiki-dark:#E06C75&quot;&gt;	&amp;lt;!-- blog stuff --&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;Layout&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that the links to the images are absolute links derived from &lt;code&gt;Astro.url&lt;/code&gt;. Some sites will be able to pick up on relative links, but in general it’s always safer to have absolute links.&lt;/p&gt;
&lt;h2 id=&quot;and-then-what&quot;&gt;And Then What?&lt;/h2&gt;
&lt;p&gt;Then you post the link to the blog post you wrote about putting Open Graph images on your website! That’s my plan, at least.&lt;/p&gt;</content:encoded></item><item><title>Creating a Dark Mode Toggle in 2026</title><link>https://lowmess.com/blog/dark-mode-toggle-2026/</link><guid isPermaLink="true">https://lowmess.com/blog/dark-mode-toggle-2026/</guid><description>Some new(-ish) CSS features make building a system-aware color theme selector ridiculously easy.</description><pubDate>Mon, 19 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I recently added a system-aware color theme selector to this website. Like, an hour ago. It was preposterously easy; the hardest part was deciding how and where I wanted to display the toggle. If you don’t believe me (or don’t want to read this article), &lt;a href=&quot;https://codepen.io/lowmess/pen/gbMgdRM&quot;&gt;here’s an entire implementation&lt;/a&gt; in about 50 lines of code. Let’s break it down.&lt;/p&gt;
&lt;h2 id=&quot;an-introduction-to-color-scheme-and-light-dark&quot;&gt;An Introduction to &lt;code&gt;color-scheme&lt;/code&gt; and &lt;code&gt;light-dark()&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;The two features that (now) make this so easy are &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/color-scheme&quot;&gt;&lt;code&gt;color-scheme&lt;/code&gt;&lt;/a&gt; and &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/color_value/light-dark&quot;&gt;&lt;code&gt;light-dark()&lt;/code&gt;&lt;/a&gt;. Put simply – or at least succinctly – &lt;code&gt;color-scheme&lt;/code&gt; allows us to tell the browser which color schemes an element supports and &lt;code&gt;light-dark()&lt;/code&gt; allows us to assign &lt;code&gt;&amp;lt;color&amp;gt;&lt;/code&gt;s by the combination of that value and the user’s &lt;code&gt;prefers-color-scheme&lt;/code&gt; setting. You could imagine a &lt;code&gt;light-dark()&lt;/code&gt; implementation in JavaScript looking something like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes Alabaster Rubber&quot; style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF;--shiki-light-bg:#F7F7F7;--shiki-dark-bg:#21252B;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;ts&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;function &lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;lightDark&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;lightValue: &lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;Color&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; darkValue: &lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;Color&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;	const colorScheme = &lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;getNearestColorSchemeRule&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;	const userPrefersDark = window&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;matchMedia&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;(prefers-color-scheme: dark)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;	if (colorScheme === &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;light&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;		return lightValue&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;	if (colorScheme === &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;dark&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;		return darkValue&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;	return colorScheme&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;includes&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;dark&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;) &amp;amp;&amp;amp; userPrefersDark&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;matches&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;		? darkValue&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;		: lightValue&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This gets at the core of what makes creating system-aware toggles so easy now. We can just tell the browser we don’t support a certain color scheme in some conditions! That’s exactly how the color palette for this site works:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes Alabaster Rubber&quot; style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF;--shiki-light-bg:#F7F7F7;--shiki-dark-bg:#21252B;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;css&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;root &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;	color-scheme&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; light dark&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;	--some-color&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; light-dark(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;#&lt;/span&gt;&lt;span style=&quot;--shiki-light:#7A3E9D;--shiki-dark:#BF79C3&quot;&gt;f00&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; #&lt;/span&gt;&lt;span style=&quot;--shiki-light:#7A3E9D;--shiki-dark:#BF79C3&quot;&gt;b44&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;root&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;data-color-theme=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;light&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;	color-scheme&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; light&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;root&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;data-color-theme=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;dark&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;	color-scheme&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; dark&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This alone is about 90% of a working implementation. If you take nothing else from this article it should be this: &lt;a href=&quot;https://knowyourmeme.com/memes/geodude-is-real-and-strong-and-hes-my-friend&quot;&gt;&lt;code&gt;light-dark()&lt;/code&gt; is real, and strong, and it’s my friend&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;creating-a-selector&quot;&gt;Creating a Selector&lt;/h2&gt;
&lt;p&gt;While you could make a group of &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;radio&amp;gt;&lt;/code&gt;s to actually allow the user to update their preference, I went with the element that is named after the verb the user will be taking: &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes Alabaster Rubber&quot; style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF;--shiki-light-bg:#F7F7F7;--shiki-dark-bg:#21252B;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;html&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;label&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; for&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;color-theme-select&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;Select color theme&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;label&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;select&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; id&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;color-theme-select&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;	&amp;lt;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;option&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; value&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;system&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;System&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;option&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;	&amp;lt;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;option&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; value&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;light&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;Light&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;option&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;	&amp;lt;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;option&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; value&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;dark&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;Dark&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;option&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;select&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once the markup is in place, updating the color scheme when the user changes the value of the &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; is simple:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes Alabaster Rubber&quot; style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF;--shiki-light-bg:#F7F7F7;--shiki-dark-bg:#21252B;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;ts&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;const $colorThemeSelect = document&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;querySelector&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;#color-theme-select&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;$colorThemeSelect&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;?.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;addEventListener&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;change&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; =&amp;gt; &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;	const nextValue = event&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;currentTarget&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;	document&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;documentElement&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;dataset&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;colorTheme = nextValue&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that even though I have a &lt;code&gt;&amp;quot;system&amp;quot;&lt;/code&gt; value in my &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt;, I don’t have to worry about it messing up any styles by setting the data attribute, since &lt;code&gt;:root&lt;/code&gt; and &lt;code&gt;:root[data-color-theme=&amp;quot;system&amp;quot;]&lt;/code&gt; would function identically. If you wanted to clear the attribute instead, that’s also a valid approach, though some of the rest of the code would need to be tweaked.&lt;/p&gt;
&lt;p&gt;That’s enough for a basic toggle, but now loading in new pages or refreshing will always revert to the default system color theme. To fix that, we’ll want to store the result of each change somewhere. Since this is a static site, I’m doing so in &lt;code&gt;localStorage&lt;/code&gt;, but a cookie or database entry may work better for dynamically rendered sites.&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes Alabaster Rubber has-diff&quot; style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF;--shiki-light-bg:#F7F7F7;--shiki-dark-bg:#21252B;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;ts&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;const $colorThemeSelect = document&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;querySelector&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;#color-theme-select&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;$colorThemeSelect&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;?.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;addEventListener&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;change&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; =&amp;gt; &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;	const nextValue = event&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;currentTarget&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;	document&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;documentElement&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;dataset&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;colorTheme = nextValue&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line diff add&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;	localStorage&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;setItem&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;color-theme&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; nextValue)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now that we have the value stored, we need to update the document on page load:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes Alabaster Rubber&quot; style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF;--shiki-light-bg:#F7F7F7;--shiki-dark-bg:#21252B;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;ts&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;const defaultTheme = &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;system&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;const storedTheme = localStorage&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;getItem&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;color-theme&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;document&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;documentElement&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;dataset&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;colorTheme = storedTheme ?? defaultTheme&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Be sure to put that script in your &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; to avoid any flashes of unwanted color. If you’re dynamically rendering the page, this is where storing the value as a cookie/in your database comes in. You can read the value and apply the appropriate &lt;code&gt;data-color-theme&lt;/code&gt; attribute to the root &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; element before the browser even gets the document.&lt;/p&gt;
&lt;p&gt;We might as well also set the default value on our &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; so the correct option is checked. Unlike setting the color theme on the document, this can happen after the initial parse/paint since it’s unlikely a user will be able to interact with the selector faster than the script parsing/execution. You can place it right next to your change handler.&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes Alabaster Rubber has-diff&quot; style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF;--shiki-light-bg:#F7F7F7;--shiki-dark-bg:#21252B;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;ts&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;const $colorThemeSelect = document&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;querySelector&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;#color-theme-select&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line diff add&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;if ($colorThemeSelect) &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line diff add&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;	$colorThemeSelect&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;value = storedTheme ?? defaultTheme&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line diff add&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;$colorThemeSelect&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;?.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;addEventListener&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;change&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; =&amp;gt; &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;	const nextValue = event&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;currentTarget&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;	document&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;documentElement&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;dataset&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;colorTheme = nextValue&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;	localStorage&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;setItem&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;color-theme&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; nextValue)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The last detail is to only display the select if the user can actually interact with it. Did you know they added a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@media/scripting&quot;&gt;&lt;code&gt;scripting&lt;/code&gt; media query to CSS&lt;/a&gt;? Me neither until earlier today.&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes Alabaster Rubber&quot; style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF;--shiki-light-bg:#F7F7F7;--shiki-dark-bg:#21252B;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;css&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;media &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;scripting&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; none&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;),&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;scripting&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; initial-only&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;),&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; print &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;	#&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;color-theme-select&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;	label&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;for=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;color-theme-select&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;		display&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; none&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;The first couple of times I built color toggles were painful. They relied on heavy and slow CSS-in-JS solutions and often resulted in a blinding version of &lt;a href=&quot;https://en.wikipedia.org/wiki/Flash_of_unstyled_content&quot;&gt;FOUC&lt;/a&gt; where the light theme would flash before correcting into the dark theme. By leaning into modern (and &lt;a href=&quot;https://caniuse.com/wf-light-dark&quot;&gt;well suppoorted&lt;/a&gt;) CSS features, we no longer have to make any trade-offs between user experience and developer experience. It’s a reminder that the web is a constantly improving platform, and just how exciting that can be.&lt;/p&gt;</content:encoded></item><item><title>The Tokens Are the System</title><link>https://lowmess.com/blog/the-tokens-are-the-system/</link><guid isPermaLink="true">https://lowmess.com/blog/the-tokens-are-the-system/</guid><description>When creating a design system library, it&amp;apos;s important to remember that the library can&amp;apos;t solve for every possible view. But our users can.</description><pubDate>Tue, 15 Dec 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Let’s pose a hypothetical.&lt;/p&gt;
&lt;p&gt;At work, you are tasked with creating a new view within the company’s application. Luckily your company has an established design system library (DSL), so you should be able to speed through the UI part of the task. But this new view introduces a component not covered by the library. You’ve talked with design, and they agree it’s an oversight, but necessary for the view. Or they acknowledge the inconvenience but point out it’s a one-off. In either case, this view cannot wait for the creation of a new component in the library. How do you solve this dilemma?&lt;/p&gt;
&lt;h2 id=&quot;that-wasnt-a-hypothetical&quot;&gt;That Wasn’t a Hypothetical&lt;/h2&gt;
&lt;p&gt;That’s right. I tricked you. That exact scenario has happened to me (and a lot of other developers, I’m certain) many times using both third-party and in-house &lt;abbr title=&quot;design system libraries&quot;&gt;DSLs&lt;/abbr&gt;. The answer is dependent on the team and on the library’s engineering. Sometimes design provides redlines for these one-offs. Sometimes you’re left on your own, and you need to scrounge around in source code to find color, spacing, and other values. Sometimes these values are defined in a &lt;a href=&quot;https://lowmess.com/blog/on-declarative-styling#design-tokens&quot;&gt;design token&lt;/a&gt; map. Sometimes they’re exposed as Sass variables or CSS custom properties; sometimes the library has a &lt;a href=&quot;https://rebassjs.org/reflexbox/&quot;&gt;Box component&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In truth, how we solve this particular hypothetical isn’t very important. What &lt;em&gt;is&lt;/em&gt; important is remembering that it isn’t a hypothetical at all. This situation &lt;strong&gt;will&lt;/strong&gt; arise.&lt;/p&gt;
&lt;p&gt;Also worth keeping in mind is that the problem isn’t only applicable to one-off components. Spacing is often (rightfully) left to the developers consuming the library, but incongruent spacing can have a dramatically negative impact on how the UI feels. How ever the developers approach the one-off component, they will approach these smaller problems the same way.&lt;/p&gt;
&lt;h2 id=&quot;designing-a-system&quot;&gt;Designing a System&lt;/h2&gt;
&lt;p&gt;Taking this quote-unquote “hypothetical” into account when creating our own &lt;abbr title=&quot;design system libraries&quot;&gt;DSLs&lt;/abbr&gt; is very important. Recall how I said the solution is dependent on the library’s engineering. Think through how your users would approach this problem with the tools you’ve given them. Have you exposed enough tokens to them for them to create their own view- or application-specific components that are harmonious with the rest of the system? How easy, how obvious is it for them to use the tokens? (Also important: have you documented your design tokens?)&lt;/p&gt;
&lt;p&gt;There are many ways to solve for this, some of which I’ve mentioned above. While I have my preference as both a library author and consumer – Box components are very good – I ultimately only care that the &lt;abbr title=&quot;design system library&quot;&gt;DSL&lt;/abbr&gt; makes my job quicker &amp;amp; easier while also improving the consistency of the UI. Whether that means the library ships with a customized &lt;a href=&quot;https://tailwindcss.com&quot;&gt;Tailwind&lt;/a&gt; config, a design token JSON file, or some other solution I’ve yet to come across is immaterial.&lt;/p&gt;
&lt;p&gt;I just want to create that damn component.&lt;/p&gt;</content:encoded></item><item><title>Styling Links for Print</title><link>https://lowmess.com/blog/styling-links-for-print/</link><guid isPermaLink="true">https://lowmess.com/blog/styling-links-for-print/</guid><description>With a little CSS, we can ensure the user always knows where a link goes. Even when they&amp;apos;re printing it.</description><pubDate>Fri, 03 Jul 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A few weeks ago, amid the ongoing COVID-19 pandemic, my apartment building left a note in my mailbox. Among the helpful information and polite requests to wear masks in shared areas, I noticed a piece of text that had a very familiar style. It was blue, and underlined, and seemed to hint at a missing piece of content. In short, it was a link. Of course, this being a printed piece of mail, the link wasn’t much help to me. And, while this note was certainly a Google Docs or Word document, it made me think about the way links are styled on the web.&lt;/p&gt;
&lt;p&gt;While people aren’t printing out websites at the same volume they used to, print styles are still very important. Modern browsers use these styles when saving a page as a PDF, for example. The problem is that links in this context are not always clickable. But with a little CSS we can ensure the user is always shown the URL the link points to:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes Alabaster Rubber&quot; style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF;--shiki-light-bg:#F7F7F7;--shiki-dark-bg:#21252B;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;css&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;media print &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;	a&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;link&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;href^=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;http&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;]::&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;after &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;		content&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; attr&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;href&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;	a&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;link&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;href^=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;]::&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;after &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;		content&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt; (https://mywebsite.com&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; attr&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;href&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;	a&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;link&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;href^=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;#&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;		text-decoration&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; none&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This CSS takes advantage of the &lt;code&gt;print&lt;/code&gt; media query to set special styles on links in that context. Specifically, we are creating &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-elements&quot;&gt;a psuedo-element&lt;/a&gt; on links that begin with &lt;code&gt;http&lt;/code&gt; (absolute links) and &lt;code&gt;/&lt;/code&gt; (root-relative links). The &lt;code&gt;content&lt;/code&gt; of those elements is set to the link element’s &lt;code&gt;href&lt;/code&gt; attribute using &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/attr&quot;&gt;the &lt;code&gt;attr&lt;/code&gt; function&lt;/a&gt;. As a bonus, we also remove the link-styling for anchor links, since those have no practical purpose in print contexts.&lt;/p&gt;
&lt;p&gt;Of course this isn’t an advised solution to &lt;em&gt;every&lt;/em&gt; link, but it is particularly useful for blog posts, technical documentation, and any content that a user might want to save to refer to later. Printing websites not be a common occurrence anymore, but setting print styles absolutely should be.&lt;/p&gt;</content:encoded></item><item><title>No Red Squigglies For Semicolons</title><link>https://lowmess.com/blog/no-red-squigglies-for-semicolons/</link><guid isPermaLink="true">https://lowmess.com/blog/no-red-squigglies-for-semicolons/</guid><description>Linters are powerful code quality tools. Stylistic consistency is important. Linters should never enforce stylistic consistency.</description><pubDate>Wed, 04 Dec 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Us developers are an opinionated bunch. We share our opinions wide and loud through blog posts, tweets, talks at meetups, in meetings. The tenacity with which we hold these opinions seems to have &lt;a href=&quot;https://en.wikipedia.org/wiki/Law_of_triviality&quot;&gt;an inverse correlation with their impact&lt;/a&gt;, and nowhere is this strange dichotomy more present than in stylistic choices. Should we use tabs or spaces for whitespace? Should we use semicolons or not? Single quotes or double quotes?&lt;/p&gt;
&lt;p&gt;Collectively, these stylistic choices are known as bikeshedding. Thousands of words have been written about how we should be painting our bikesheds. We have many tools to help us paint the bikeshed. But, truth be told, I don’t care much about what color the bikeshed is. What I do care about is my code editor’s linting extension putting a red squiggly line under a function declaration because there’s a space between the name of the function and its parameter list.&lt;/p&gt;
&lt;h2 id=&quot;linters-are-for-code-quality-formatters-are-for-style&quot;&gt;Linters Are For Code Quality, Formatters Are For Style&lt;/h2&gt;
&lt;p&gt;Linters are incredible code quality tools. They enforce best patterns and can help catch bugs well before our code makes it to a browser. But they are ill-equipped to handle issues of style. Attempting to use an undefined variable is a bug, and your linter should point this out to you. Using single quotes instead of double quotes, on the other hand, will not impact your code’s logic or ability to run. As such, your linter should not care about this issue. At the very least it should not interrupt your work to tell you about it.&lt;/p&gt;
&lt;p&gt;Formatters are a different kind of tool altogether. They care only about enforcing a consistent style and rarely (if ever) give real-time feedback. Instead, they wait until your work is at a safe stopping point to run: when you save a file, when you commit, or when you explicitly tell the formatter to run. Because of this, they are much less likely to introduce friction into your workflow.&lt;/p&gt;
&lt;p&gt;In the JavaScript world the most used linter is &lt;a href=&quot;https://eslint.org&quot;&gt;ESLint&lt;/a&gt;, and the most used formatter is &lt;a href=&quot;https://prettier.io&quot;&gt;Prettier&lt;/a&gt;. &lt;a href=&quot;https://lowmess.com/colophon/&quot;&gt;They’re what I use&lt;/a&gt; across my projects. Unfortunately, many popular ESLint configs include strict enforcement of stylistic rules. Recognizing that conflicts between the two tools would arise, the Prettier team created &lt;a href=&quot;https://github.com/prettier/eslint-config-prettier&quot;&gt;&lt;code&gt;eslint-config-prettier&lt;/code&gt;&lt;/a&gt;. The aptly-named ESLint config turns off any rules that conflict with Prettier, giving it full dominion over all issues of style. Using it allows us to keep the code quality benefits of our linter, only without those pesky bikeshedding errors.&lt;/p&gt;
&lt;h2 id=&quot;if-you-have-to-remember-to-do-it-youll-forget-to-do-it&quot;&gt;If You Have to Remember To Do It, You’ll Forget To Do It&lt;/h2&gt;
&lt;p&gt;The workflow friction does not stop with squiggly lines, of course. If your setup relies on you to remember to run a linter or formatter (or test suite) against your code, you will forget to do so. Probably not every time, maybe even not most of the time, but &lt;em&gt;definitely&lt;/em&gt; some of the time. This is especially true when working in a shared codebase: we can’t be sure all members of our team have the same linting &amp;amp; formatting extensions installed, that those extenstions are configured the same way, or be sure our teammates will remember to run those tools manually. Instead, I find it better to set these tools up to run with Git hooks.&lt;/p&gt;
&lt;p&gt;Git hooks allow us to run arbitrary commands when performing a Git function; they allow us to run scripts before or after committing or pushing or pulling. While possible to &lt;a href=&quot;https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks&quot;&gt;set these hooks up manually&lt;/a&gt;, tools like &lt;a href=&quot;https://github.com/typicode/husky&quot;&gt;husky&lt;/a&gt; make setting them up much easier. I also often use a tool called &lt;a href=&quot;https://github.com/okonet/lint-staged&quot;&gt;lint-staged&lt;/a&gt; to lint &amp;amp; format only those files staged for commit. This makes the process much faster, and ensures that no extraneous files are included in a commit.&lt;/p&gt;
&lt;p&gt;Combined with removing bikeshedding rules from your linter, introducing a formatter and setting up Git hooks will remove a fair amount of friction from your code writing process. Setting these tools up — ESLint, Prettier, husky, &amp;amp; lint-staged — are among the first things I do in any new JavaScript project. I highly recommend giving it a shot.&lt;/p&gt;</content:encoded></item><item><title>On Declarative Styling</title><link>https://lowmess.com/blog/on-declarative-styling/</link><guid isPermaLink="true">https://lowmess.com/blog/on-declarative-styling/</guid><description>By limiting the amount of CSS we can write, we improve the quality of the CSS we do write.</description><pubDate>Fri, 19 Jul 2019 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;The enemy of art is the absence of limitations.&lt;/p&gt;
&lt;p class=&quot;attribution&quot;&gt;Orson Welles&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;There is a problem at the heart of CSS. It’s not the cascade, or specificity, or inconsistencies between rendering engines – though these things can be annoying. No, it’s much simpler than that: the problem is that we can write too much of it.&lt;/p&gt;
&lt;p&gt;I am not talking about &lt;a href=&quot;https://css-tricks.com/oh-no-stylesheet-grows-grows-grows-append-stylesheet-problem/&quot;&gt;append-only stylesheets&lt;/a&gt; (though these too cause their issues). Even if we’re extremely disciplined about refactoring our CSS, and we only add new rules when absolutely needed, something is still wrong. The problem is the flexibility of the language itself. There are nearly unlimited valid values that a &lt;code&gt;padding&lt;/code&gt; declaration can take, and while extremely freeing this also introduces surface area for inconsistencies in our designs. And consistency is key to good design! It reduces the end user’s cognitive load, it (generally) looks better, and it minimizes the workload for designers &amp;amp; developers to boot.&lt;/p&gt;
&lt;p&gt;Artificially limiting the number of values that we can use in declarations is key to avoiding these inconsistencies. We want a declaration like &lt;code&gt;padding&lt;/code&gt; to act a little more like &lt;code&gt;float&lt;/code&gt;; we should only be able to set a value that we’ve defined in our governing system. There are many techniques and technologies that can help us accomplish this (or at least get us close). I call the overarching concept that these tools encompass “declarative styling”.&lt;/p&gt;
&lt;h2 id=&quot;defining-declarative&quot;&gt;Defining “Declarative”&lt;/h2&gt;
&lt;p&gt;This term – declarative styling – is derived from the computer science concept &lt;a href=&quot;https://en.wikipedia.org/wiki/Declarative_programming&quot;&gt;declarative programming&lt;/a&gt;. It means we want to tell the computer the rules for drawing our interface, and let it follow those rules for us. We no longer want to write &lt;code&gt;padding: 1rem&lt;/code&gt;, we want to write something like &lt;code&gt;padding: 3&lt;/code&gt; and have the computer replace the &lt;code&gt;3&lt;/code&gt; with the 3rd value in our spacing scale.&lt;/p&gt;
&lt;p&gt;This accomplishes several things for us. It ensures consistency across our design by allowing us to use a reference rather than a specific value. It also reduces the cognitive load for stakeholders by providing a common language to communicate in. These factors (among others) can make designing and iterating faster, and all but eliminate the inherent friction in designer-developer handoff.&lt;/p&gt;
&lt;h2 id=&quot;design-tokens&quot;&gt;Design Tokens&lt;/h2&gt;
&lt;p&gt;Those familiar with the concept of &lt;a href=&quot;https://css-tricks.com/what-are-design-tokens/&quot;&gt;design tokens&lt;/a&gt; may find a lot of conceptual overlap here. Design tokens are an essential part of declarative styling: they are how we define our custom subset of styling options. If a rule in our stylesheet declares a &lt;code&gt;background-color&lt;/code&gt;, that swatch should be found in our tokens.&lt;/p&gt;
&lt;p&gt;There are many techniques for storing and parsing design tokens. I’m partial to the JSON-based &lt;a href=&quot;https://system-ui.com/theme&quot;&gt;System UI theme specification&lt;/a&gt;, which organizes our tokens into a variety of scales. Several of the tools discussed below rely on this or a similar technique, but the concept remains the same: there should be a source of truth for these values, and it should not be the CSS rule itself.&lt;/p&gt;
&lt;h2 id=&quot;techniques&quot;&gt;Techniques&lt;/h2&gt;
&lt;p&gt;Much like there are a multitude of ways to store our tokens, there are many, many ways to apply them to our styles.&lt;/p&gt;
&lt;h3 id=&quot;css-variables&quot;&gt;CSS Variables&lt;/h3&gt;
&lt;p&gt;The most commonly-used solution, and one you’re likely familiar with, is to use variables whenever possible. Although Sass and LESS have had variables since their inception, CSS now has native variable support &lt;a href=&quot;http://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties&quot;&gt;with the custom properties specification&lt;/a&gt;.&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes Alabaster Rubber&quot; style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF;--shiki-light-bg:#F7F7F7;--shiki-dark-bg:#21252B;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;css&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;box &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;	padding&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;--spacing-3&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Unlike the variables of preprocessors like Sass and LESS, custom properties can take full advantage of the cascade. This means we can create fully themeable component styles natively.&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes Alabaster Rubber&quot; style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF;--shiki-light-bg:#F7F7F7;--shiki-dark-bg:#21252B;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;css&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;btn &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#AA3731;--shiki-dark:#E06C75&quot;&gt;	/* First we define our variables in the base component style */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;	--button-padding-y&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;--spacing-2&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;	--button-padding-x&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;--spacing-3&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#AA3731;--shiki-dark:#E06C75&quot;&gt;	/* Then we apply our variables to our declarations */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;	padding&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;--button-padding-y&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;--button-padding-x&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#AA3731;--shiki-dark:#E06C75&quot;&gt;/* By updating our variables, the styles will change automatically */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;btn--large &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;	--button-padding-y&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;--spacing-3&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;	--button-padding-x&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;--spacing-4&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To take full advantage of this system, we need to convert our tokens to custom properties in the &lt;code&gt;:root&lt;/code&gt; selector. The easiest way to do this is to copy and paste the values by hand, though there are &lt;a href=&quot;https://github.com/salesforce-ux/theo&quot;&gt;tools to automate the process&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Of course, no method is without a downside. In this case, the biggest flaw is the same as its biggest draw: the low barrier to entry. There are no guardrails to stop you from writing &lt;code&gt;padding: 24px&lt;/code&gt; instead of using your variable. It takes a lot of discipline to not deviate from the system, and any time you’re writing new CSS is an opportunity to create a discrepancy. But combined with a strict code review process, this can be a powerful way of enforcing consistency while creating a themeable system.&lt;/p&gt;
&lt;h3 id=&quot;atomic-css&quot;&gt;Atomic CSS&lt;/h3&gt;
&lt;p&gt;Atomic CSS (aka Functional CSS, aka Utility-first CSS) libraries like BassCSS, Tachyons, and Tailwind CSS are declarative by definition. Classes like &lt;code&gt;p3&lt;/code&gt; automatically follow our &lt;code&gt;padding&lt;/code&gt; rule from above: we’re telling the engine to apply equal padding to all sides (the &lt;code&gt;p&lt;/code&gt;) using the third step from our spacing scale (the &lt;code&gt;3&lt;/code&gt;).&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes Alabaster Rubber&quot; style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF;--shiki-light-bg:#F7F7F7;--shiki-dark-bg:#21252B;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;html&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; class&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;p3&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;Hi&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Customizing BassCSS and Tachyons can be tough, &lt;a href=&quot;https://lowmess.com/blog/customizing-tachyons/&quot;&gt;but it is possible&lt;/a&gt;. Tailwind, on the other hand, is &lt;a href=&quot;https://tailwindcss.com/docs/configuration&quot;&gt;fully customizable by default&lt;/a&gt;. The Tailwind configuration system is a plain JS object exported from a Node script. A major benefit to this approach is that we can read our tokens from a JSON or YAML file, and apply them to our custom config with a few lines of code.&lt;/p&gt;
&lt;p&gt;I’m on the record as being a big, big fan of atomic CSS. But I’m not blind to the disadvantages. The learning curve can be quite steep; not only do we need to internalize the naming scheme, but we also need to rethink how we apply our CSS. Because we also need to apply a fair amount of classnames to our components, I also tend to recommend this approach only for very simple projects or for projects that have a powerful templating system. Applying atomic CSS classes to a React component or Pug mixin makes applying the classnames to our elements much more palatable.&lt;/p&gt;
&lt;h3 id=&quot;styled-system&quot;&gt;Styled System&lt;/h3&gt;
&lt;p&gt;The advent of CSS-in-JS has presented us with a unique opportunity: by taking advantage of an expressive, fully-featured programming language, we can create abstractions on top of CSS that would otherwise be impossible. For example, by taking the lessons learned from atomic CSS and applying them to a JSX-based CSS-in-JS system, &lt;a href=&quot;https://jxnblk.com/&quot;&gt;Brent Jackson&lt;/a&gt; has created several purpose-built declarative styling libraries.&lt;/p&gt;
&lt;p&gt;The first of these libraries, the foundation upon which the others are built, is called &lt;a href=&quot;https://styled-system.com/&quot;&gt;Styled System&lt;/a&gt;. The library provides a consistent interface to refer to your design tokens when defining or consuming a component. The true genius of Styled System is &lt;a href=&quot;https://styled-system.com/responsive-styles&quot;&gt;how it handles responsive styles&lt;/a&gt;. By default, Styled System props accept a string, which the library parses and converts to a value represented by a design token. If the token is not found, the literal value is passed to the underlying CSS-in-JS library. However, by passing a prop an array, it will apply the 0-index value to the component by default, the 1-index value to the component at the first breakpoint, and so on and so forth.&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes Alabaster Rubber&quot; style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF;--shiki-light-bg:#F7F7F7;--shiki-dark-bg:#21252B;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;jsx&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;import styled from &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;styled-components&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;import &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; space&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; color &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; from &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;styled-system&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;const Box = styled&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;	${&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;space&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;	${&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;color&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;const &lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;MyStyledSystemComponent&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;props&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; =&amp;gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;	&amp;lt;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;Box&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; p=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;--shiki-light:#7A3E9D;--shiki-dark:#BF79C3&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;--shiki-light:#7A3E9D;--shiki-dark:#BF79C3&quot;&gt; 3&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; color=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;primary&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;...props&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;}&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;		Hi&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;	&amp;lt;/&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;Box&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This approach allows us to not only create components that are not only consistent with our system, but are extremely portable as well. A rule of thumb I try to follow when styling a component is to only define surrounding vertical margin when the component is actually consumed in a view. By spreading our props to a root component defined with Styled System, it becomes trivial to follow this rule.&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes Alabaster Rubber&quot; style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF;--shiki-light-bg:#F7F7F7;--shiki-dark-bg:#21252B;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;jsx&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;const &lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;MyView&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; =&amp;gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;	&amp;lt;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;main&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;		&amp;lt;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;header&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;header&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;		&amp;lt;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;MyStyledSystemComponent&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; my=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;--shiki-light:#7A3E9D;--shiki-dark:#BF79C3&quot;&gt;4&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;		&amp;lt;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;footer&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;footer&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;	&amp;lt;/&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;main&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because Styled System (when used in React) reads from the theme put into context from your CSS-in-JS library of choice’s &lt;code&gt;ThemeProvider&lt;/code&gt;, creating themeable components is a breeze. However, attaching &lt;code&gt;space&lt;/code&gt;, &lt;code&gt;color&lt;/code&gt;, and other token references to all your components can be quite annoying. Luckily, Mr. Jackson has also created a library of primitive components built on top of Styled System called &lt;a href=&quot;https://rebassjs.org/&quot;&gt;Rebass&lt;/a&gt;. These components operate like the &lt;code&gt;Box&lt;/code&gt; component we utilized inside of &lt;code&gt;MyStyledSystemComponent&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Most recently, Brent has released &lt;a href=&quot;https://theme-ui.com/&quot;&gt;Theme UI&lt;/a&gt;, a further iteration of this idea. Theme UI exposes an &lt;code&gt;sx&lt;/code&gt; prop on JSX components that allows us to reference our tokens, provides primitive components to use as a foundation, handles styling of markdown content with MDX, and much, much more. It’s an exciting evolution of the approach, and one I intend to explore more fully in the future.&lt;/p&gt;
&lt;p&gt;The Styled System approach, much like the others, comes with its fair share of downsides. For starters, it shares the same learning curve problem as atomic CSS. It can be somewhat cumbersome to set up, particularly when not using Rebass or Theme UI’s primitive components. And while it can technically work with any framework that supports JSX, React is the only true first-class Styled System citizen. But, when given a choice, it is still my preferred method for defining and consuming styles.&lt;/p&gt;
&lt;h2 id=&quot;a-vision-of-the-future&quot;&gt;A Vision of the Future&lt;/h2&gt;
&lt;p&gt;The web has always been an extremely flexible and expressive platform. This has lead to some amazing creative applications of technology, new media experiences, and beautiful interactions. It has also created plenty of &lt;a href=&quot;https://en.wiktionary.org/wiki/footgun&quot;&gt;footgun&lt;/a&gt; opportunities. By placing self-imposed constraints on the very fabric of the web, we get to remove the more self-sabotaging aspects of designing for the platform. Rather than limiting our creativity, these constraints provide guardrails for us to explore the limits of our designs while providing a consistent and visually harmonious interface to our users.&lt;/p&gt;
&lt;p&gt;The declarative styling methodology will continue to evolve over time. As design tokens become more prevalent, design software will add first-class support for them. More libraries and methods will evolve for applying them to our products, both on the web and beyond. As we solve the problem at the heart of CSS, the language we use to communicate between ideation and implementation will meld into one. Perhaps our tools will as well.&lt;/p&gt;</content:encoded></item><item><title>Request Timeouts With the Fetch API</title><link>https://lowmess.com/blog/fetch-with-timeout/</link><guid isPermaLink="true">https://lowmess.com/blog/fetch-with-timeout/</guid><description>The Fetch API is great, but didn&apos;t ship with the ability to timeout requests. There&amp;apos;s a new API to help with that.</description><pubDate>Sat, 30 Mar 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I’m a big fan of the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API&quot;&gt;Fetch API&lt;/a&gt;. I use it regularly in all sorts of projects, including this site and the API that powers the stats on the &lt;a href=&quot;https://lowmess.com/about/&quot;&gt;about page&lt;/a&gt;. However it isn’t always as clear how to do things like error handling and request timeouts as it is in libraries like &lt;a href=&quot;https://github.com/axios/axios&quot;&gt;Axios&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you’re not familiar with &lt;code&gt;fetch&lt;/code&gt;, it’s a native API that massively simplifies making AJAX requests compared to the older &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/XMLHTTPRequest&quot;&gt;XHR&lt;/a&gt; method, and &lt;a href=&quot;https://caniuse.com/#feat=fetch&quot;&gt;it’s supported in all modern browsers&lt;/a&gt;. When it initially landed, however, there was no easy way to handle request timeouts. You could fake it with &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race&quot;&gt;&lt;code&gt;Promise.race&lt;/code&gt;&lt;/a&gt; or by &lt;a href=&quot;https://github.com/github/fetch/issues/175#issuecomment-216791333&quot;&gt;wrapping your &lt;code&gt;fetch&lt;/code&gt; in another Promise&lt;/a&gt;, but these solutions don’t actually cancel the request. This is where &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/AbortController&quot;&gt;&lt;code&gt;AbortController&lt;/code&gt;&lt;/a&gt; comes in.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;AbortController&lt;/code&gt; is an API that, much like its name and my previous sentence suggests, allows us to abort (cancel) requests. Though &lt;a href=&quot;https://caniuse.com/#feat=abortcontroller&quot;&gt;browser support isn’t &lt;em&gt;wonderful&lt;/em&gt; at time of writing&lt;/a&gt;, it can be used in most modern browsers and &lt;a href=&quot;https://github.com/mo/abortcontroller-polyfill&quot;&gt;polyfills are available&lt;/a&gt;. The API itself has a very small surface area: a &lt;code&gt;signal&lt;/code&gt; property to attach to request objects, and an &lt;code&gt;abort&lt;/code&gt; method to actually cancel the request. Because the API is so simple, it’s very flexible – Jake Archibald has &lt;a href=&quot;https://developers.google.com/web/updates/2017/09/abortable-fetch&quot;&gt;a fairly in-depth article on the Google Developers blog&lt;/a&gt; going over various cancellation scenarios, as well as the history behind the API, and I highly recommend giving it a read.&lt;/p&gt;
&lt;p&gt;With &lt;code&gt;AbortController&lt;/code&gt;, it becomes trivial to cancel a request if it doesn’t resolve before a given period of time: if the &lt;code&gt;abort&lt;/code&gt; method is called before the request resolves (or before the response &lt;code&gt;Body&lt;/code&gt; is consumed), the request is cancelled; if it’s called after, the browser just ignores the call. To put it all together, we need to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create an instance of &lt;code&gt;AbortController&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Create a &lt;code&gt;setTimeout&lt;/code&gt; function that calls the controller’s &lt;code&gt;abort&lt;/code&gt; method&lt;/li&gt;
&lt;li&gt;Pass the controller’s &lt;code&gt;signal&lt;/code&gt; to &lt;code&gt;fetch&lt;/code&gt;’s options object&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;putting-it-all-together&quot;&gt;Putting It All Together&lt;/h2&gt;
&lt;p&gt;First, because we’re basically writing a shim around &lt;code&gt;fetch&lt;/code&gt;, I’m going to add an extra little perk. If the response doesn’t return in the &lt;code&gt;200&lt;/code&gt; range (that is, if &lt;code&gt;response.ok&lt;/code&gt; evaluates to &lt;code&gt;false&lt;/code&gt;), we’re going to throw an error. We absolutely do not need to do this – we could just catch our timeout and the function would work the same (we actually don’t even &lt;em&gt;need&lt;/em&gt; to do that). However, I always perform this check anyways, so this removes a lot of boilerplate code for me.&lt;/p&gt;
&lt;p&gt;Anyways, here is my generic &lt;code&gt;fetchWithTimeout&lt;/code&gt; function. It should work in any environment that supports &lt;code&gt;fetch&lt;/code&gt; and &lt;code&gt;AbortController&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes Alabaster Rubber&quot; style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF;--shiki-light-bg:#F7F7F7;--shiki-dark-bg:#21252B;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;const &lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;fetchWithTimeout&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;uri&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; options = &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;{},&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; time = &lt;/span&gt;&lt;span style=&quot;--shiki-light:#7A3E9D;--shiki-dark:#BF79C3&quot;&gt;5000&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; =&amp;gt; &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#AA3731;--shiki-dark:#E06C75&quot;&gt;	// Lets set up our `AbortController`, and create a request options object&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#AA3731;--shiki-dark:#E06C75&quot;&gt;	// that includes the controller&amp;#39;s `signal` to pass to `fetch`.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;	const controller = new &lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;AbortController&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;	const config = &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; ...options&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; signal&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; controller&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;signal &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#AA3731;--shiki-dark:#E06C75&quot;&gt;	// Set a timeout limit for the request using `setTimeout`. If the body&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#AA3731;--shiki-dark:#E06C75&quot;&gt;	// of this timeout is reached before the request is completed, it will&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#AA3731;--shiki-dark:#E06C75&quot;&gt;	// be cancelled.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;	const timeout = &lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;setTimeout&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; =&amp;gt; &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;		controller&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;abort&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;	},&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; time)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;	return &lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;fetch&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;(uri&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; config)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;		.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;then&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;response&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; =&amp;gt; &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#AA3731;--shiki-dark:#E06C75&quot;&gt;			// Because _any_ response is considered a success to `fetch`, we&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#AA3731;--shiki-dark:#E06C75&quot;&gt;			// need to manually check that the response is in the 200 range.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#AA3731;--shiki-dark:#E06C75&quot;&gt;			// This is typically how I handle that.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;			if (!response&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;ok) &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;				throw new &lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;Error&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;`${&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;response&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;status&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;response&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;statusText&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;			}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;			return response&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;		}&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;		.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;catch&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; =&amp;gt; &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#AA3731;--shiki-dark:#E06C75&quot;&gt;			// When we abort our `fetch`, the controller conveniently throws&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#AA3731;--shiki-dark:#E06C75&quot;&gt;			// a named error, allowing us to handle them separately from&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#AA3731;--shiki-dark:#E06C75&quot;&gt;			// other errors.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;			if (error&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;name === &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;AbortError&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;				throw new &lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;Error&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;Response timed out&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;			}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;			throw new &lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;Error&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;(error&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;message)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;		}&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using the function is fairly straightforward. Because we return &lt;code&gt;fetch&lt;/code&gt; directly, we can use it in much the same way; the only change should be the addition of a third parameter (our &lt;code&gt;time&lt;/code&gt; argument) and the extra error handling we discussed above.&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes Alabaster Rubber&quot; style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF;--shiki-light-bg:#F7F7F7;--shiki-dark-bg:#21252B;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#AA3731;--shiki-dark:#E06C75&quot;&gt;// This example _always_ logs the error, because I&amp;#39;m telling httpstat.us&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#AA3731;--shiki-dark:#E06C75&quot;&gt;// to wait at least 1s before responding, but setting the timeout&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#AA3731;--shiki-dark:#E06C75&quot;&gt;// threshold to 500ms.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#AA3731;--shiki-dark:#E06C75&quot;&gt;// Also, this could definitely be written in async/await if you preferred.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;fetchWithTimeout&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;	&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;https://httpstat.us/200?sleep=1000&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;	{&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; headers&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; Accept&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;application/json&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; }&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#7A3E9D;--shiki-dark:#BF79C3&quot;&gt;	500&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;	.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;then&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;response&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; =&amp;gt; response&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;	.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;then&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; =&amp;gt; &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;		console&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;This will never log out: &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;	}&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;	.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;catch&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; =&amp;gt; &lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;		console&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#325CC0;--shiki-dark:#61AFEF&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;(error&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;message)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;	}&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That’s it. That’s the whole post. Though the snippet is ultimately pretty simple (it’s 20 lines without whitespace and comments) writing this provided me with three major benefits: it forced me to abstract the function to the most reusable version I could, it gave me an opportunity to research &lt;code&gt;AbortController&lt;/code&gt; to make sure I knew exactly how it behaved, and it provided a place where I can come find this snippet in the future instead of rooting through old projects.&lt;/p&gt;</content:encoded></item><item><title>A Better Album Ranking Algorithm</title><link>https://lowmess.com/blog/a-better-album-ranking-algorithm/</link><guid isPermaLink="true">https://lowmess.com/blog/a-better-album-ranking-algorithm/</guid><description>In its quest to glorify the scrobble, Last.fm has forgotten the humble album. We can almost fix that.</description><pubDate>Mon, 12 Mar 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Years ago, I deleted my &lt;a href=&quot;https://last.fm&quot;&gt;Last.fm&lt;/a&gt; account, because it felt like a dying social media network. Months ago, I created &lt;a href=&quot;https://www.last.fm/user/lowmess&quot;&gt;a new one&lt;/a&gt;, because Last.fm is a treasure trove of behaviorally generated personal data. It’s a lovely service for a developer to have around for side projects, as it provides instant and easy access to a large dataset with a particularly personal connection if you’re a music lover. In the process of using it for &lt;a href=&quot;https://lowmess.com/about/&quot;&gt;one of those side projects&lt;/a&gt;, I found a dark &amp;amp; disturbing secret: Last.fm actually kind of sucks for tracking album plays.&lt;/p&gt;
&lt;p&gt;Fair warning: things are about to take a turn for the opinionated, pedantic, and downright trivial.&lt;/p&gt;
&lt;h2 id=&quot;the-scrobble-killed-the-record-star&quot;&gt;The Scrobble Killed the Record Star&lt;/h2&gt;
&lt;p&gt;Last.fm is built upon an atomic unit: the scrobble. From it, all other units are derived. A scrobble represents one play of one song, and Last.fm sets your top album by how many scrobbles it has. The problem with this is that it’s the complete wrong way to count album plays.&lt;/p&gt;
&lt;p&gt;Album play counts are slightly different than song play counts. They should start from basically the same place – one playthrough is one playthrough – but from there they branch in subtle but important ways. For example, it is much more common to only partially listen to an album than it is to partially listen to a song. Albums are also, generally, between 10 and 12 times longer than songs, and broken into as many parts.&lt;/p&gt;
&lt;p&gt;If the length of an album is what makes a complete playthrough of an album rarer than a playthrough of a song, a playthrough of a longer album should count for more than that of a shorter album. (There’s an argument to be made that the same is true of songs, but we’ll leave that for another day.) Thus, the ideal album ranking algorithm would account for total playthroughs, with a weight given to longer albums. Maybe even something as simple as this:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes Alabaster Rubber&quot; style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF;--shiki-light-bg:#F7F7F7;--shiki-dark-bg:#21252B;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;albumPlaythroughs * (&lt;/span&gt;&lt;span style=&quot;--shiki-light:#7A3E9D;--shiki-dark:#BF79C3&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; + albumDuration / &lt;/span&gt;&lt;span style=&quot;--shiki-light:#7A3E9D;--shiki-dark:#BF79C3&quot;&gt;2700&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, we weight the duration in 45 minute increments, the length of a single LP (&lt;code&gt;albumDuration&lt;/code&gt; being in seconds). Adding 1 to the duration calculation helps to ensure we’re not simply normalizing for record length – while that is also a valid way to rank album plays, it is not the one I prefer.&lt;/p&gt;
&lt;h2 id=&quot;ok-you-got-me-something-exactly-as-simple-as-that&quot;&gt;Ok, You Got Me. Something Exactly As Simple As That&lt;/h2&gt;
&lt;p&gt;Let’s take three albums of varying lengths and see how they handle this algorithm. Our albums:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes Alabaster Rubber&quot; style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF;--shiki-light-bg:#F7F7F7;--shiki-dark-bg:#21252B;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;[&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;	{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;		&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;Cardinal&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;		&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;artist&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;Pinegrove&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;		&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;duration&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#7A3E9D;--shiki-dark:#BF79C3&quot;&gt; 1845&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;	},&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;	{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;		&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;Marquee Moon&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;		&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;artist&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;Television&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;		&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;duration&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#7A3E9D;--shiki-dark:#BF79C3&quot;&gt; 2754&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;	},&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;	{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;		&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;Summertime &amp;#39;06&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;		&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;artist&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;Vince Staples&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;		&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#448C27;--shiki-dark:#98C379&quot;&gt;duration&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#7A3E9D;--shiki-dark:#BF79C3&quot;&gt; 3545&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Given one playthrough each, the three records will be ranked in exactly the same order they are now. Let’s listen to a few more tracks.&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes Alabaster Rubber&quot; style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF;--shiki-light-bg:#F7F7F7;--shiki-dark-bg:#21252B;overflow-x:auto&quot; tabindex=&quot;0&quot; data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;const cardinalRank = &lt;/span&gt;&lt;span style=&quot;--shiki-light:#7A3E9D;--shiki-dark:#BF79C3&quot;&gt;3.5&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; * (&lt;/span&gt;&lt;span style=&quot;--shiki-light:#7A3E9D;--shiki-dark:#BF79C3&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; + &lt;/span&gt;&lt;span style=&quot;--shiki-light:#7A3E9D;--shiki-dark:#BF79C3&quot;&gt;1845&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; / &lt;/span&gt;&lt;span style=&quot;--shiki-light:#7A3E9D;--shiki-dark:#BF79C3&quot;&gt;2700&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#AA3731;--shiki-dark:#E06C75&quot;&gt; // 5.89&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;const marqueeRank = &lt;/span&gt;&lt;span style=&quot;--shiki-light:#7A3E9D;--shiki-dark:#BF79C3&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; * (&lt;/span&gt;&lt;span style=&quot;--shiki-light:#7A3E9D;--shiki-dark:#BF79C3&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; + &lt;/span&gt;&lt;span style=&quot;--shiki-light:#7A3E9D;--shiki-dark:#BF79C3&quot;&gt;2745&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; / &lt;/span&gt;&lt;span style=&quot;--shiki-light:#7A3E9D;--shiki-dark:#BF79C3&quot;&gt;2700&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#AA3731;--shiki-dark:#E06C75&quot;&gt; // 6.05&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;const summertimeRank = &lt;/span&gt;&lt;span style=&quot;--shiki-light:#7A3E9D;--shiki-dark:#BF79C3&quot;&gt;2.5&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; * (&lt;/span&gt;&lt;span style=&quot;--shiki-light:#7A3E9D;--shiki-dark:#BF79C3&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; + &lt;/span&gt;&lt;span style=&quot;--shiki-light:#7A3E9D;--shiki-dark:#BF79C3&quot;&gt;3545&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt; / &lt;/span&gt;&lt;span style=&quot;--shiki-light:#7A3E9D;--shiki-dark:#BF79C3&quot;&gt;2700&lt;/span&gt;&lt;span style=&quot;--shiki-light:#000;--shiki-dark:#ABB2BF&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#777777;--shiki-dark:#737C8C&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#AA3731;--shiki-dark:#E06C75&quot;&gt; // 5.78&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we’re getting somewhere. Even though &lt;em&gt;Cardinal&lt;/em&gt; has the most playthroughs, &lt;em&gt;Marquee Moon&lt;/em&gt; has the highest rank thanks to its lengthier runtime. &lt;em&gt;Summertime ’06&lt;/em&gt; is just barely in third place despite having a full playthrough less than &lt;em&gt;Cardinal&lt;/em&gt;. The system works, folks.&lt;/p&gt;
&lt;h2 id=&quot;this-is-the-part-with-the-but&quot;&gt;This Is the Part With the “But”&lt;/h2&gt;
&lt;p&gt;Given all the information we need, the algorithm works. But we still have to get the information, and unfortunately, Last.fm doesn’t make it too easy on us:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We can easily get a user’s top albums, but the response doesn’t include information on the amount of tracks on, or duration of, each album.&lt;/li&gt;
&lt;li&gt;Last.fm doesn’t use unique IDs to track albums, so we have to use an endpoint that searches by album name and artist to get detailed album information. This often fails or retrieves the wrong album, even when the album name and artist are provided by Last.fm (like from the previously discussed endpoint).&lt;/li&gt;
&lt;li&gt;Once we find an album, track listings are missing or incomplete about 20% of the time. This happens more often with newer albums.&lt;/li&gt;
&lt;li&gt;Neither the top albums nor album info endpoints give us the album’s release year. This doesn’t impact the algorithm, I just thought it was odd.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All told, to implement this algorithm on our own means we would have to make an insane amount of requests just to get incomplete or missing data. If you think that doesn’t sound like it’s worth it, you’re right. And while I didn’t use it, &lt;a href=&quot;https://gist.github.com/lowmess/f3c03b1a6fe50ee04622706047f8a654&quot;&gt;you can see an implementation here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Last.fm still ranks albums wrong, though.&lt;/p&gt;</content:encoded></item><item><title>Customizing Tachyons</title><link>https://lowmess.com/blog/customizing-tachyons/</link><guid isPermaLink="true">https://lowmess.com/blog/customizing-tachyons/</guid><description>Tachyons is an insanely powerful atomic CSS library with sane defaults. But sane isn&amp;apos;t perfect for every project.</description><pubDate>Mon, 27 Nov 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;http://tachyons.io/&quot;&gt;Tachyons&lt;/a&gt;, if you haven’t heard, is incredible. It’s often called an atomic (or functional) CSS library, but if you spend enough time with it, you come to realize it’s something else entirely: a powerful and expressive framework for defining and consuming design system primitives.&lt;/p&gt;
&lt;p&gt;The problem, then, is that not every design system will use the same primitives. While Tachyons has some great default settings to help you achieve just about any design you please, you often need to tweak some of those properties: a different color palette, or type scale, or you have sizing needs outside of the scope of the defaults. How should you go about customizing this CSS file that has nearly every property:value pair in CSS?&lt;/p&gt;
&lt;h2 id=&quot;kiss-stupid&quot;&gt;K.I.S.S., Stupid&lt;/h2&gt;
&lt;p&gt;The simplest solution is the most oft-overlooked: just frickin’ copy the files into your project and start hacking away. The good folks over at Tachyons HQ even cooked up a &lt;a href=&quot;https://github.com/tachyons-css/tachyons-cli&quot;&gt;CLI&lt;/a&gt; so you don’t have to goof around with a build process.&lt;/p&gt;
&lt;h2 id=&quot;get-sassy&quot;&gt;Get Sassy&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;http://sass-lang.com/&quot;&gt;Sass&lt;/a&gt; has come to be the defacto CSS preprocessor. It brings a bunch of goodies to CSS: nesting, functions, and of course, variables. This makes it a great way to consume and customize libraries. And wouldn’t you know it, there’s an &lt;a href=&quot;https://github.com/tachyons-css/tachyons-sass&quot;&gt;official Sass fork&lt;/a&gt; of Tachyons ready to be brought into the fold of your Sass projects.&lt;/p&gt;
&lt;h2 id=&quot;deliver-some-packages&quot;&gt;Deliver Some Packages&lt;/h2&gt;
&lt;p&gt;Did you know that nearly every Tachyons module is &lt;a href=&quot;https://www.npmjs.com/browse/keyword/tachyons&quot;&gt;available independently on npm&lt;/a&gt;? If you only need to customize a few of them (like, say, the typography scale), you can simply replace the default modules for your custom ones. The downside to this is that you have to import every single module you want to use into your project, but the folks over at &lt;a href=&quot;https://github.com/eggheadio/tachyons-egghead&quot;&gt;Egghead don’t seem to mind&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;its-your-property-do-what-you-want-with-it&quot;&gt;It’s Your Property, Do What You Want With It&lt;/h2&gt;
&lt;p&gt;Much like Sass before it, CSS itself is getting variable functionality (and &lt;a href=&quot;https://caniuse.com/#feat=css-variables&quot;&gt;browser support is pretty good&lt;/a&gt; too). Wouldn’t it be great if Tachyons used this new syntax to define all of the properties we’d like to update? Meet &lt;a href=&quot;https://github.com/tachyons-css/tachyons-custom&quot;&gt;&lt;code&gt;tachyons-custom&lt;/code&gt;&lt;/a&gt;, who I’m sure you saw standing just off stage-right over there. Simply include it in your project, and because CSS custom properties follow the cascade, any of your variables will overwrite the Tachyons defaults.&lt;/p&gt;
&lt;h2 id=&quot;generate-the-future-today&quot;&gt;Generate the Future, Today&lt;/h2&gt;
&lt;p&gt;Tachyons HQ is hard at work developing a &lt;a href=&quot;https://github.com/tachyons-css/tachyons-generator&quot;&gt;Tachyons Generator&lt;/a&gt;. This tool allows you to describe your Tachyons variables in JSON and have a full-on version of Tachyons spit back out at you. This is the tool I am most excited about, because it has the potential to completely automate all of the other customization options we just went over while &lt;em&gt;also&lt;/em&gt; being more expressive and powerful. And that’s a beautiful thing.&lt;/p&gt;</content:encoded></item></channel></rss>