<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  
  <title>Zach Leatherman</title>
  <subtitle>A web development blog written by @zachleat.</subtitle>
  <link href="https://www.zachleat.com/web/feed/atom.xml" rel="self" />
  <link href="https://www.zachleat.com/" />
  <updated>2026-09-21T05:00:00Z</updated>
  <id>https://www.zachleat.com/</id>
  <author>
    <name>Zach Leatherman</name>
  </author>
  <entry>
    <title>“AI, make the website good”</title>
    <link href="https://www.zachleat.com/web/ai-websites/" />
    <updated>2026-09-21T05:00:00Z</updated>
    <id>https://www.zachleat.com/web/ai-websites/</id>
    <content type="html">&lt;style&gt;
.iframe--card {
	margin-block: 15vh;
}
&lt;/style&gt;
&lt;p&gt;Millions of &lt;em&gt;Large-Language-Model&lt;/em&gt;-generated words have been spilled on craftsmanship in this &lt;strong&gt;gilded-age of software development&lt;/strong&gt;. We now have agents to plan how code will be written, agents to write code, agents to review code, agents to orchestrate and dispatch tasks to other more (or less) capable subagents (these heavily subsidized tokens aren’t going to pay for themselves).&lt;/p&gt;
&lt;iframe class=&quot;iframe--card&quot; src=&quot;https://www.speedlify.dev/site/x-ai/filmstrip/&quot; title=&quot;x.ai filmstrip&quot; width=&quot;100%&quot; height=&quot;360&quot; style=&quot;border: 0&quot; loading=&quot;lazy&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;I’ve been in this game long enough to recognize self-congratulatory tooling improvements for what they are: automated simplicity (aka &lt;em&gt;can-kicking complexity&lt;/em&gt;) from software companies claiming to be at the bleeding edge of technique and application.&lt;/p&gt;
&lt;iframe class=&quot;iframe--card&quot; src=&quot;https://www.speedlify.dev/site/openai-com/filmstrip/&quot; title=&quot;openai.com filmstrip&quot; width=&quot;100%&quot; height=&quot;360&quot; style=&quot;border: 0&quot; loading=&quot;lazy&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;I’m always left wondering (suspecting the answer with hopeful optimism that my hunch is wrong this time): do these workflow improvements actually improve how people (technical or otherwise) &lt;em&gt;experience&lt;/em&gt; software? Certainly &lt;strong&gt;we have more software&lt;/strong&gt;. But is it better?&lt;/p&gt;
&lt;iframe class=&quot;iframe--card&quot; src=&quot;https://www.speedlify.dev/site/mistral-ai/filmstrip/&quot; title=&quot;mistral.ai filmstrip&quot; width=&quot;100%&quot; height=&quot;360&quot; style=&quot;border: 0&quot; loading=&quot;lazy&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;Front-end web developers have lived through about fifteen years of the &lt;em&gt;single-page-application&lt;/em&gt;-era smokescreen, sold as though improvements to developer experience would cause the reanimated corpse of Ronald Reagan himself to ask you for commit access to ship trickle-down improvements to user experience.&lt;/p&gt;
&lt;p&gt;Are we repeating the same playbook (on a more accelerated pace)? Certainly we’ve seen numerous recent anecdotal examples of software becoming &lt;em&gt;less&lt;/em&gt; reliable over time (GitHub is the most recent scapegoat I’ve seen, but on my read they seem to be a second-order victim more than anything) &lt;em&gt;(seems like a great time to be in the Monitoring/Status Page business).&lt;/em&gt;&lt;/p&gt;
&lt;iframe class=&quot;iframe--card&quot; src=&quot;https://www.speedlify.dev/site/www-perplexity-ai/filmstrip/&quot; title=&quot;perplexity.ai filmstrip&quot; width=&quot;100%&quot; height=&quot;360&quot; style=&quot;border: 0&quot; loading=&quot;lazy&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;With these seismic changes, I will continue to make the argument that craft defined as &lt;strong&gt;caring about the quality of output&lt;/strong&gt; (independent of the tools used to generate the output) is &lt;strong&gt;more important than ever&lt;/strong&gt;. Caring about what you ship will always have &lt;strong&gt;real-world value&lt;/strong&gt; to &lt;s&gt;visitors/consumers/users&lt;/s&gt; &lt;strong&gt;real people&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Prefers-reduced-motion respecting web performance filmstrips from &lt;a href=&quot;https://www.speedlify.dev/group/ai/&quot;&gt;the AI category on Speedlify&lt;/a&gt;, a category boasting a failing median Core Web Vitals score with 12 out of 13 sites failing Lighthouse performance and Axe accessibility checks.&lt;/em&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Icon Fall Web Component</title>
    <link href="https://www.zachleat.com/web/icon-fall/" />
    <updated>2026-09-16T05:00:00Z</updated>
    <id>https://www.zachleat.com/web/icon-fall/</id>
    <content type="html">&lt;p&gt;I’ve been doing some spring/fall cleaning and found an unpublished follow-up component to &lt;a href=&quot;https://www.zachleat.com/web/snow-fall/&quot;&gt;&lt;code&gt;&amp;lt;snow-fall&amp;gt;&lt;/code&gt;&lt;/a&gt; (a web component to “snow” on your website — which also happens to work with emoji too). Originally it was created as an easter egg for &lt;a href=&quot;https://blog.fontawesome.com/&quot;&gt;Blog Awesome&lt;/a&gt; (click on the Font Awesome flag in the very bottom of the footer) but others have wanted to use it for more things!&lt;/p&gt;
&lt;p&gt;Presenting: &lt;code&gt;&amp;lt;icon-fall&amp;gt;&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://zachleat.github.io/icon-fall/demo.html&quot;&gt;Demo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/zachleat/icon-fall&quot;&gt;Source code on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It’s in use on this page (if you are on my web site and not in a feed reader). You can also install it with &lt;code&gt;npm install @zachleat/icon-fall&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;It has options for &lt;code&gt;count&lt;/code&gt; (attribute, number of icons) and a comma separated list of colors (via &lt;code&gt;--icon-fall-colors&lt;/code&gt; custom property).&lt;/p&gt;
&lt;script type=&quot;module&quot; src=&quot;https://www.zachleat.com/static/icon-fall.js&quot;&gt;&lt;/script&gt;
&lt;is-land on:media=&quot;(prefers-reduced-motion: no-preference)&quot;&gt;
	&lt;icon-fall count=&quot;20&quot; style=&quot;--icon-fall-colors: #548cd8,#f36945,#00a776&quot;&gt;
		&lt;svg class=&quot;z-icon&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;use href=&quot;#fab-fa-font-awesome&quot; xlink:href=&quot;#fab-fa-font-awesome&quot;&gt;&lt;/use&gt;&lt;/svg&gt;
		&lt;svg class=&quot;z-icon&quot; viewBox=&quot;0 0 640 512&quot;&gt;&lt;use href=&quot;#fab-fa-web-awesome&quot; xlink:href=&quot;#fab-fa-web-awesome&quot;&gt;&lt;/use&gt;&lt;/svg&gt;
		&lt;svg class=&quot;z-icon&quot; viewBox=&quot;0 0 448 512&quot;&gt;&lt;use href=&quot;#fab-fa-build-awesome&quot; xlink:href=&quot;#fab-fa-build-awesome&quot;&gt;&lt;/use&gt;&lt;/svg&gt;
	&lt;/icon-fall&gt;
&lt;/is-land&gt;</content>
  </entry>
  <entry>
    <title>Table of Contents Web Component</title>
    <link href="https://www.zachleat.com/web/table-of-contents/" />
    <updated>2026-09-15T05:00:00Z</updated>
    <id>https://www.zachleat.com/web/table-of-contents/</id>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;…and another one.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This little (zero CSS) web component has two modes: progressively enhance a hierarchy of server rendered &lt;code&gt;&amp;lt;ol&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;ul&amp;gt;&lt;/code&gt; elements (or conveniently client render them for you if they don’t exist) to highlight which are currently visible in the viewport.&lt;/p&gt;
&lt;p&gt;I pulled this code out of the &lt;a href=&quot;https://github.com/11ty/tugboat/blob/main/_components/table-of-contents.webc&quot;&gt;&lt;code&gt;table-of-contents.webc&lt;/code&gt;&lt;/a&gt; component from the &lt;a href=&quot;https://tugboat.11ty.dev/&quot;&gt;WebC Tugboat starter project&lt;/a&gt; for standalone use.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://zachleat.github.io/table-of-contents/demo.html&quot;&gt;&lt;strong&gt;Demo&lt;/strong&gt;&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;And a demo in the sidebar of this blog post on my website.&lt;/li&gt;
&lt;li&gt;And a server rendered demo on Blog Awesome, &lt;em&gt;e.g. &lt;a href=&quot;https://blog.fontawesome.com/font-awesome-7-3-release/&quot;&gt;Font Awesome 7.3 Is Here!&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/zachleat/table-of-contents&quot;&gt;Source code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.npmjs.com/package/@zachleat/table-of-contents&quot;&gt;&lt;code&gt;@zachleat/table-of-contents&lt;/code&gt; on npm&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can configure a &lt;code&gt;selector&lt;/code&gt; attribute to control the scope of the headings search. My site uses &lt;code&gt;main :is(h2, h3)&lt;/code&gt; but the default is &lt;code&gt;main :is(h2, h3, h4, h5, h6)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Again, all styles for this component are application styles. There is no component CSS on this one.&lt;/p&gt;
&lt;h2 id=&quot;installation&quot;&gt;Installation&lt;/h2&gt;
&lt;pre tabindex=&quot;0&quot;&gt;&lt;code&gt;npm install @zachleat/table-of-contents&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;some-sample-headings&quot;&gt;Some sample headings&lt;/h2&gt;
&lt;div class=&quot;card card--demo&quot;&gt;
&lt;h3 id=&quot;heading-3a&quot;&gt;Heading 3a&lt;/h3&gt;
&lt;h4 id=&quot;heading-4-via-3a&quot;&gt;Heading 4 (via 3a)&lt;/h4&gt;
&lt;h3 id=&quot;heading-3b&quot;&gt;Heading 3b&lt;/h3&gt;
&lt;h4 id=&quot;heading-4-via-3b&quot;&gt;Heading 4 (via 3b)&lt;/h4&gt;
&lt;h3 id=&quot;heading-3c&quot;&gt;Heading 3c&lt;/h3&gt;
&lt;/div&gt;</content>
  </entry>
  <entry>
    <title>Solar Eclipse Toggle Web Component (two state dark/light mode toggle)</title>
    <link href="https://www.zachleat.com/web/solar-eclipse/" />
    <updated>2026-09-14T05:00:00Z</updated>
    <id>https://www.zachleat.com/web/solar-eclipse/</id>
    <content type="html">&lt;p&gt;In &lt;a href=&quot;https://www.zachleat.com/web/two-state-dark-mode-toggle/&quot;&gt;Trying out a Two-state Dark Mode Toggle&lt;/a&gt; I summarized the Two-state Dark and Light mode Toggle discussion as I experimented with a  toggle in my personal website footer.&lt;/p&gt;
&lt;p&gt;After launch, &lt;a href=&quot;https://fediverse.zachleat.com/@cwilcox808@c.im/117259821195055457&quot;&gt;Curtis Wilcox left some good feedback to improve the accessibility of the component&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I decided to fold this into a reusable component so that I can benefit from this work on future websites (and you can use it too, I guess!). It’s called &lt;code&gt;@zachleat/solar-eclipse-toggle&lt;/code&gt; and you can find it &lt;a href=&quot;https://github.com/zachleat/solar-eclipse-toggle&quot;&gt;on GitHub&lt;/a&gt; or &lt;a href=&quot;https://www.npmjs.com/package/@zachleat/solar-eclipse-toggle&quot;&gt;on npm now&lt;/a&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://zachleat.github.io/solar-eclipse-toggle/demo.html&quot;&gt;&lt;strong&gt;Demo&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;…or in the footer of this website.&lt;/li&gt;
&lt;li&gt;…also in the footer on &lt;a href=&quot;https://www.speedlify.dev/&quot;&gt;Speedlify&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;My favorite parts about the component are that it shows an inert button pre-JS, incurs no CLS, is still functional when the (separate) CSS doesn’t load successfully (&lt;code&gt;[hidden]&lt;/code&gt; for the win), and all strings are inserted from light DOM for good internationalization.&lt;/p&gt;
&lt;p&gt;Hope it’s useful to others!&lt;/p&gt;
&lt;h2 id=&quot;installation&quot;&gt;Installation&lt;/h2&gt;
&lt;pre tabindex=&quot;0&quot;&gt;&lt;code&gt;npm install @zachleat/solar-eclipse-toggle&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  <entry>
    <title>Trying out a Two-state Dark Mode Toggle</title>
    <link href="https://www.zachleat.com/web/two-state-dark-mode-toggle/" />
    <updated>2026-09-11T05:00:00Z</updated>
    <id>https://www.zachleat.com/web/two-state-dark-mode-toggle/</id>
    <content type="html">&lt;div class=&quot;callout callout-update&quot;&gt;
	&lt;svg class=&quot;z-icon&quot; viewBox=&quot;0 0 576 512&quot;&gt;&lt;use href=&quot;#fas-fa-clock-rotate-left&quot; xlink:href=&quot;#fas-fa-clock-rotate-left&quot;&gt;&lt;/use&gt;&lt;/svg&gt;
	&lt;h3 id=&quot;update&quot;&gt;Update&lt;/h3&gt;
	&lt;p&gt;&lt;em&gt;I made some updates and released it as a web component: details
in &lt;a href=&quot;https://www.zachleat.com/web/solar-eclipse/&quot;&gt;Solar Eclipse Toggle, a Dark/Light mode
Toggle Web Component&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Lea Verou sparked some lovely discussions about &lt;a href=&quot;https://lea.verou.me/blog/2026/dark-mode-toggles/&quot;&gt;dark/light mode toggles&lt;/a&gt; by asking a simple question: do these form controls need three options for two user-facing states?&lt;/p&gt;
&lt;p&gt;You have &lt;em&gt;Dark&lt;/em&gt; mode (1). You have &lt;em&gt;Light&lt;/em&gt; mode (2). You have &lt;em&gt;System&lt;/em&gt; mode (inheriting your operating system preference) which is ultimately &lt;em&gt;Dark&lt;/em&gt; (1) or &lt;em&gt;Light&lt;/em&gt; mode (2). Typically the form controls have three options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;System&lt;/em&gt; (auto)&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Light&lt;/em&gt; (saved on device as an override)&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Dark&lt;/em&gt; (saved on device as an override)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In Lea’s proposal, you’d have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;System&lt;/em&gt; (auto)&lt;/li&gt;
&lt;li&gt;Opposite of &lt;em&gt;System&lt;/em&gt; (saved on device as an override)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Toggling back to system/auto should always clear any saved override preference.&lt;/p&gt;
&lt;p&gt;I’ve added a little toggle to the footer of my own site (noting when the system preference matches the current selection with &lt;code&gt;(auto)&lt;/code&gt;) and it looks like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;s&gt;&lt;code&gt;Dark (auto) [Toggle] Light&lt;/code&gt;&lt;/s&gt;&lt;/li&gt;
&lt;li&gt;&lt;s&gt;&lt;code&gt;Dark [Toggle] Light (auto)&lt;/code&gt;&lt;/s&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Use light theme&lt;/code&gt; (shown) and &lt;code&gt;Use dark theme (auto)&lt;/code&gt; for users with a &lt;em&gt;Dark&lt;/em&gt; system preference.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Use dark theme&lt;/code&gt; (shown) and &lt;code&gt;Use light theme (auto)&lt;/code&gt; for users with a &lt;em&gt;Light&lt;/em&gt; system preference.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I’m enjoying it so far!&lt;/p&gt;
&lt;p&gt;I should also note that &lt;a href=&quot;https://www.bram.us/2026/08/18/the-case-for-tri-state-dark-mode-toggles/&quot;&gt;Bramus did write a criticism&lt;/a&gt; of the approach, noting a special case for users that have their system preference switch automatically (which I do personally use). I do think the simpler control and the simpler interaction (every toggle has a noticeable visual effect on the page) is worth the tradeoff there (personally). The strongest counterargument is that &lt;strong&gt;one of the three options on the tri-state toggle has no visual effect at all.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Related: Lea’s reactions follow-up post: &lt;a href=&quot;https://lea.verou.me/blog/2026/dark-mode-toggles-2/&quot;&gt;The best dark mode toggle is probably none&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>I was tired of building 474 separate pages for Open Graph Images</title>
    <link href="https://www.zachleat.com/web/open-graph-easy-build/" />
    <updated>2026-09-09T05:00:00Z</updated>
    <id>https://www.zachleat.com/web/open-graph-easy-build/</id>
    <content type="html">&lt;p&gt;&lt;em&gt;Prior blog post (if you want the full context): &lt;a href=&quot;https://www.zachleat.com/web/automatic-opengraph/&quot;&gt;If I work really hard on my Open Graph Images, People will share my Blog Posts&lt;/a&gt; (2021 Aug)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Here’s the gist:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Open Graph images are those little pictures that show up when your site is shared on social media. […] Each individual blog post (on my website) has a dedicated Open Graph image page.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;These Open Graph pages had the URL shape &lt;code&gt;/web/:title/opengraph/&lt;/code&gt; and there were 474 of them.&lt;/p&gt;
&lt;p&gt;This was great from a purist’s perspective (in that the Open Graph pages were built similarly to how I build other websites) but my perspective has changed a bit. I don’t think that server rendering a static page for each of the 474 blog posts is necessary! 🫣&lt;/p&gt;
&lt;p&gt;Now I use &lt;a href=&quot;https://www.zachleat.com/opengraph/&quot;&gt;&lt;strong&gt;one &lt;code&gt;/opengraph/&lt;/code&gt; page&lt;/strong&gt;&lt;/a&gt; that &lt;strong&gt;client renders&lt;/strong&gt; &lt;em&gt;(the horror!)&lt;/em&gt; the appropriate metadata for the Open Graph image. Client rendering here is an appropriate tradeoff for build time because this page is still going through a Chromium-based screenshot service and the ultimate deliverable is a static image. This page isn’t intended to be consumed by human beings (unless they &lt;em&gt;really&lt;/em&gt; want to, I dunno, I’m not your dad).&lt;/p&gt;
&lt;p&gt;Note that the caching mechanism hasn’t changed: it still lives in the screenshot service and the &lt;em&gt;image&lt;/em&gt; content isn’t being regenerated from scratch (with my old static pages method or this new client rendered method).&lt;/p&gt;
&lt;p&gt;I now use this approach on a few places and it’s made implementing Open Graph support on websites much easier to build and manage:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.zachleat.com/opengraph/&quot;&gt;zachleat.com&lt;/a&gt; (this is the web site you’re on now)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://build.awesome.me/opengraph/&quot;&gt;build.awesome.me&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://speedlify.dev/opengraph/&quot;&gt;speedlify.dev&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Importantly, there is no freeform mechanism on these pages to modify the content of the images via URL parameter. There is no &lt;code&gt;?text=…&lt;/code&gt; to control the title text, it’s keyed off of the local page URL and that alone. I don’t want y’all to put whatever text you want next to my picture on my website (you have to do that on &lt;em&gt;your&lt;/em&gt; website 😘).&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Use Speedlify2 to Continuously Measure Website Performance</title>
    <link href="https://www.zachleat.com/web/speedlify2/" />
    <updated>2026-08-26T05:00:00Z</updated>
    <id>https://www.zachleat.com/web/speedlify2/</id>
    <content type="html">&lt;p&gt;&lt;a href=&quot;https://www.speedlify.dev/&quot;&gt;&lt;img src=&quot;https://www.zachleat.com/img/built/XeWs_v2xk7-1000.avif&quot; alt=&quot;Speedlify2 screenshot&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1000&quot; height=&quot;960&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;a href=&quot;https://www.speedlify.dev/&quot;&gt;&lt;svg class=&quot;z-icon&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;use href=&quot;#fas-fa-arrow-right&quot; xlink:href=&quot;#fas-fa-arrow-right&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;strong&gt;&lt;code&gt;speedlify.dev&lt;/code&gt;, now running Speedlify2&lt;/strong&gt;&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The year is 2020. Inspired by a lack of budget for a web performance monitoring tool, the &lt;a href=&quot;https://www.zachleat.com/web/speedlify/&quot;&gt;original Speedlify project&lt;/a&gt; &lt;em&gt;(Speedlify Classic)&lt;/em&gt; was shipped to continuously measure site performance. It ran &lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/&quot;&gt;Lighthouse&lt;/a&gt; against a predetermined list of sites and ranked their performance against each other. Over time, I started to add a few more things to Speedlify.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A &lt;a href=&quot;https://www.zachleat.com/web/lighthouse-in-footer/&quot;&gt;&lt;code&gt;&amp;lt;speedlify-score&amp;gt;&lt;/code&gt; Web Component&lt;/a&gt; was added to show scores on your web site.&lt;/li&gt;
&lt;li&gt;The &lt;a href=&quot;https://www.zachleat.com/web/eleventy-leaderboard-speedlify/&quot;&gt;Eleventy Leaderboards were migrated to use Speedlify&lt;/a&gt;, running 31 unique contests and testing 1208 unique websites.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;As the Leaderboards grew, the work involved in maintaining them scaled similarly. Due to infrastructure build time constraints (a 15 minute build limit) and limitations in how Speedlify executed its measurement step (all sites would run serially and a compare step ran when all measurements had been taken), the Leaderboards needed to run on my own hardware.&lt;/p&gt;
&lt;p&gt;Over six years, it’s pretty natural that some folks would let their websites and domains lapse. A few sites moved away to use other tools. All of which culminated in manual work to publish Leaderboard scores. You could probably see this reflected in how often the Leaderboards were published 🫣 — though in retrospect 31 runs in six years (~2 month cadence on adverage) isn’t bad!&lt;/p&gt;
&lt;h2 id=&quot;better-and-more-automated&quot;&gt;Better and more Automated&lt;/h2&gt;
&lt;p&gt;Regardless, I’m delighted to show off some huge updates and improvements have been made to the brand new Speedlify2. Changes which will ultimately save me a bunch of time and effort!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Speedlify2 is &lt;em&gt;now measuring &lt;strong&gt;1565 sites&lt;/strong&gt;&lt;/em&gt; continuously without requiring any manual work.&lt;/li&gt;
&lt;li&gt;Each category has its own cadence configuration, and the Built Awesome category is currently configured to visit sites once per week to measure and test.&lt;/li&gt;
&lt;li&gt;Everything is static and self contained, deployable via GitHub Actions to GitHub Pages. You can &lt;a href=&quot;https://github.com/zachleat/speedlify2/&quot;&gt;easily deploy your own Speedlify2&lt;/a&gt;. &lt;em&gt;(Run the reset script to start from scratch)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Measurements happen in small chunks in parallel on GitHub Actions. The measurement step is now decoupled from ranking. When the site builds, it pulls from the newest measurements for all steps and ranks them. This is far more robust!&lt;/li&gt;
&lt;li&gt;Sites that move away from Build Awesome (11ty) are now automatically put into an unranked Emeritus category. I went ahead and backfilled the 92 websites that have moved away since we started the showcase in 2018. Emeritus sites &lt;em&gt;can&lt;/em&gt; show up on the Perfect Scores ranking on the home page (though at time of writing only 3 of the 92 of them do).&lt;/li&gt;
&lt;li&gt;Instead of the classic 4-circle Lighthouse design, this adds additional circles for Core Web Vitals (&lt;strong&gt;field data alongside lab data&lt;/strong&gt;) and now explicitly shows the output of a more detailed and rigorous Axe CLI run. Lighthouse does &lt;em&gt;use&lt;/em&gt; Axe internally but doesn’t report everything (Speedlify2 does).&lt;/li&gt;
&lt;li&gt;Adds a new &lt;code&gt;&amp;lt;speedlify2-score&amp;gt;&lt;/code&gt; component.&lt;/li&gt;
&lt;li&gt;The production service has backwards API compatibility with any legacy &lt;code&gt;&amp;lt;speedlify-score&amp;gt;&lt;/code&gt; components using the old speedlify.dev instance as their data source (and all sites measured there are measured on the new speedlify.dev).&lt;/li&gt;
&lt;li&gt;Categories are no longer mutually exclusive. URLs can exist in multiple categories and the home page shows all of the perfect scoring sites across all categories. To avoid extra testing traffic, single measurements are re-used across categories.&lt;/li&gt;
&lt;li&gt;Adds a new randomly selected Perfect Site of the Day.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It might be interesting to look at the report of &lt;a href=&quot;https://www.speedlify.dev/site/www-speedlify-dev/&quot;&gt;Speedlify measuring Speedlify&lt;/a&gt;, which includes measurements before the production domain was moved from Speedlify (classic) to Speedlify2. We can see how the migration went!&lt;/p&gt;
&lt;h3 id=&quot;no-javascript-comparisons&quot;&gt;No-JavaScript Comparisons&lt;/h3&gt;
&lt;p&gt;Speedlify2 also shows screenshots for No-JavaScript and JavaScript-enabled, reporting the visual percentage difference between the two. This does not feeding into rankings (as it isn’t a foolproof measure) but it does have utility!&lt;/p&gt;
&lt;p&gt;Specifically, it can reveal sites that exclusively use client side rendering (&lt;a href=&quot;https://www.speedlify.dev/site/www-solidjs-com/#without-javascript&quot;&gt;&lt;code&gt;solidjs.com&lt;/code&gt; at ∆ 77.4%&lt;/a&gt;):&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.speedlify.dev/site/www-solidjs-com/#without-javascript&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://www.zachleat.com/img/built/T-QrMJHGzn-1000.avif&quot; sizes=&quot;(min-width: 75em) 44.5625em, (min-width: 61.25em) 40.6875em, (min-width: 41.25em) 36.8125em, 96vw&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://www.zachleat.com/img/built/T-QrMJHGzn-1000.jpeg&quot; alt=&quot;With and without JavaScript, the left panel is completely white&quot; width=&quot;1000&quot; height=&quot;517&quot;&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It can reveal sites that may have bugs in their dark/light mode switcher (&lt;a href=&quot;https://www.speedlify.dev/site/nuxt-com/#without-javascript&quot;&gt;&lt;code&gt;nuxt.com&lt;/code&gt; at ∆ 99.8%&lt;/a&gt;):&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.speedlify.dev/site/nuxt-com/#without-javascript&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://www.zachleat.com/img/built/-bGZ8jZ9pm-1000.avif&quot; sizes=&quot;(min-width: 75em) 44.5625em, (min-width: 61.25em) 40.6875em, (min-width: 41.25em) 36.8125em, 96vw&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://www.zachleat.com/img/built/-bGZ8jZ9pm-1000.jpeg&quot; alt=&quot;With and without JavaScript, the left panel is light themed and the right panel is dark themed&quot; width=&quot;1000&quot; height=&quot;522&quot;&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It can reveal sites that use an announcement bar that causes the entire page to layout shift (&lt;a href=&quot;https://www.speedlify.dev/site/svelte-dev/#without-javascript&quot;&gt;&lt;code&gt;svelte.dev&lt;/code&gt; at ∆ 36.2%&lt;/a&gt;):&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.speedlify.dev/site/svelte-dev/#without-javascript&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://www.zachleat.com/img/built/pVlj4VYSYG-1000.avif&quot; sizes=&quot;(min-width: 75em) 44.5625em, (min-width: 61.25em) 40.6875em, (min-width: 41.25em) 36.8125em, 96vw&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://www.zachleat.com/img/built/pVlj4VYSYG-1000.jpeg&quot; alt=&quot;With and without JavaScript, the right panel has an announcement bar and the content has shifted down&quot; width=&quot;1000&quot; height=&quot;529&quot;&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;highlighting-good-sites&quot;&gt;Highlighting Good Sites&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://www.speedlify.dev/group/11ty-community/&quot;&gt;Built Awesome Leaderboards&lt;/a&gt; are intended to be a very rigorous and competitive leaderboard. The sites that score well here are &lt;em&gt;very&lt;/em&gt; fast.&lt;/p&gt;
&lt;p&gt;But (similarly to the Eleventy Leaderboards before them) we &lt;strong&gt;don’t highlight bad scores&lt;/strong&gt;. The (exhaustively long) list is exclusive to sites with all green circles. With an exception for unknown Core Web Vitals, which requires a level of minimum production traffic in Chrome browsers to show data.&lt;/p&gt;
&lt;p&gt;Sites with yellow or red circles are shown in an overflow list (randomly ordered) with no scores visible. You can navigate through to check your site’s page individually and review the report to learn how you can improve for the next run.&lt;/p&gt;
&lt;p&gt;The other top level categories (&lt;a href=&quot;https://www.speedlify.dev/group/hosts/&quot;&gt;Web Hosts&lt;/a&gt;, &lt;a href=&quot;https://www.speedlify.dev/group/ssg/&quot;&gt;Site Generators&lt;/a&gt;, &lt;a href=&quot;https://www.speedlify.dev/group/builders/&quot;&gt;Website Builders&lt;/a&gt;, &lt;a href=&quot;https://www.speedlify.dev/group/package-managers/&quot;&gt;Package Managers&lt;/a&gt; et al) are for corporate accountability and do show poorly scoring sites. This is a separate tact we want to avoid for personal or individual websites. We can and should hold professional and paid websites to a higher standard!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Am I neglecting my open source projects?</title>
    <link href="https://www.zachleat.com/web/is-this-still-being-maintained/" />
    <updated>2026-08-10T05:00:00Z</updated>
    <id>https://www.zachleat.com/web/is-this-still-being-maintained/</id>
    <content type="html">&lt;p&gt;I have &lt;em&gt;a lot&lt;/em&gt; of open source projects (of varying sizes and shapes).&lt;/p&gt;
&lt;p&gt;At time of writing, I’m currently maintaining ×78 npm packages. Of those, 22 are super lightweight zero-dependency custom elements a.k.a. 🚾 Web Components (&lt;em&gt;love the future compatibility I get with very little maintenance required&lt;/em&gt;). In addition, I also have a few &lt;a href=&quot;https://www.zachleat.com/domains/&quot;&gt;active websites&lt;/a&gt; and starter/template projects that are open source too.&lt;/p&gt;
&lt;p&gt;I usually rotate maintenance on these and needed a way to quickly scan my little ecosystem of packages to know which ones needed love. Thus, a new little dashboard has appeared on my web site that I’m calling:&lt;/p&gt;
&lt;script type=&quot;module&quot; src=&quot;https://www.zachleat.com/static/browser-window.js&quot;&gt;&lt;/script&gt;
&lt;div&gt;&lt;browser-window mode=&quot;auto&quot; class=&quot;bw-site-theme&quot; icon url=&quot;https://www.zachleat.com/projects/&quot; shadow flush style=&quot;--bw-background: oklch(82.691% 0.09444 136.65);--bw-foreground: #000&quot;&gt;&lt;a href=&quot;https://www.zachleat.com/projects/&quot; class=&quot;favicon-optout&quot;&gt;&lt;img alt=&quot;Screenshot image for https://www.zachleat.com/projects/&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; class=&quot;&quot; src=&quot;https://screenshot.11ty.app/https%3A%2F%2Fwww.zachleat.com%2Fprojects%2F/opengraph/&quot; width=&quot;1200&quot; height=&quot;630&quot;&gt;&lt;/a&gt;&lt;/browser-window&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://www.zachleat.com/projects/&quot; class=&quot;primarylink&quot;&gt;&lt;em&gt;Is this still being maintained??&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The name pays homage to those &lt;s&gt;helpful&lt;/s&gt; passive aggressive comments folks sometimes leave on issue trackers (mirroring a fair question that perhaps I should be asking myself).&lt;/p&gt;
&lt;p&gt;Each project is given a Neglect score that represents how much love it needs. It factors popularity (via npm downloads and GitHub stars) and need (GitHub issues and pull requests, npm audits), and recency (npm publish and GitHub commit dates). It shows how many production dependencies (including transitive ones) that a project uses. It even has a little sparkline to give a little data visualization to how frequently packages are published.&lt;/p&gt;
&lt;h2 id=&quot;a-broader-look&quot;&gt;A broader look&lt;/h2&gt;
&lt;p&gt;The aggregate statistics there were genuinely surprising to me. &lt;em&gt;67 million downloads in the last year&lt;/em&gt; (though this is heavily influenced by dependencies I maintain in Build Awesome/11ty, which are counted separately).&lt;/p&gt;
&lt;p&gt;Over 1.1k published versions, 4.7k closed issues, and 2.2k pull requests merged! &lt;em&gt;(not all by me, of course)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Currently my most neglected package is the &lt;a href=&quot;https://github.com/11ty/eleventy-plugin-vue&quot;&gt;Eleventy Vue plugin&lt;/a&gt; (which owns 31 of my 42 total npm audits) and I’m actively deciding whether to revive it or retire it 🫣.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;All of my 77 other packages only have 11 total npm audit reports!&lt;/strong&gt; I’m feeling extremely proud of that, which feels related to the work I’ve been doing to &lt;a href=&quot;https://www.zachleat.com/web/npm-security/&quot;&gt;lock down publishing workflows&lt;/a&gt; and &lt;a href=&quot;https://www.11ty.dev/blog/dependency-watch/&quot;&gt;reduce dependencies&lt;/a&gt;. Though that number may change over time, I now have an easy centralized place to check it. This report also makes it very obvious the relationship between dependency count and audit reports. &lt;a href=&quot;https://e18e.dev/&quot;&gt;Keep reducing those dependencies, y’all!&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The other result of monitoring this report is that I have archived a bunch of old projects that I don’t want to track, which has a nice cleansing feeling to it.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Back Build Awesome Pro and make it easier to build for the web!</title>
    <link href="https://www.zachleat.com/web/build-awesome-pro/" />
    <updated>2026-04-29T05:00:00Z</updated>
    <id>https://www.zachleat.com/web/build-awesome-pro/</id>
    <content type="html">&lt;p&gt;The &lt;a href=&quot;https://www.kickstarter.com/projects/fontawesome/build-awesome-pro?ref=43ttgb&quot;&gt;Build Awesome (11ty) Kickstarter (Final_FINAL_v2)&lt;/a&gt; is live! We’re trying to make it easier for &lt;em&gt;anyone&lt;/em&gt; to build, publish, and maintain web sites!&lt;/p&gt;
&lt;div class=&quot;primarylink&quot;&gt;
	Update: the &lt;a href=&quot;https://www.kickstarter.com/projects/fontawesome/build-awesome-pro?ref=43ttgb&quot;&gt;Kickstarter campaign has concluded&lt;/a&gt; but late pledging (and late pricing) is still available.
&lt;/div&gt;
&lt;hr&gt;
&lt;ul&gt;
&lt;li&gt;Go &lt;a href=&quot;https://www.kickstarter.com/projects/fontawesome/build-awesome-pro?ref=43ttgb&quot;&gt;directly to the Kickstarter&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Read more on the &lt;a href=&quot;https://www.11ty.dev/blog/build-awesome-pro/&quot;&gt;Blog&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Collaborative Editing as Progressive Enhancement</title>
    <link href="https://www.zachleat.com/web/collaborative-editing/" />
    <updated>2026-04-22T05:00:00Z</updated>
    <id>https://www.zachleat.com/web/collaborative-editing/</id>
    <content type="html">&lt;p&gt;We’re ramping up again to launch the &lt;a href=&quot;https://www.kickstarter.com/projects/fontawesome/build-awesome-pro?ref=43ttgb&quot;&gt;Build Awesome (11ty) Kickstarter Final_FINAL_v2 on &lt;strong&gt;April 28, 2026&lt;/strong&gt;&lt;/a&gt; and in this post I make the case for a new web site builder can layer itself on top of your existing projects as a progressive enhancement. Infrastructure as progressive enhancement!&lt;/p&gt;
&lt;div class=&quot;primarylink&quot;&gt;
	&lt;is-land on:idle on:media=&quot;(prefers-reduced-motion: no-preference)&quot;&gt;&lt;a href=&quot;https://www.kickstarter.com/projects/fontawesome/build-awesome-pro?ref=43ttgb&quot;&gt;Launching &lt;time-difference live units=&quot;milliseconds&quot; mode=&quot;countdown&quot;&gt;&lt;time datetime=&quot;2026-04-28T15:00:00Z&quot;&gt;April 28&lt;/time&gt;&lt;/time-difference&gt;.&lt;/a&gt;&lt;/is-land&gt;
&lt;/div&gt;</content>
  </entry>
</feed>