<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://bridgetownrb.com/" version="2.1.0">Bridgetown</generator><link href="https://thathtml.blog/feed.xml" rel="self" type="application/atom+xml" /><link href="https://thathtml.blog/" rel="alternate" type="text/html" /><updated>2025-12-24T09:06:44-08:00</updated><id>https://thathtml.blog/feed.xml</id><title type="html">That HTML Blog</title><subtitle>The fresh place to be for posts about developing websites &amp; applications using “vanilla” &amp; standards-adjacent web technologies</subtitle><author><name>Jared White</name><uri>https://jaredwhite.com</uri></author><entry><title type="html">The Web Platform is a Triumph of Object-Oriented Programming</title><link href="https://thathtml.blog/2025/12/the-dom-is-a-triumph-of-object-oriented-programming/" rel="alternate" type="text/html" title="The Web Platform is a Triumph of Object-Oriented Programming" /><published>2025-12-18T10:18:13-08:00</published><updated>2025-12-18T10:18:13-08:00</updated><id>repo://posts.collection/_posts/2025-12/2025-12-18-the-dom-is-a-triumph-of-object-oriented-programming.md</id><content type="html" xml:base="https://thathtml.blog/2025/12/the-dom-is-a-triumph-of-object-oriented-programming/">&lt;p&gt;In a past life, I wrote an article called &lt;a href=&quot;https://www.spicyweb.dev/html-serialized-object-graph/&quot;&gt;HTML is a Serialized Object Graph and That Changes Everything&lt;/a&gt; which made the case why understanding the &lt;em&gt;declarative&lt;/em&gt; nature of HTML as an object graph which sits alongside JavaScript’s &lt;em&gt;imperative&lt;/em&gt; DOM API is so crucial to becoming a well-rounded web developer who prioritizes performance, accessibility, efficiency, and future-proofing.&lt;/p&gt;

&lt;p&gt;I want to continue unpacking some of those concepts, namely efficiency, because every so often I come across a troubling perspective: that &lt;em&gt;Object-Oriented Programming (OOP)&lt;/em&gt; is somehow “bad” and therefore the DOM is bad because it’s based on OOP.&lt;/p&gt;

&lt;p&gt;Let’s be very clear here, and I consider this statement to be wholly undeniable:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Web Platform Would Not Exist Without OOP.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It’s not just that web APIs often take advantage of OOP. &lt;em&gt;They would not exist without OOP!&lt;/em&gt; Virtually every web API you have ever used is designed specifically within the OOP paradigm. And every entity in the lifecycle of a web page is based on objects. These &lt;em&gt;objects&lt;/em&gt; are instances of &lt;em&gt;classes&lt;/em&gt; and in a great many cases these classes are subclasses of other classes. This means that key functionality is &lt;strong&gt;spread across a class hierarchy&lt;/strong&gt;. That isn’t a bug, it’s a feature! And this means that it’s absurd to think that to write a web app you need to reach for a “framework”. &lt;strong&gt;The Web Platform &lt;em&gt;is&lt;/em&gt; a framework.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here’s a list of many of Web Platform classes you have used (or the libraries you import use):&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;Window&lt;/code&gt; – this is available as a “global” &lt;code class=&quot;highlighter-rouge&quot;&gt;window&lt;/code&gt; instance which is a &lt;em&gt;window&lt;/em&gt; (see what I did there?) into many other web APIs. In addition, when you directly call &lt;code class=&quot;highlighter-rouge&quot;&gt;screen&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;location&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;document&lt;/code&gt;, and some other global objects, you are actually accessing them via &lt;code class=&quot;highlighter-rouge&quot;&gt;window&lt;/code&gt;. This is also true of a great many “functions”…for example, did you know that &lt;code class=&quot;highlighter-rouge&quot;&gt;setTimeout&lt;/code&gt; is actually a method of the &lt;code class=&quot;highlighter-rouge&quot;&gt;window&lt;/code&gt; object? &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Window&quot;&gt;(MDN Docs)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;Screen&lt;/code&gt; – this is a also a global instance which provides you with a number of properties describing the web browser’s display. &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Screen&quot;&gt;(MDN Docs)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;Storage&lt;/code&gt; – when you access either the &lt;code class=&quot;highlighter-rouge&quot;&gt;localStorage&lt;/code&gt; or &lt;code class=&quot;highlighter-rouge&quot;&gt;sessionStorage&lt;/code&gt; property of &lt;code class=&quot;highlighter-rouge&quot;&gt;window&lt;/code&gt;, you are accessing an instance of &lt;code class=&quot;highlighter-rouge&quot;&gt;Storage&lt;/code&gt;. This is why the public API is the same for these two types of storage even though the &lt;em&gt;internals&lt;/em&gt; (and thus behavior) are a bit different. &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Storage&quot;&gt;(MDN Docs)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;Document&lt;/code&gt; – now here’s where things get even more interesting. When you access the &lt;code class=&quot;highlighter-rouge&quot;&gt;document&lt;/code&gt; property of &lt;code class=&quot;highlighter-rouge&quot;&gt;window&lt;/code&gt;, you are accessing the current document that’s being loaded and displayed (specifically an &lt;code class=&quot;highlighter-rouge&quot;&gt;HTMLDocument&lt;/code&gt; instance). However, you can create new documents! It’s actually possible just to write &lt;code class=&quot;highlighter-rouge&quot;&gt;const newDoc = new Document()&lt;/code&gt; and &lt;em&gt;boom!&lt;/em&gt; You have a new instance of the &lt;code class=&quot;highlighter-rouge&quot;&gt;Document&lt;/code&gt; class, with all of the properties &amp;amp; methods thereof. (Typically you’ll create a document using a parser, such as the static class method &lt;code class=&quot;highlighter-rouge&quot;&gt;Document.parseHTMLUnsafe&lt;/code&gt; which returns a new &lt;code class=&quot;highlighter-rouge&quot;&gt;HTMLDocument&lt;/code&gt; instance.) The &lt;code class=&quot;highlighter-rouge&quot;&gt;Document&lt;/code&gt; class is itself a subclass of &lt;code class=&quot;highlighter-rouge&quot;&gt;Node&lt;/code&gt;. Every part of the DOM ultimately is a subclass (of a subclass, of a subclass) of &lt;code class=&quot;highlighter-rouge&quot;&gt;Node&lt;/code&gt;, such as &lt;code class=&quot;highlighter-rouge&quot;&gt;Element&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;Comment&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;Attr&lt;/code&gt;, and &lt;code class=&quot;highlighter-rouge&quot;&gt;Text&lt;/code&gt;. But &lt;code class=&quot;highlighter-rouge&quot;&gt;Node&lt;/code&gt; itself is actually a subclass of &lt;code class=&quot;highlighter-rouge&quot;&gt;EventTarget&lt;/code&gt;, which is the ultimate abstract superclass. Even &lt;code class=&quot;highlighter-rouge&quot;&gt;Window&lt;/code&gt; is a subclass of &lt;code class=&quot;highlighter-rouge&quot;&gt;EventTarget&lt;/code&gt;. This is how (and why) the entire events system works in the browser! &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Document&quot;&gt;(MDN Docs)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;Request/Response&lt;/code&gt; – when you access the &lt;code class=&quot;highlighter-rouge&quot;&gt;fetch&lt;/code&gt; method of &lt;code class=&quot;highlighter-rouge&quot;&gt;window&lt;/code&gt;, you probably provide a URL and perhaps some additional settings like the HTTP method, headers, a body payload, etc. But that’s simply shorthand for creating a new instance of the &lt;code class=&quot;highlighter-rouge&quot;&gt;Request&lt;/code&gt; class. And you can actually do that! &lt;code class=&quot;highlighter-rouge&quot;&gt;fetch(params)&lt;/code&gt; is the same as &lt;code class=&quot;highlighter-rouge&quot;&gt;const request = new Request(params); fetch(request)&lt;/code&gt;. Which is pretty handy because that means you can pass &lt;code class=&quot;highlighter-rouge&quot;&gt;Request&lt;/code&gt; objects around, save them, modify them, and do whatever you want with them before passing them to &lt;code class=&quot;highlighter-rouge&quot;&gt;fetch&lt;/code&gt;. The return value of &lt;code class=&quot;highlighter-rouge&quot;&gt;fetch&lt;/code&gt; is, well you guessed it! An instance of the &lt;code class=&quot;highlighter-rouge&quot;&gt;Response&lt;/code&gt; class. When you call &lt;code class=&quot;highlighter-rouge&quot;&gt;.json()&lt;/code&gt; or &lt;code class=&quot;highlighter-rouge&quot;&gt;.text()&lt;/code&gt; after getting the response, those are methods on &lt;code class=&quot;highlighter-rouge&quot;&gt;Response&lt;/code&gt;. &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API&quot;&gt;(MDN Docs)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’re ever wondering what the superclass is (if any) for a particular class, you can write &lt;code class=&quot;highlighter-rouge&quot;&gt;Object.getPrototypeOf(ClassNameHere)&lt;/code&gt; in the console. All of the native HTML elements types, such as &lt;code class=&quot;highlighter-rouge&quot;&gt;HTMLDialogElement&lt;/code&gt;, will be subclasses of &lt;code class=&quot;highlighter-rouge&quot;&gt;HTMLElement&lt;/code&gt;. This is why when you write your own custom element, you write &lt;code class=&quot;highlighter-rouge&quot;&gt;class MyWebComponent extends HTMLElement&lt;/code&gt;. You are subclassing the &lt;em&gt;same&lt;/em&gt; superclass as all the other elements in HTML!&lt;/p&gt;

&lt;p&gt;You can also &lt;em&gt;monkey patch&lt;/em&gt; classes and objects. This is quite common in many OOP paradigms, but generally frowned upon in the context of web/browser APIs because of the fear of future breakage. Nevertheless, if you know what you’re doing and choose a suitable naming convention, I say go for it! For example, &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Response#fetching_an_image&quot;&gt;there’s an example on MDN&lt;/a&gt; of how to get a blob URL from a fetched image resource which you can then pass along to an &lt;code class=&quot;highlighter-rouge&quot;&gt;img&lt;/code&gt; tag. Let’s monkey patch &lt;code class=&quot;highlighter-rouge&quot;&gt;Response&lt;/code&gt; to make this a little easier!&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// Monkey patch&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;Response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;prototype&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;userlandBlobURL&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;blob&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;blob&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;createObjectURL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;blob&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Test it out!&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;resp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;https://placehold.co/600x400&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;blobURL&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;resp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;userlandBlobURL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// &apos;blob:https://example.com/bc10ebbc-d1bc-44fa-a23e-3bf398061502&apos;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;img&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;img&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;img&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;src&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;blobURL&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;img&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Perhaps JavaScript should have some kind of “blessed” naming convention for “custom methods” the way HTML does for custom elements (&lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;you-must-use-dashes&amp;gt;&lt;/code&gt;). At any rate, I chose &lt;code class=&quot;highlighter-rouge&quot;&gt;userland&lt;/code&gt; as a prefix in this example. YMMV!&lt;/p&gt;

&lt;p&gt;To go back to the word I used above, &lt;em&gt;efficiency&lt;/em&gt;, the beauty of the web platform is once you learn these class hierarchies and the various relationships between the objects in the &lt;strong&gt;framework&lt;/strong&gt; that is built into the browser, you are well on your way to understanding how to build in a high-quality fashion. As I’ve mentioned many times, the reason you don’t want to spray “div tag soup” all over your users’ download web pages is because every tag is an object instance. Got 5 &lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;div&amp;gt;&lt;/code&gt;s? That’s five &lt;code class=&quot;highlighter-rouge&quot;&gt;HTMLDivElement&lt;/code&gt; object instances loaded into memory. Got 5000 &lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;div&amp;gt;&lt;/code&gt;s? That’s &lt;em&gt;five thousand&lt;/em&gt; &lt;code class=&quot;highlighter-rouge&quot;&gt;HTMLDivElement&lt;/code&gt; object instances loaded into memory. Thankfully browsers are themselves marvels of efficiency, written in low-level C++, so even then it might not be the end of the world. But don’t be an asshole and assume &lt;em&gt;your&lt;/em&gt; tab is the only tab running on a machine.&lt;/p&gt;

&lt;p&gt;Imagine a world where &lt;em&gt;every damn browser tab&lt;/em&gt; has an app loaded in it with an incredibly over-engineered and overwrought DOM littered with needless element instances in a sprawling in-memory object graph. Sadly we don’t need to imagine it because that world is already here. &lt;strong&gt;But that doesn’t mean we need to be OK with it.&lt;/strong&gt; We can push back and fight for a more considerate world, one where you have much more functionality written into far fewer objects because &lt;em&gt;you understand what these objects are and how they work&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don’t be caught flat-footed&lt;/strong&gt; the next time a frontend engineer asks you: “So, what are all the objects loaded into memory as your web application boots up? Describe them to me.” Oh dear, has no one ever asked you that before?!&lt;/p&gt;

&lt;p&gt;🚨 &lt;strong&gt;Web dev education still has a l-o-o-ong way to go…&lt;/strong&gt; 🫠🤪&lt;/p&gt;</content><author><name>Jared White</name><uri>https://jaredwhite.com</uri></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://thathtml.blog/images/that-html-blog-social.png" /><media:content medium="image" url="https://thathtml.blog/images/that-html-blog-social.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">The Nuances of JavaScript Typing using JSDoc</title><link href="https://thathtml.blog/2025/12/nuances-of-typing-with-jsdoc/" rel="alternate" type="text/html" title="The Nuances of JavaScript Typing using JSDoc" /><published>2025-12-02T20:07:57-08:00</published><updated>2025-12-02T20:07:57-08:00</updated><id>repo://posts.collection/_posts/2025-12/2025-12-02-nuances-of-typing-with-jsdoc.md</id><content type="html" xml:base="https://thathtml.blog/2025/12/nuances-of-typing-with-jsdoc/">&lt;p&gt;As someone who has worked extensively on codebases using TypeScript, as well as codebases using JavaScript, I am here to inform you: &lt;strong&gt;I greatly prefer JavaScript.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It’s not that I don’t like specifying types for my variables and function signatures. I do! In fact, &lt;em&gt;I like it so much &lt;a href=&quot;https://www.fullstackruby.dev/ruby-3-fundamentals/2021/03/01/static-typing-in-ruby-3-gives-me-a-headache/&quot;&gt;I even do it in Ruby&lt;/a&gt;.&lt;/em&gt; 😲&lt;/p&gt;

&lt;p&gt;But you see, what I don’t like is my types being part of “the code”. I believe code should be strictly about the behavior. What it’s called. What it does. The “metadata” surrounding the code—this is a &lt;em&gt;string&lt;/em&gt;, that is an &lt;em&gt;integer&lt;/em&gt;—makes perfect sense as part of documentation in the form of code comments. Because guess what? &lt;strong&gt;You should be documenting your code anyway.&lt;/strong&gt; Whoever said don’t write lots of comments in code is grossly mistaken.&lt;/p&gt;

&lt;p&gt;Yes indeed, it is my sincere belief that you should be documenting your functions, and your value objects, and your classes, and all sorts of other things as much as possible. (Without going overboard…usually a sentence or two is quite sufficient.) Which brings us to…&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://jsdoc.app&quot;&gt;JSDoc&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When it comes to documenting JavaScript, JSDoc is the way to go. Even if you don’t expressly use the tool to generate an API site (I actually have never done that personally!), your JSDoc comments are interpreted by a wide variety of tools and editors. Which brings us to…&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.typescriptlang.org/docs/handbook/intro-to-js-ts.html&quot;&gt;TypeScript&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;wait-if-you-prefer-javascript-why-are-you-talking-about-typescript&quot;&gt;Wait, if you prefer JavaScript, why are you talking about TypeScript??&lt;/h2&gt;

&lt;p&gt;Because TypeScript is how you type check JavaScript even if you’re writing JavaScript with JSDoc and not TypeScript. (Confused yet? 🥴)&lt;/p&gt;

&lt;p&gt;Here’s an example. To declare a new string variable in TypeScript proper, you might write something like this:&lt;/p&gt;

&lt;div class=&quot;language-ts highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;string&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;str&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Hello world&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;str&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;123&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// this will cause a type error!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;However, you can also get the exact same benefits in pure JavaScript by adding &lt;code class=&quot;highlighter-rouge&quot;&gt;// @ts-check&lt;/code&gt; as the first line of a &lt;code class=&quot;highlighter-rouge&quot;&gt;.js&lt;/code&gt; file and then specifying types via JSDoc:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// @ts-check&lt;/span&gt;

&lt;span class=&quot;cm&quot;&gt;/** @type {string} */&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;str&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;str&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Hello world&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;str&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;123&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// this will cause a type error!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you’re using an IDE like VSCode or Zed, you’ll likely see the type hints and errors show up automatically, but it also might be a good idea to &lt;code class=&quot;highlighter-rouge&quot;&gt;npm install typescript -D&lt;/code&gt; because you may want to run &lt;code class=&quot;highlighter-rouge&quot;&gt;tsc&lt;/code&gt; separately to generate TypeScript declaration files or to type check your files in a CI process. (In my &lt;code class=&quot;highlighter-rouge&quot;&gt;package.json&lt;/code&gt; I have a script which looks like this: &lt;code class=&quot;highlighter-rouge&quot;&gt;&quot;build:types&quot;: &quot;npx tsc&quot;&lt;/code&gt;.)&lt;/p&gt;

&lt;p&gt;You can configure how the type checking works by adding a &lt;code class=&quot;highlighter-rouge&quot;&gt;jsconfig.json&lt;/code&gt; file to your project’s root folder. Here’s one I use:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;compilerOptions&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;strictNullChecks&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;target&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;es2022&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;At the same time, you’ll &lt;em&gt;also&lt;/em&gt; probably need to add a &lt;code class=&quot;highlighter-rouge&quot;&gt;tsconfig.json&lt;/code&gt; file at some point, like so:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;Change&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;match&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;your&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;include&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;src/**/*&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;compilerOptions&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;Tells&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;TypeScript&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;JS&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;files&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;as&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;normally&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;they&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;are&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;ignored&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;as&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;files&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;allowJs&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;Generate&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;d.ts&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;files&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;declaration&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;This&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;compiler&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;should&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;only&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;d.ts&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;files&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;emitDeclarationOnly&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;Types&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;should&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;go&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;into&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;directory.&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;Removing&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;would&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;place&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;the&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.d.ts&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;files&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;the&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.js&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;files&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;outDir&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;types&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;go&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;js&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;using&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;IDE&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;functions&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;like&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Go to Definition&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;in&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;VSCode&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;declarationMap&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I know that might all sound like a lot, but I promise you once you get a project going with your editor and your CLI tooling, you can replicate that setup across countless more projects and it becomes second nature.&lt;/p&gt;

&lt;p&gt;Rather than continue to just &lt;em&gt;talk&lt;/em&gt; about using JSDoc for typing, let’s dive into some examples!&lt;/p&gt;

&lt;h2 id=&quot;jsdoc-in-the-wild&quot;&gt;JSDoc in the wild&lt;/h2&gt;

&lt;p&gt;Here’s a class constructor which accepts a number of arguments.&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ReciprocalProperty&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
 &lt;span class=&quot;cm&quot;&gt;/**
   *
   * @param {HTMLElement} element - element to connect
   * @param {string} name - property name
   * @param {(value: any) =&amp;gt; any} signalFunction - function to call to create a signal
   * @param {() =&amp;gt; any} effectFunction - function to call to establish an effect
   */&lt;/span&gt;
  &lt;span class=&quot;nf&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;element&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;signalFunction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;effectFunction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;element&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;element&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;determineType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// etc.&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As you can see, it’s fine to keep using &lt;code class=&quot;highlighter-rouge&quot;&gt;any&lt;/code&gt; at times when you really don’t “care” about the exact nature of the variable in question. And again, the nice thing about putting your type information in as part of the code documentation is…&lt;strong&gt;now you have documentation!&lt;/strong&gt; 🙌&lt;/p&gt;

&lt;p&gt;Here’s an example of specifying a variable type that is a JavaScript object (a “record” in TypeScript parlance) with typing for the key/value pairs:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cm&quot;&gt;/** @type {Record&amp;lt;string, ReciprocalProperty&amp;gt;} */&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;attrProps&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;element&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;_reciprocalProperties&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can also see here we’re calling &lt;code class=&quot;highlighter-rouge&quot;&gt;this.element[&quot;_reciprocalProperties&quot;]&lt;/code&gt; instead of &lt;code class=&quot;highlighter-rouge&quot;&gt;this.element._reciprocalProperties&lt;/code&gt; because TypeScript gets fussy about accessing properties it doesn’t know about. Using &lt;code class=&quot;highlighter-rouge&quot;&gt;[]&lt;/code&gt; syntax bypasses those complaints (just make sure you know what you’re doing!).&lt;/p&gt;

&lt;p&gt;Here’s an example of specifying a type inline as part of a &lt;code class=&quot;highlighter-rouge&quot;&gt;for…of&lt;/code&gt; loop:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;stop&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;of&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/** @type {StreetcarStatementElement[]} */&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;([...&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;children&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;stop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;operate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That particular syntax took me a while to figure out! 😅 When in doubt, put your inline &lt;code class=&quot;highlighter-rouge&quot;&gt;/** @type */&lt;/code&gt; declarations in front of a piece of code wrapped in parentheses. That usually does the trick.&lt;/p&gt;

&lt;p&gt;Here’s an example of specifying a type inline within a method signature:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;htmx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;defineExtension&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;streetcar&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;handleSwap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;swapStyle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;_target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/** @type {Element} */&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fragment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// etc.&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And here’s an example of importing just a type (aka not a standard JavaScript import) using &lt;code class=&quot;highlighter-rouge&quot;&gt;@typedef&lt;/code&gt; syntax:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cm&quot;&gt;/**
 * @typedef { import(&quot;./HostEffects.js&quot;).default } HostEffects
 */&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// later on…&lt;/span&gt;

&lt;span class=&quot;cm&quot;&gt;/**
 * @param {HostEffects}
 */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can also use &lt;code class=&quot;highlighter-rouge&quot;&gt;@typedef&lt;/code&gt; to define the equivalent of TypeScript’s &lt;code class=&quot;highlighter-rouge&quot;&gt;interface&lt;/code&gt; &lt;a href=&quot;https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html#typedef-callback-and-param&quot;&gt;which is documented here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And finally: every once in a while, you may just need to do something funky that TypeScript simply isn’t going to like, &lt;em&gt;and that’s OK!&lt;/em&gt; As the documentation states:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;TypeScript may offer you errors which you disagree with, in those cases you can ignore errors on specific lines by adding &lt;code class=&quot;highlighter-rouge&quot;&gt;// @ts-ignore&lt;/code&gt; or &lt;code class=&quot;highlighter-rouge&quot;&gt;// @ts-expect-error&lt;/code&gt; on the preceding line.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;javascript--jsdoc--tsc-should-be-the-industry-default&quot;&gt;JavaScript + JSDoc + tsc should be the industry default&lt;/h2&gt;

&lt;p&gt;I &lt;a href=&quot;https://thathtml.blog/2023/09/adam-argyle-typescript-smackdown/&quot;&gt;have stated&lt;/a&gt; on &lt;a href=&quot;https://thathtml.blog/2025/09/reciprocate-reactivity-for-html-web-components/#some-technical-details&quot;&gt;multiple occasions&lt;/a&gt; that I believe it has been a huge mistake for “TypeScript” to become a sort of industry default. I firmly encourage everyone I talk to to start writing real web open standard &lt;code class=&quot;highlighter-rouge&quot;&gt;.js&lt;/code&gt; files which don’t require any build steps or tooling to execute properly, all while utilizing the power combo of JSDoc + tsc to gain all of the benefits of type hints in IDEs and type checking in CI. It really is the best of both worlds, and the number of cases when you &lt;em&gt;must&lt;/em&gt; give in and start authoring &lt;code class=&quot;highlighter-rouge&quot;&gt;.ts&lt;/code&gt; files proper is vanishingly small.&lt;/p&gt;

&lt;p&gt;It’s possible there are certain frameworks you may need to use which essentially “require” you to author your code using TypeScript. In those instances, so be it. But if you have &lt;em&gt;any&lt;/em&gt; control over the shape of a project, I hope you’ll consider using good ol’ fashioned JavaScript. After all, it’s ECMAScript—not TypeScript—that is the &lt;em&gt;lingua franca&lt;/em&gt; of the web.&lt;/p&gt;</content><author><name>Jared White</name><uri>https://jaredwhite.com</uri></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://thathtml.blog/images/that-html-blog-social.png" /><media:content medium="image" url="https://thathtml.blog/images/that-html-blog-social.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Write Custom Extensions for htmx to Make it Your Own Personal Toolkit</title><link href="https://thathtml.blog/2025/11/htmx-extensions-your-personal-toolkit/" rel="alternate" type="text/html" title="Write Custom Extensions for htmx to Make it Your Own Personal Toolkit" /><published>2025-11-11T09:15:18-08:00</published><updated>2025-11-11T09:15:18-08:00</updated><id>repo://posts.collection/_posts/2025-11/2025-11-11-htmx-extensions-your-personal-toolkit.md</id><content type="html" xml:base="https://thathtml.blog/2025/11/htmx-extensions-your-personal-toolkit/">&lt;p&gt;&lt;strong&gt;Hot take:&lt;/strong&gt; the &lt;em&gt;killer feature&lt;/em&gt; of &lt;a href=&quot;https://htmx.org&quot;&gt;htmx&lt;/a&gt; which transforms it from a nice lil’ opinionated DSL for HTML-based reactivity to a &lt;em&gt;powerhouse&lt;/em&gt; full-stack framework with which you can build ambitious, even experimental application architectures is this:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://htmx.org/extensions/building/&quot;&gt;Extensions.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Unfortunately, if you follow the link above to “Building htmx extensions”, there’s virtually no documentation there. 😒&lt;/p&gt;

&lt;p&gt;So I’m guessing the answer to &lt;em&gt;Why didn’t anyone tell me about this sooner??&lt;/em&gt; is likely &lt;em&gt;Because nobody knows!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I’m here to rectify that situation; in particular, explain the ability for you to build your own &lt;strong&gt;custom swaps&lt;/strong&gt;.&lt;/p&gt;

&lt;h2 id=&quot;what-is-a-custom-swap-for-that-matter-what-is-a-swap&quot;&gt;What is a Custom Swap? (for that matter, what is a swap?!)&lt;/h2&gt;

&lt;p&gt;In &lt;a href=&quot;https://htmx.org/docs/#introduction&quot;&gt;htmx in a nutshell&lt;/a&gt;, the following example is provided:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;button&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;hx-post=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/clicked&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;hx-target=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;#parent-div&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;hx-swap=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;outerHTML&quot;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  Click Me!
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In UX-speak, this translates to: “As a user, when I click the &lt;code class=&quot;highlighter-rouge&quot;&gt;Click Me!&lt;/code&gt; button, I would like to see the new content appear in the box” (the “box” being a &lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;div id=&quot;parent-div&quot;&amp;gt;&lt;/code&gt; element somewhere on the page, and “content” being an HTML fragment returned by the server).&lt;/p&gt;

&lt;p&gt;htmx comes with &lt;a href=&quot;https://htmx.org/docs/#swapping&quot;&gt;a number of swap mechanisms&lt;/a&gt; at your disposal, including ones like &lt;code class=&quot;highlighter-rouge&quot;&gt;beforeend&lt;/code&gt; which is equivalent to &lt;code class=&quot;highlighter-rouge&quot;&gt;targetEl.append(anotherEl)&lt;/code&gt; and even &lt;code class=&quot;highlighter-rouge&quot;&gt;delete&lt;/code&gt; which ignores a response and simply deletes a target element.&lt;/p&gt;

&lt;p&gt;Let’s call these &lt;strong&gt;built-in swaps&lt;/strong&gt;. You can certainly accomplish a lot with only the built-in swaps, as evidenced by the growing popularity of htmx. However this leads us to the question at hand: &lt;em&gt;Can I create my own swap?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Yes!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The genius of htmx is that the &lt;code class=&quot;highlighter-rouge&quot;&gt;hx-swap&lt;/code&gt; attribute &lt;strong&gt;can be anything you want&lt;/strong&gt;. &lt;code class=&quot;highlighter-rouge&quot;&gt;hx-swap=&quot;bop&quot;&lt;/code&gt;? Sure. &lt;code class=&quot;highlighter-rouge&quot;&gt;hx-swap=&quot;snap-crackle-pop&quot;&lt;/code&gt; Absolutely!&lt;/p&gt;

&lt;p&gt;Yet the behavior you’ll end up with out-of-the-box is the same as &lt;code class=&quot;highlighter-rouge&quot;&gt;innerHTML&lt;/code&gt; because you haven’t defined what these new swap mechanisms are. &lt;strong&gt;That’s where extensions come in.&lt;/strong&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Let’s write the most simple extension imaginable. Assuming there’s an &lt;code class=&quot;highlighter-rouge&quot;&gt;htmx&lt;/code&gt; import or global defined, in your JavaScript entrypoint write:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;htmx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;defineExtension&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;bop&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;isInlineSwap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;swapStyle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;swapStyle&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;bop&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;handleSwap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;swapStyle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;_target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/** @type {Element} */&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fragment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;swapStyle&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;bop&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Bop!&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fragment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// log the server HTML fragment&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You’ll also need to add an &lt;code class=&quot;highlighter-rouge&quot;&gt;hx-ext&lt;/code&gt; attribute on your site layout’s body:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;body&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;hx-ext=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;bop&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now you can update the button example from above!&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;button&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;hx-post=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/whoop&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;hx-swap=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;bop&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Boop!&lt;span class=&quot;nt&quot;&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Clicking &lt;code class=&quot;highlighter-rouge&quot;&gt;Boop!&lt;/code&gt; will go through the normal htmx ritual of calling the server and parsing the incoming HTML into a DOM fragment. However this time, your custom swap will kick in and you’ll get a &lt;code class=&quot;highlighter-rouge&quot;&gt;Bop!&lt;/code&gt; console log. 🎉&lt;/p&gt;

&lt;p&gt;Let’s talk about &lt;strong&gt;boosting&lt;/strong&gt; for a moment. htmx &lt;a href=&quot;https://htmx.org/docs/#boosting&quot;&gt;provides an option&lt;/a&gt; where forms automatically submit using the AJAX technique so you can easily use swapping techniques. You can boost via the &lt;code class=&quot;highlighter-rouge&quot;&gt;hx-boost&lt;/code&gt; attribute either on a container of your page or on &lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;body&amp;gt;&lt;/code&gt; itself.&lt;/p&gt;

&lt;p&gt;However, once you do this, &lt;strong&gt;your browser URL will change&lt;/strong&gt; when htmx handles a request/response thus adding to your history. If you don’t want your custom swap to affect browser history and you don’t want to require users to fiddle with htmx’s &lt;a href=&quot;https://htmx.org/docs/#history&quot;&gt;history attribute&lt;/a&gt;, you can do so manually. Let’s add an event handler to our extension:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &lt;span class=&quot;nx&quot;&gt;onEvent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;htmx:beforeOnLoad&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/** @type {HTMLElement} */&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;elt&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;detail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;elt&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;elt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getAttribute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;hx-swap&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;bop&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;elt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;hasAttribute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;hx-push-url&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;elt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setAttribute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;hx-push-url&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This sets the &lt;code class=&quot;highlighter-rouge&quot;&gt;hx-push-url&lt;/code&gt; manually if it’s not already present on the element triggering the swap, thus indicating to htmx it shouldn’t mess with browser history.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;And…that’s all folks!&lt;/em&gt; If you’ve ever looked at a library like htmx and wondered &lt;em&gt;but what if I want to write my own code to deal with an incoming HTML fragment?&lt;/em&gt; (or am I the only weirdo who does this?! 😅), &lt;strong&gt;this is your solution.&lt;/strong&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p style=&quot;text-align: center; font-size: 200%&quot;&gt;&lt;a href=&quot;https://codepen.io/jaredcwhite/pen/pvyvZOY&quot; class=&quot;button&quot;&gt;DEMO TIME!&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;have-a-piece-of-toast&quot;&gt;Have a Piece of Toast&lt;/h2&gt;

&lt;p&gt;It wouldn’t be a fresh &lt;strong&gt;That HTML Blog&lt;/strong&gt; installment without a demo, so for this one I thought I’d come up with a fun little strategy of returning messages from the backend so the frontend can display a toast notification. Since my fav UI library Web Awesome sadly doesn’t come with toasts yet (unlike its Shoelace predecessor), I roped in the &lt;a href=&quot;https://github.com/simple-notify/simple-notify&quot;&gt;Simple Notify&lt;/a&gt; library which is actually pretty stellar.&lt;/p&gt;

&lt;p&gt;In a nutshell, my custom &lt;code class=&quot;highlighter-rouge&quot;&gt;toasts&lt;/code&gt; htmx extension pulls in HTML like this:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;output&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;toast-notification&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;data-title=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Success!&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  I&apos;m a toast from the server!
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/output&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And turns it into Notify-compatible JavaScript:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Notify&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;toast&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dataset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;success&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;toast&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dataset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;toast&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;textContent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;effect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;slide&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;speed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;400&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;toast&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dataset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;outline&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;right bottom&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Could you have come up with your own vanilla JS solution, perhaps &lt;code class=&quot;highlighter-rouge&quot;&gt;fetch&lt;/code&gt;ing some JSON instead and using that to populate the toast values? Sure…but then that wouldn’t be very htmx-y now would it?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The htmx ethos&lt;/strong&gt; is all about using HTML as the source of truth in your application. HTML fragments can be used verbatim to populate the DOM, but why not use HTML an an interchange format as well? After all, once you introduce custom elements into the mix, you’re practically writing XML using a schema you invent for the task at hand. Is that a bad thing? &lt;em&gt;I would say no!&lt;/em&gt; And &lt;a href=&quot;https://xslt.rip&quot;&gt;considering the recent outcry over removing XSLT from browsers&lt;/a&gt;, I’m not the only one. 😂&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So that’s htmx extensions.&lt;/strong&gt; What will you create with it? Hop on over to Human Web Collective’s &lt;a href=&quot;https://humansare.social/c/indie_web_dev&quot;&gt;indie_web_dev@humansare.social&lt;/a&gt; Forum or &lt;a href=&quot;https://discord.gg/CUuYVH7Qa9&quot;&gt;Discord&lt;/a&gt; to chat about this and a whole lot more!&lt;/p&gt;</content><author><name>Jared White</name><uri>https://jaredwhite.com</uri></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://thathtml.blog/images/that-html-blog-social.png" /><media:content medium="image" url="https://thathtml.blog/images/that-html-blog-social.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Add Terrific DX to Your UI Web Components with nimble-html</title><link href="https://thathtml.blog/2025/10/nimble-html-adds-great-dx-to-ui-components/" rel="alternate" type="text/html" title="Add Terrific DX to Your UI Web Components with nimble-html" /><published>2025-10-16T21:00:06-07:00</published><updated>2025-10-16T21:00:06-07:00</updated><id>repo://posts.collection/_posts/2025-10/2025-10-16-nimble-html-adds-great-dx-to-ui-components.md</id><content type="html" xml:base="https://thathtml.blog/2025/10/nimble-html-adds-great-dx-to-ui-components/">&lt;p&gt;Previously on &lt;strong&gt;That HTML Blog&lt;/strong&gt;, I wrote about &lt;a href=&quot;https://thathtml.blog/2025/09/reciprocate-reactivity-for-html-web-components/&quot;&gt;a new library I released called Reciprocate&lt;/a&gt;. The TL;DR is that it lets you add signal-based reactivity (both properties &amp;amp; attributes) to your otherwise vanilla web components (particularly handy for “HTML Web Components” aka custom elements with templates already rendered by the server and preexisting in the DOM).&lt;/p&gt;

&lt;p&gt;However, if you wanted to write more “traditional” frontend UI components with dynamic, declaratively-written templates defined in the custom element JS itself, you’d need to reach for something else. Fans of libraries such as Lit might wonder &lt;em&gt;Where is my &lt;code class=&quot;highlighter-rouge&quot;&gt;html&lt;/code&gt; function? I want my &lt;code class=&quot;highlighter-rouge&quot;&gt;html&lt;/code&gt; function!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And that’s where &lt;a href=&quot;https://github.com/lume/nimble-html&quot;&gt;nimble-html&lt;/a&gt; comes in!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Created by Joe Pea, it’s a 2.3kB minzipped library after my own heart because it does one thing and one thing only: give you an &lt;code class=&quot;highlighter-rouge&quot;&gt;html&lt;/code&gt; function. 🙌 Essentially it lets you author tagged template literals containing HTML and extra syntactic sugar to set properties and add event handlers (just like in Lit!), and then insert those templates to any place in the DOM. It doesn’t even have to be anything related to a custom element, but obviously that’s where my brain goes.&lt;/p&gt;

&lt;p&gt;In fact, as I read the details of how it works, it got me thinking: &lt;em&gt;could I take some Reciprocate demo components and rewrite the HTML templates using nimble-html?&lt;/em&gt;&lt;/p&gt;

&lt;p style=&quot;text-align: center; font-size: 200%&quot;&gt;&lt;a href=&quot;https://codepen.io/jaredcwhite/pen/vELWQPp&quot; class=&quot;button&quot;&gt;DEMO TIME!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Yes indeed, it works beautifully. With just a bit of boilerplate all tucked away in a &lt;code class=&quot;highlighter-rouge&quot;&gt;BaseElement&lt;/code&gt;, you can write a new component with DX which feels very familiar:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Counter&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BaseElement&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;observedAttributes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  
  &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;customElements&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;define&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;ui-counter&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nf&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;initialize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`
      &amp;lt;style&amp;gt;
      output {
        font-size: 200%;
        vertical-align: middle;
        margin-inline: .5rem;
      }
      &amp;lt;/style&amp;gt;
      &amp;lt;wa-button @click=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; variant=&quot;danger&quot;&amp;gt;- Dec&amp;lt;/wa-button&amp;gt;
      &amp;lt;output&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;lt;/output&amp;gt;
      &amp;lt;wa-button @click=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; variant=&quot;success&quot;&amp;gt;+ Inc&amp;lt;/wa-button&amp;gt;
    `&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Pretty awesome, right?! I love the idea that I could use Reciprocate alone to assist with some simple server-rendered HTML Web Components, and then if I needed more client-side smarts and lots of dynamic interactivity, I could simply import &lt;code class=&quot;highlighter-rouge&quot;&gt;html&lt;/code&gt; from nimble-html and we’re off and away.&lt;/p&gt;

&lt;p&gt;This really does prove out my thesis that the best way of thinking about web components isn’t that you want to adopt the One True Library to do it all. Rather, you should layer on small, self-contained APIs as needed and build up your own frontend stack that works for &lt;em&gt;your&lt;/em&gt; specific projects and use cases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ideally in the future, more and more of those APIs are provided by the browser itself.&lt;/strong&gt; In the meantime, we can reach for userland solutions which focus on doing one or two things very well.&lt;/p&gt;</content><author><name>Jared White</name><uri>https://jaredwhite.com</uri></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://thathtml.blog/images/that-html-blog-social.png" /><media:content medium="image" url="https://thathtml.blog/images/that-html-blog-social.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Give “fetch” a Bit More Oomph with “ffetch”</title><link href="https://thathtml.blog/2025/09/give-fetch-a-bit-more-oomph/" rel="alternate" type="text/html" title="Give “fetch” a Bit More Oomph with “ffetch”" /><published>2025-09-18T09:46:16-07:00</published><updated>2025-09-18T09:46:16-07:00</updated><id>repo://posts.collection/_posts/2025-09/2025-09-18-give-fetch-a-bit-more-oomph.md</id><content type="html" xml:base="https://thathtml.blog/2025/09/give-fetch-a-bit-more-oomph/">&lt;p&gt;I cannot sing the praises of &lt;code class=&quot;highlighter-rouge&quot;&gt;fetch&lt;/code&gt; loudly enough. As a frontend (and backend!) JavaScript developer, having ready access to this native web API where you can pull down HTML fragments or JSON data with a line or two of code is simply brilliant.&lt;/p&gt;

&lt;p&gt;But &lt;a href=&quot;https://blog.gaborkoos.com/posts/2025-09-13-Making-Your-Fetch-Requests-Production-Ready-With-Ffetch/&quot;&gt;Gabor Koos wants you to know&lt;/a&gt; there are a few things lacking in such a simplistic approach when it comes to shipping robust production-ready code:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;The network is unpredictable and you have to be ready for it. You better have answers to these questions: What happens when the network is slow or unreliable? What happens when the backend is down or returns an error? If you consume external API’s, what happens when you hit the rate limit and get blocked? How do you handle these scenarios gracefully and provide a good user experience?&lt;/p&gt;

  &lt;p&gt;Honestly, vanilla Fetch is not enough to handle these scenarios. You need to add a lot of boilerplate code to handle errors, retries, timeouts, caching, etc. This can quickly become messy and hard to maintain.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And the solution Gabor provides is a tiny 2.3KB wrapper around &lt;code class=&quot;highlighter-rouge&quot;&gt;fetch&lt;/code&gt; called, appropriately, &lt;code class=&quot;highlighter-rouge&quot;&gt;ffetch&lt;/code&gt;. Here are some of its features:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Timeouts&lt;/strong&gt; – per-request or global&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Retries&lt;/strong&gt; – exponential backoff + jitter&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Circuit breaker&lt;/strong&gt; – automatic failure protection&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Hooks&lt;/strong&gt; – logging, auth, metrics, request/response transformation&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Pending requests&lt;/strong&gt; – real-time monitoring of active requests&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Per-request overrides&lt;/strong&gt; – customize behavior on a per-request basis&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s what a sample request looks like:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;createClient&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;@gkoos/ffetch&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Create a client with timeout and retries&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;api&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;createClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;timeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;retries&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;retryDelay&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;attempt&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;attempt&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Math&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Make requests&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;api&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;https://api.example.com/users&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/gkoos/ffetch/blob/main/docs/examples.md&quot;&gt;There are many more usage examples here&lt;/a&gt;, including how to write your own API client class with a lot of built-in smarts. In this regard, &lt;code class=&quot;highlighter-rouge&quot;&gt;ffetch&lt;/code&gt; reminds me a bit of a Ruby gem I like to use when writing requests in a Ruby backend, &lt;a href=&quot;https://lostisland.github.io/faraday&quot;&gt;faraday&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Some of you may be wondering “well, if I’m going to use a library instead of vanilla &lt;code class=&quot;highlighter-rouge&quot;&gt;fetch&lt;/code&gt;, why not just use &lt;a href=&quot;https://axios-http.com&quot;&gt;axios&lt;/a&gt;?”&lt;/p&gt;

&lt;p&gt;Well, a couple of reasons I can think of. One is that &lt;code class=&quot;highlighter-rouge&quot;&gt;ffetch&lt;/code&gt; mirrors the &lt;code class=&quot;highlighter-rouge&quot;&gt;fetch&lt;/code&gt; API so you can use it as a “drop-in” replacement. Axios doesn’t even use &lt;code class=&quot;highlighter-rouge&quot;&gt;fetch&lt;/code&gt;, it wraps the older &lt;code class=&quot;highlighter-rouge&quot;&gt;XMLHTTPRequest&lt;/code&gt; API. Second, it comes in at a whopping 14KB minzipped—&lt;em&gt;that’s 600% larger&lt;/em&gt; than &lt;code class=&quot;highlighter-rouge&quot;&gt;ffetch&lt;/code&gt;. 🤯&lt;/p&gt;

&lt;p&gt;So yeah. My advice is always to use native web APIs until you outgrow them (and get sick of writing a ton of boilerplate), then see if you can reach for something which is as lightweight and “vanilla-adjacent” as possible to layer on a few more smarts. &lt;code class=&quot;highlighter-rouge&quot;&gt;ffetch&lt;/code&gt; appears to be a worthy candidate, and I look forward to trying it out on my next project.&lt;/p&gt;</content><author><name>Jared White</name><uri>https://jaredwhite.com</uri></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://thathtml.blog/images/that-html-blog-social.png" /><media:content medium="image" url="https://thathtml.blog/images/that-html-blog-social.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Announcing Reciprocate, a Sweet Solution for Making Your HTML Web Components Reactive</title><link href="https://thathtml.blog/2025/09/reciprocate-reactivity-for-html-web-components/" rel="alternate" type="text/html" title="Announcing Reciprocate, a Sweet Solution for Making Your HTML Web Components Reactive" /><published>2025-09-08T11:04:56-07:00</published><updated>2025-09-08T11:04:56-07:00</updated><id>repo://posts.collection/_posts/2025-09/2025-09-08-reciprocate-reactivity-for-html-web-components.md</id><content type="html" xml:base="https://thathtml.blog/2025/09/reciprocate-reactivity-for-html-web-components/">&lt;p&gt;Have you ever been chipping away on a vanilla web component when you began to wonder &lt;em&gt;“hey waitaminute…how do I make it so when I set this JavaScript property, the equivalent HTML attribute is updated? And if I set this attribute, the equivalent property is updated? Or when either is updated, my component will re-render?! Where are all the APIs for this stuff??”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Alas, there are none. 😥&lt;/p&gt;

&lt;p&gt;Why the native web platform doesn’t provide primitives for custom elements to act like, well, any other native element is a question for another time…because you need not worry about this strange oversight any longer! 🤓&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Introducing &lt;a href=&quot;https://codeberg.org/heartml/reciprocate&quot;&gt;Reciprocate&lt;/a&gt;&lt;/strong&gt;. This has been a long time coming…&lt;/p&gt;

&lt;p&gt;(Feel free to skip down to the explainer bits, or keep reading for a little backstory!)&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Approximately three years ago, I first started noodling around with the then brand-new &lt;strong&gt;Signals&lt;/strong&gt; package from the Preact team. It felt so whiz-bang and cool, I couldn’t even wrap my head around it at first. Once I reverse-engineered it enough that I could understand it…&lt;em&gt;jeeezzus, what a mind job&lt;/em&gt;. 😎&lt;/p&gt;

&lt;p&gt;After a bit of time passed, it started to dawn on me that this could prove to be a great underlying foundation for handling “fine-grained reactivity” within web components, as a lightweight alternative to the &lt;strong&gt;Lit&lt;/strong&gt; base element (which I had been using a lot at the time).&lt;/p&gt;

&lt;p&gt;I spent 2023 on-and-off iterating on a solution and for a brief while released it as part of some other experiments I’d been working on with and alongside Lit. Yet I eventually arrived at the decision to embark on development of my &lt;em&gt;own&lt;/em&gt; custom element-based component format which would also offer server-based rendering and various neat-o features like HTML Modules. This umbrella project I called ❤️ &lt;strong&gt;Heartml&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;As I was working on building up that feature set more and more, I chickened out on my original goal of offering a series of “unbundled” micro-libraries which people could simply add to their own vanilla web components. I figured if anybody would care to adopt Heartml, it’d have to be an install-and-you’re-done turnkey solution.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;However…&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Fast forward to summer 2025, and I was feeling incredibly uncertain about my approach with Heartml. One of the stated goals of the Lit project is that as more native web APIs offer good “DX” and helpful ergonomics for web developers, Lit itself would need to ship less code. I love that philosophy! With Heartml, I started to feel like I should take that concept even a step further by making all of its userland features &lt;em&gt;opt-in&lt;/em&gt;, rather than just another magical black box you feed your own code into.&lt;/p&gt;

&lt;p&gt;So I started over…with my original plan. 😅 I stripped out the reactivity from a monolithic HeartElement (which thankfully wasn’t too challenging because my code was still very modular), polished it up with some new smarts to make creating signals and writing “effects” easier than ever, and &lt;em&gt;tada!&lt;/em&gt; &lt;strong&gt;I’m proud to release it as the first public Heartml library: Reciprocate.&lt;/strong&gt; 🎉&lt;/p&gt;

&lt;hr /&gt;

&lt;p style=&quot;text-align: center; font-size: 200%&quot;&gt;&lt;a href=&quot;https://codepen.io/jaredcwhite/pen/JoYxOmm&quot; class=&quot;button&quot;&gt;DEMO&lt;/a&gt; &lt;span style=&quot;opacity: 0.5&quot;&gt;||&lt;/span&gt; &lt;a href=&quot;https://codeberg.org/heartml/reciprocate&quot; class=&quot;button&quot;&gt;REPO&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;get-right-into-it&quot;&gt;Get Right Into It&lt;/h2&gt;

&lt;p&gt;So, what does Reciprocate &lt;em&gt;do?&lt;/em&gt; To quote from the project:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Reciprocate is a helper utility for adding signal-based reactivity and attribute/property reflection to any vanilla custom element class.&lt;/p&gt;

  &lt;p&gt;The &lt;code class=&quot;highlighter-rouge&quot;&gt;ReciprocalProperty&lt;/code&gt; class takes advantage of fine-grained reactivity using a concept called “signals” to solve a state problem—often encountered in building components (vanilla or otherwise). Signals are values which track “subscriptions” when accessed within an side-effect callback. You write an effect, access one or more signals within that effect, and then any time any of those signal values change, that effect is re-executed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now if you’ve ever tried to write attribute/property reflection yourself (supporting multiple value types like numbers, booleans, and even JSON) and then include that custom code in all of your web components, &lt;strong&gt;you know what a PITA it is.&lt;/strong&gt; And believe me, I’ve done it far too many times. I’m more than happy to let a tiny library handle it for me! And being able to use signals for those attribute/property values in order write rendering effects against? &lt;em&gt;That’s the cherry on top!&lt;/em&gt; 🍒&lt;/p&gt;

&lt;p&gt;Reciprocate tries to be so unbundled, it doesn’t even come with its own signals implementation. In the readme it shows how to use Preact Signals, but you can also use alien-signals or potentially any other library out there. And one day if TC39 adds signals support into JavaScript directly, we can immediately leverage that!&lt;/p&gt;

&lt;p&gt;Here’s an example of what writing the ubiquitous counter demo looks like using Reciprocate. Notice that we don’t inherit from anything other than &lt;code class=&quot;highlighter-rouge&quot;&gt;HTMLElement&lt;/code&gt;. Yes, this is a real vanilla custom element! Reciprocate is simply an add-on. Even better, it uses “HTML Web Component” smarts to preserve the server-rendered content as-is and only “resume” it for future renders.&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;my-counter&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;count=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;button&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;button&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;data-behavior=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;dec&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Dec -&lt;span class=&quot;nt&quot;&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;output&amp;gt;&lt;/span&gt;1&lt;span class=&quot;nt&quot;&gt;&amp;lt;/output&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;button&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;button&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;data-behavior=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;inc&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Inc +&lt;span class=&quot;nt&quot;&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/my-counter&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MyCounter&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HTMLElement&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;observedAttributes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;customElements&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;define&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;my-counter&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;#effects&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;#resumed&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;

  &lt;span class=&quot;nf&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;#effects&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;reciprocate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;signal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;effect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nf&quot;&gt;connectedCallback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;click&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/** Create the effects which run whenever signal values are changed */&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;#effects&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;countValue&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// set up subscription&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;#resumed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;textContent&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;countValue&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;#resumed&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nf&quot;&gt;handleEvent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;click&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;button&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;closest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;button&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;switch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;button&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dataset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;behavior&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;dec&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
          &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;inc&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
          &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nf&quot;&gt;disconnectedCallback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;#effects&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;stop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;fn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nf&quot;&gt;attributeChangedCallback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;#effects&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setProp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;OK, as you can see we’re handling click events, managing state, and re-rendering on state changes. All the typical stuff you need to deal with in components. And if you’re wondering to yourself, wait, how does it know that &lt;code class=&quot;highlighter-rouge&quot;&gt;this.count&lt;/code&gt; should be turned into a signal?! That’s thanks to the power of &lt;code class=&quot;highlighter-rouge&quot;&gt;Object.keys&lt;/code&gt;. Reciprocate knows what your properties are right when it’s called and sets up the appropriate signals and getters/setters.&lt;/p&gt;

&lt;p&gt;And if you’re also wondering to yourself, well, that does feel like a fair bit of boilerplate mixed in with component code 😕…never fear. You can write a lightweight base element (an example is in the readme) and then each component will be about as simple as can be! For example:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SimpleGreeting&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BaseElement&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;observedAttributes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;whoa&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;mood&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;customElements&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;define&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;simple-greeting&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nf&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;initialize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;whoa&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Whassup&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;mood&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;😃&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nf&quot;&gt;connectedCallback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;shadowRoot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;innerHTML&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;`&amp;lt;slot&amp;gt;&amp;lt;/slot&amp;gt;&amp;lt;p id=&apos;emoji&apos;&amp;gt;&amp;lt;/p&amp;gt;`&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;textContent&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;whoa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;shadowRoot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;#emoji&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;textContent&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;mood&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;There are a lot more details about how Reciprocate works in the readme, and you can even take a peek at the test suite for a riff on the &lt;a href=&quot;/2025/08/declarative-html-binding-with-signals/&quot;&gt;“declarative signal binding”&lt;/a&gt; demo featured previously on That HTML Blog, which in theory would mean you could eliminate the rendering effects in JavaScript—let the HTML template provide the logic for you! 🤯&lt;/p&gt;

&lt;p&gt;As I mentioned up top, Reciprocate is the first library in a series of libraries to come from the Heartml project. Next up, I’m hoping to feature an &lt;em&gt;interesting&lt;/em&gt; take on server roundtrip of interactive commands based on using &lt;code class=&quot;highlighter-rouge&quot;&gt;fetch&lt;/code&gt; (via forms or otherwise). By combining web components and a modular set of commands via server requests, you could build a very sophisticated client/server web application with still mostly vanilla code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In summary…&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A lot of libraries out there—even really good ones—want you to do things &lt;em&gt;their&lt;/em&gt; way. I love Lit, but Lit is quite opinionated and falls down in scenarios where Lit is just a suboptimal solution. Likewise, I also admire htmx quite a bit, but that too is very opinionated and throws a lot of “magic” at you that you might not even want or need.&lt;/p&gt;

&lt;p&gt;I like tools which adhere to the &lt;a href=&quot;https://en.wikipedia.org/wiki/Unix_philosophy&quot;&gt;Unix philosophy&lt;/a&gt;. I like micro-libraries which you can chain together to make higher-order logic work the way &lt;em&gt;you&lt;/em&gt; want it to. I don’t want to replace &lt;code class=&quot;highlighter-rouge&quot;&gt;HTMLElement&lt;/code&gt;. I don’t want to replace &lt;code class=&quot;highlighter-rouge&quot;&gt;fetch&lt;/code&gt;. I simply want to provide you with some ergonomics to make vanilla web APIs feel even more powerful and expressive. That’s the goal. &lt;a href=&quot;https://codeberg.org/heartml/reciprocate&quot;&gt;Feel free to submit issues and PRs with your feedback and ideas!&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;some-technical-details&quot;&gt;Some Technical Details&lt;/h2&gt;

&lt;p&gt;Reciprocate is a small library, but some of the details of building it may interest you.&lt;/p&gt;

&lt;p&gt;First of all, everything is written in JavaScript. Not TypeScript. &lt;strong&gt;This is a fundamental belief I hold: we should be writing for the web with the language of the web.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;However, typing information is very useful and I would argue is a form of documentation. Furthermore, the act of static typing &lt;em&gt;is&lt;/em&gt; the act of documenting your code. As such it makes &lt;em&gt;perfect&lt;/em&gt; sense to use JSDoc. By annotating your code with JSDoc comments, both the types as well as other information you want to share about classes and methods and functions can all live within the same syntax that is 100% JavaScript compatible.&lt;/p&gt;

&lt;p&gt;It’s easy to build and export types, as the &lt;a href=&quot;https://www.typescriptlang.org/docs/handbook/declaration-files/dts-from-js.html&quot;&gt;TypeScript docs demonstrate&lt;/a&gt;. Other folks who write with JSDoc-based JavaScript or TypeScript proper can equally benefit from those types published in the NPM package. This sensible approach has already been adopted by a growing number of well-known packages such as the &lt;a href=&quot;https://github.com/sveltejs/svelte/tree/main/packages/svelte&quot;&gt;Svelte frontend framework&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Now let’s talk about testing.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In the past, I had been using &lt;a href=&quot;https://modern-web.dev/docs/test-runner/overview/&quot;&gt;Modern Web’s web-test-runner&lt;/a&gt; to write real browser-based tests for JS code, but a couple of reasons gave me pause for this latest project:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;It felt a bit like a magic black box to me, and as you may be surmising by now, I hate black boxes. 😅&lt;/li&gt;
  &lt;li&gt;I had been using web-test-runner with Playwright for cross-browser testing which is a project from Micro$oft. I am actively boycotting all Microsoft technology at this point, which left me needing to use something else. While I’m by no means a fan of Google either, Puppeteer is a project by the Chrome team directly which feels relatively “neutral” to me. And if I’m going to bother switching to Puppeteer, why not re-examine my whole testing infra?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So after a ridiculous amount of yak shaving to find a possible alternative, I ended up with a blessedly straightforward setup using &lt;a href=&quot;https://github.com/mochajs/mocha&quot;&gt;Mocha&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Mocha is an oldie but goodie in the JavaScript testing world, which is something I tend to love. Now out of the box Mocha doesn’t offer browser-based testing per se, which is why you need to spin up a static server in order to test via Puppeteer. Initially I used Express because that’s &lt;a href=&quot;https://github.com/mochajs/mocha-examples/blob/main/packages/puppeteer/test/index.spec.js&quot;&gt;what was demonstrated in this Mocha example&lt;/a&gt;, but then I started to wonder if I even needed that dependency.&lt;/p&gt;

&lt;p&gt;Turns out, &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Learn_web_development/Extensions/Server-side/Node_server_without_framework&quot;&gt;I did not! (MDN)&lt;/a&gt; You can built a functional zero-dependency static web server with Node, as that linked MDN article shows, and after a few tweaks I was able to wave Express bye-bye.&lt;/p&gt;

&lt;p&gt;The nice thing about this setup is it’s &lt;em&gt;incredibly fast&lt;/em&gt;. I can run the tests from scratch in just a couple of seconds. And furthermore, &lt;strong&gt;I’m testing an entire website!&lt;/strong&gt; If I were to spin up the server outside of the test harness and visit it in a web browser, I could see the exact same behavior on display.&lt;/p&gt;

&lt;p&gt;The only possible downside to my setup is &lt;strong&gt;it’s buildless&lt;/strong&gt;. 🤯 That’s right, I’m not using a bundler of any kind anywhere. Not for the tests, not even for publishing my package! Everything is raw ESM, and I think that’s the coolest thing ever. The static web server literally loads dependency code right out of the local &lt;code class=&quot;highlighter-rouge&quot;&gt;node_modules&lt;/code&gt; folder. It’s &lt;em&gt;possible&lt;/em&gt; that some setup in the future will break down if I need to utilize a package that doesn’t publish to NPM well for a buildless architecture. &lt;em&gt;But so far, so good!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;(But…you may be wondering why I don’t &lt;em&gt;publish&lt;/em&gt; a “dist” flavor of the package? Because there are plenty of CDNs out there which will do that for you! &lt;a href=&quot;https://esm.sh&quot;&gt;esm.sh&lt;/a&gt;, &lt;a href=&quot;https://www.jsdelivr.com/?docs=esm&quot;&gt;jsDelivr&lt;/a&gt;, and others. And of course, a local bundler like esbuild will likewise solve the same problem. It’s time to embrace our glorious &lt;strong&gt;ES Modules&lt;/strong&gt; future, and bid CommonJS and shipping janky bundled/minified code farewell!)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One final note:&lt;/strong&gt; &lt;a href=&quot;https://codeberg.org/heartml/reciprocate&quot;&gt;the Reciprocate repo is hoted on Codeberg!&lt;/a&gt; That’s right, my boycott of Micro$oft includes GitHub as well, which at this point might as well be called &lt;em&gt;Micro$oft Copilot CodeHub 3000&lt;/em&gt;. Personally, I refuse to play their idiotic games, and I heartily encourage you to migrate off of GitHub yourself and &lt;em&gt;support a true open source ecosystem!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So there you have it. I’m very pleased with the architecture of this new package, so much so that I expect it to be the blueprint of many more to come in the ❤️ Heartml family. &lt;strong&gt;Enjoy!&lt;/strong&gt; 😊&lt;/p&gt;</content><author><name>Jared White</name><uri>https://jaredwhite.com</uri></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://thathtml.blog/images/that-html-blog-social.png" /><media:content medium="image" url="https://thathtml.blog/images/that-html-blog-social.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Declarative HTML Binding in Only 20 Lines of Code! (and Signals)</title><link href="https://thathtml.blog/2025/08/declarative-html-binding-with-signals/" rel="alternate" type="text/html" title="Declarative HTML Binding in Only 20 Lines of Code! (and Signals)" /><published>2025-08-08T12:10:25-07:00</published><updated>2025-08-08T12:10:25-07:00</updated><id>repo://posts.collection/_posts/2025-08/2025-08-08-declarative-html-binding-with-signals.md</id><content type="html" xml:base="https://thathtml.blog/2025/08/declarative-html-binding-with-signals/">&lt;p&gt;Over the past few years I’ve said on many occasions that &lt;strong&gt;signals&lt;/strong&gt;, a reactive primitive popularized by many JavaScript frameworks and libraries (and most easy to pick up &lt;a href=&quot;https://github.com/preactjs/signals&quot;&gt;using the implementation by the Preact folks&lt;/a&gt;), makes it much easier to add &lt;em&gt;declarative binding&lt;/em&gt; to your HTML using basic vanilla DOM techniques + signal effects.&lt;/p&gt;

&lt;p&gt;But I haven’t had a concrete example to show off just how simple yet powerful this can be…&lt;em&gt;until now!&lt;/em&gt; 😎&lt;/p&gt;

&lt;p style=&quot;text-align: center; font-size: 150%; margin-block: 2.25rem&quot;&gt;
  &lt;a href=&quot;https://codepen.io/jaredcwhite/pen/MYaoMKz&quot; class=&quot;button&quot;&gt;View the CodePen&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;In these examples, I show the ubiquitous counter demo, a todo list with a completion count, and a typewriter printing out a message—all with a binding solution in only &lt;strong&gt;20 lines of code&lt;/strong&gt;! (And that includes the &lt;code class=&quot;highlighter-rouge&quot;&gt;import&lt;/code&gt; statement to load the signals library!) Here’s the entire solution:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;signal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;computed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;effect&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;https://esm.sh/@preact/signals-core&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;bindSignals&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;container&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;boundElements&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[...&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;container&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;querySelectorAll&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;el&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[...&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;el&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;attributes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;some&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;attr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;attr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;bind-&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;el&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;of&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;boundElements&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;attrs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[...&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;el&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;attributes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;attr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;attr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;bind-&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;attr&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;of&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;attrs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;keys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;attr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;attr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;bind-&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;nf&quot;&gt;effect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;nx&quot;&gt;el&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;textContent&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;attr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;nx&quot;&gt;el&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;innerHTML&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;attr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;nx&quot;&gt;el&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setAttribute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;attr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Let me walk you through it line by line:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;I import the relevant functions from the signals library.&lt;/li&gt;
  &lt;li&gt;I start the function block.&lt;/li&gt;
  &lt;li&gt;I first gather up all the elements within the container featuring attributes of &lt;code class=&quot;highlighter-rouge&quot;&gt;bind-*&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;I loop through each of those elements.&lt;/li&gt;
  &lt;li&gt;I collect one (or more) of the &lt;code class=&quot;highlighter-rouge&quot;&gt;bind-*&lt;/code&gt; attributes.&lt;/li&gt;
  &lt;li&gt;I loop through those attributes.&lt;/li&gt;
  &lt;li&gt;If the values object (aka signals) doesn’t include the key provided in the HTML, we’ll exit the loop.&lt;/li&gt;
  &lt;li&gt;I get the name of the attribute minus the &lt;code class=&quot;highlighter-rouge&quot;&gt;bind-&lt;/code&gt; prefix.&lt;/li&gt;
  &lt;li&gt;I create a new &lt;code class=&quot;highlighter-rouge&quot;&gt;effect&lt;/code&gt;. &lt;strong&gt;This is where the magic happens!&lt;/strong&gt; This callback will re-run every time any signal or computed value changes that gets referenced within the effect.&lt;/li&gt;
  &lt;li&gt;If the attribute is &lt;code class=&quot;highlighter-rouge&quot;&gt;bind-text&lt;/code&gt;, I’ll set the text of the element with the value.&lt;/li&gt;
  &lt;li&gt;By using &lt;code class=&quot;highlighter-rouge&quot;&gt;textContent&lt;/code&gt;, we ensure all HTML is escaped (so this should be the default approach).&lt;/li&gt;
  &lt;li&gt;Or if the attribute is &lt;code class=&quot;highlighter-rouge&quot;&gt;bind-html&lt;/code&gt;, I’ll set the element’s &lt;code class=&quot;highlighter-rouge&quot;&gt;innerHTML&lt;/code&gt; with the value.&lt;/li&gt;
  &lt;li&gt;And here’s where we (unsafely) do that. 😅&lt;/li&gt;
  &lt;li&gt;Otherwise…&lt;/li&gt;
  &lt;li&gt;I’ll use the value to set the element’s &lt;em&gt;attribute&lt;/em&gt; using whatever the label was coming after &lt;code class=&quot;highlighter-rouge&quot;&gt;bind-&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;End block.&lt;/li&gt;
  &lt;li&gt;End block &amp;amp; function.&lt;/li&gt;
  &lt;li&gt;End block.&lt;/li&gt;
  &lt;li&gt;End block. (Monotonous, isn’t it? 😃)&lt;/li&gt;
  &lt;li&gt;End block.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;And that’s all folks!&lt;/em&gt; Now you can simply pass a container element and an object containing signal values to &lt;code class=&quot;highlighter-rouge&quot;&gt;bindSignals&lt;/code&gt; and suddenly your bog-standard HTML has been granted superpowers!&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;basketOfSignals&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;myStr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;signal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Abc&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;myInt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;signal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;123&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;bindSignals&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;queryElement&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;basketOfSignals&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Caveats:&lt;/strong&gt; there are a few more smarts we should consider here before yeeting the code right into a repo and pushing to production!&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The attribute setting logic is too basic. What about boolean-style attributes like &lt;code class=&quot;highlighter-rouge&quot;&gt;hidden&lt;/code&gt; where you want &lt;code class=&quot;highlighter-rouge&quot;&gt;value === true&lt;/code&gt; to result in a &lt;code class=&quot;highlighter-rouge&quot;&gt;hidden=&quot;&quot;&lt;/code&gt; attribute and &lt;code class=&quot;highlighter-rouge&quot;&gt;value === false&lt;/code&gt; to result in the removal of that attribute? I think this could be done with a simple change such as &lt;code class=&quot;highlighter-rouge&quot;&gt;values[attr.value].value === false ? el.removeAttribute(name) : el.setAttribute(name, values[attr.value])&lt;/code&gt;, but testing validation is required!&lt;/li&gt;
  &lt;li&gt;You might want to bind &lt;em&gt;properties&lt;/em&gt; of elements and not just attributes (which will always resolve to string data), but that’s a whole ‘nuther can of worms!&lt;/li&gt;
  &lt;li&gt;While I showcase using this technique within a custom element, that also opens up a can of worms because attribute/property reflection is usually something you want a custom element to support as part of reactivity. While it’s beyond the scope of this article, I’ve written such code in vanilla JS as well—more than once!—so perhaps I’ll document that in a future installment.&lt;/li&gt;
  &lt;li&gt;Sometimes you’ll want to bind an array and &lt;em&gt;loop&lt;/em&gt; through multiple elements which include bound values of objects inside the array, not just one-off values. This is definitely another degree of complexity as you have to manage adding/updating/removing multiple items (usually involving an embedded &lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;template&amp;gt;&lt;/code&gt; which is used to “stamp” each collection item).&lt;/li&gt;
  &lt;li&gt;If you were to set up “nested” components where the bound HTML of “inner” components shouldn’t mess up “outer” components with their own bound HTML, this solution would break. You’d need to figure out how to determine the unique boundaries of each component. If you use web components with shadow DOM, however, that might be &lt;em&gt;problem solved!&lt;/em&gt; (Assuming such an approach doesn’t introduce other problems…)&lt;/li&gt;
  &lt;li&gt;And finally…you will want to clean up effects when elements are removed from the DOM. If you’re in a standard website environment (an MPA), that’s not an issue because a full-page refresh will clear the JS execution context as well as the DOM. But in an SPA environment, if you remove elements which have been bound, suddenly you introduce memory leaks and possibly errors. The &lt;code class=&quot;highlighter-rouge&quot;&gt;effect&lt;/code&gt; method returns a disposal callback—the most obvious solution would be to gather those as you set up a custom element in &lt;code class=&quot;highlighter-rouge&quot;&gt;connectedCallback&lt;/code&gt;, and then in &lt;code class=&quot;highlighter-rouge&quot;&gt;disconnectedCallback&lt;/code&gt; you execute the callbacks to cancel the effects.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Whew! Don’t let that all throw you off however!&lt;/strong&gt; I think for simple projects where you want something smarter than littering your vanilla JavaScript with a bunch of &lt;code class=&quot;highlighter-rouge&quot;&gt;textContent=&lt;/code&gt; statements everywhere, but less committed than pulling in an entire binding/rendering library, you can start with a solution as basic as this and ratchet up the complexity only when needed. And don’t forget, once you have a more established solution working, you could wrap it up into a little library of your own and re-use it across multiple projects.&lt;/p&gt;

&lt;p&gt;So what do you think? Does this get you excited about using signals in your vanilla JavaScript? Are you hoping the web platform adds signals as a native feature so we don’t need to pull in a userland library? (I sure am!) Stranger things have happened!&lt;/p&gt;

&lt;p style=&quot;text-align: center; font-size: 150%; margin-block: 2.25rem&quot;&gt;
  &lt;a href=&quot;https://codepen.io/jaredcwhite/pen/MYaoMKz&quot; class=&quot;button&quot;&gt;View the CodePen&lt;/a&gt;
&lt;/p&gt;</content><author><name>Jared White</name><uri>https://jaredwhite.com</uri></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://thathtml.blog/images/that-html-blog-social.png" /><media:content medium="image" url="https://thathtml.blog/images/that-html-blog-social.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Friends at Last: Tailwind &amp;amp; CSS…whodathunkit?!</title><link href="https://thathtml.blog/2025/08/tailwind-and-css-friends-at-last/" rel="alternate" type="text/html" title="Friends at Last: Tailwind &amp; CSS…whodathunkit?!" /><published>2025-08-04T10:01:24-07:00</published><updated>2025-08-04T10:01:24-07:00</updated><id>repo://posts.collection/_posts/2025-08/2025-08-04-tailwind-and-css-friends-at-last.md</id><content type="html" xml:base="https://thathtml.blog/2025/08/tailwind-and-css-friends-at-last/">&lt;p style=&quot;text-align: center&quot;&gt;&lt;img src=&quot;/images/tailwind-plus-css.jpg&quot; alt=&quot;Tailwind plus CSS equals rainbows and unicorns&quot; style=&quot;max-inline-size: 817.5px;inline-size: 100%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Ever since the momentous release of &lt;a href=&quot;https://tailwindcss.com&quot;&gt;Tailwind CSS version 4&lt;/a&gt; at the start of this year (January 2025), I have been telling people that the &lt;strong&gt;CSS Framework Wars™&lt;/strong&gt; are finally over. &lt;em&gt;CSS won!&lt;/em&gt; And amazingly, so did Tailwind. &lt;strong&gt;Plot twist!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/2024/12/tailwind-4-a-mea-culpa-moment/&quot;&gt;As I reported back in December:&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;You could take an existing project, add Tailwind 4 to it, and &lt;em&gt;nothing needs to change&lt;/em&gt; (provided you can import TW’s design tokens and utilities but not its full reset). Then you could incrementally use some tokens or some utilities only when and where needed.&lt;/p&gt;

  &lt;p&gt;Tailwind has &lt;em&gt;desperately&lt;/em&gt; needed to understand it’s just a tool coexisting with many other tools in a vast developer landscape where vanilla web standards have been improving exponentially. CSS in particular has leaped forward at a &lt;em&gt;dizzying&lt;/em&gt; rate. Tailwind’s “best practices don’t work” marketing line is ludicrous in 2025 when the “best practices” being argued against are from 2010.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Regarding that last line, I’m pleased to say that’s nowhere to be found on the new Tailwind homepage. In fact, their biggest marketing line now is:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Tailwind is unapologetically modern, and takes advantage of all the latest and greatest CSS features to make the developer experience as enjoyable as possible.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And while “modern” in some contexts is silly indeed, in this case that’s exactly what we’ve been clamoring for. Tailwind 4 may &lt;em&gt;look&lt;/em&gt; familiar to those who are familiar with Tailwind, but it’s actually a ground-up rewrite that feels like it was developed &lt;strong&gt;by CSS fans, for CSS fans.&lt;/strong&gt; Gone are the days of configuring the CSS framework with (*checks notes*) JavaScript, and using design tokens which simply don’t exist in vanilla CSS as real CSS variables.&lt;/p&gt;

&lt;p&gt;If you had polled a bunch of people who didn’t like Tailwind before (&lt;em&gt;me! me! ooo pick me!&lt;/em&gt;) and asked them how to “fix” Tailwind; then swirled the responses in a jar, pulled most of them out, and &lt;strong&gt;then made those fixes&lt;/strong&gt;, that’s essentially what we got in version 4. 🤯&lt;/p&gt;

&lt;p&gt;That’s a pretty big swing, and one I simply couldn’t be more thrilled with. And to demonstrate just how excited I am, I created a demo!&lt;/p&gt;

&lt;p style=&quot;text-align: center; font-size: 200%&quot;&gt;&lt;a href=&quot;https://nonviral-tailwind.site.whitefusion.studio&quot; class=&quot;button&quot;&gt;DEMO&lt;/a&gt; &lt;span style=&quot;opacity: 0.5&quot;&gt;||&lt;/span&gt; &lt;a href=&quot;https://codeberg.org/thespicyweb/nonviral-tailwind&quot; class=&quot;button&quot;&gt;REPO&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It’s a simple site built with &lt;a href=&quot;https://11ty.dev&quot;&gt;Eleventy&lt;/a&gt; + &lt;a href=&quot;https://postcss.org/&quot;&gt;PostCSS&lt;/a&gt;, and it showcases Tailwind 4, &lt;a href=&quot;/2025/07/web-awesome-is-the-first-native-component-framework/&quot;&gt;Web Awesome&lt;/a&gt; (formerly Shoelace), and custom vanilla CSS all playing quite nicely together. The Tailwind config is only &lt;em&gt;slightly&lt;/em&gt; non-standard, and I’ll explain why.&lt;/p&gt;

&lt;h2 id=&quot;configuring-tailwind-to-be-non-viral&quot;&gt;Configuring Tailwind to Be “Non-Viral”&lt;/h2&gt;

&lt;p&gt;The out-of-the-box default way to install Tailwind is to add this to your CSS entrypoint:&lt;/p&gt;

&lt;div class=&quot;language-css highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;@import&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&quot;tailwindcss&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This imports the “preflight” (a basic CSS reset), Tailwind’s theme (aka all the design tokens), and the JIT (Just-In-Time) utility classes functionality.&lt;/p&gt;

&lt;p&gt;That will work insofar as you want Tailwind, only Tailwind, and nuthin’ but Tailwind. However, if you have literally any other setup to your project—perhaps you already have a CSS architecture in mind (&lt;em&gt;pssst&lt;/em&gt;, my course &lt;a href=&quot;https://www.spicyweb.dev/css-nouveau&quot;&gt;CSS Nouveau&lt;/a&gt; is now free! 😎), or you want to use an established design system, or whatever the case may be—what you really want is an installation of Tailwind which doesn’t “take over” anything. Literally until you need to use a particular Tailwind feature, it should be invisible…a “non-viral” flavor of Tailwind.&lt;/p&gt;

&lt;p&gt;That is a surprisingly straightforward proposition! Here’s what our CSS entrypoint should be changed to instead:&lt;/p&gt;

&lt;div class=&quot;language-css highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;@layer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;theme&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;components&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;utilities&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;@import&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&quot;tailwindcss/theme.css&quot;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;layer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;theme&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;theme&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;static&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prefix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tw&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;@import&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&quot;tailwindcss/utilities.css&quot;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;layer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;utilities&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prefix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tw&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I’ll walk you through it line-by-line:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;The &lt;code class=&quot;highlighter-rouge&quot;&gt;@layer&lt;/code&gt; directive sets up your CSS cascade layers for the Tailwind imports. All major browsers since March 2022 support cascade layers, so at this point in time you should be fine. 🤞 (Tailwind 4 specifically requires browsers this new.)&lt;/li&gt;
  &lt;li&gt;We skip importing any preflight and go straight to the theme import, applying it to the &lt;code class=&quot;highlighter-rouge&quot;&gt;theme&lt;/code&gt; layer. We ask for a “static” theme meaning all the CSS variables of the theme are exported (this is &lt;em&gt;critically important&lt;/em&gt; as I will soon explain), and we also specify that all the variables should have a &lt;code class=&quot;highlighter-rouge&quot;&gt;tw&lt;/code&gt; prefix. So instead of &lt;code class=&quot;highlighter-rouge&quot;&gt;--color-red-500&lt;/code&gt;, you’ll get &lt;code class=&quot;highlighter-rouge&quot;&gt;--tw-color-red-500&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Finally, we import the JIT utilities via the &lt;code class=&quot;highlighter-rouge&quot;&gt;utilities&lt;/code&gt; layer and similarly we ask for a &lt;code class=&quot;highlighter-rouge&quot;&gt;tw&lt;/code&gt; prefix. So instead of &lt;code class=&quot;highlighter-rouge&quot;&gt;text-red-500&lt;/code&gt;, you’d write &lt;code class=&quot;highlighter-rouge&quot;&gt;tw:text-red-500&lt;/code&gt; (note the colon here…this tripped me up for hours wondering why &lt;code class=&quot;highlighter-rouge&quot;&gt;tw-text-red-500&lt;/code&gt; wasn’t working! 😅).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The reasons we need to specify &lt;code class=&quot;highlighter-rouge&quot;&gt;theme(static)&lt;/code&gt; is because by default, Tailwind will apply JIT logic to the CSS variables as well, meaning &lt;em&gt;only the variables needed to support utility classes&lt;/em&gt; which have already been used in the project will be included in the theme. This is supposed to be a performance gain, but I think it’s bananas because why would you ever want your “design system tokens” to be missing and unavailable to vanilla CSS use cases in production?!&lt;/p&gt;

&lt;p&gt;Thankfully with that resolved and this new CSS entrypoint in place, it is now possible to use Tailwind alongside literally anything and you incur no hassles and no penalties! You can write this:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;tw:bg-indigo-700&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;...&lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;or this:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;style=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;background-color: var(--tw-color-indigo-700)&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;...&lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;or this:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;box&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nc&quot;&gt;.box&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;--tw-color-indigo-700&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;and they are all completely idiomatic. Don’t believe me? &lt;a href=&quot;https://tailwindcss.com/docs/styling-with-utility-classes#using-custom-css&quot;&gt;Here’s Tailwind’s own example of writing a button class!&lt;/a&gt;&lt;/p&gt;

&lt;div class=&quot;language-css highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;@layer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;components&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nc&quot;&gt;.btn-primary&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;border-radius&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;infinity&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;--color-violet-500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;py&quot;&gt;padding-inline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;l&quot;&gt;--spacing&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;py&quot;&gt;padding-block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;l&quot;&gt;--spacing&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;font-weight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;--font-weight-semibold&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;--color-white&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;box-shadow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;--shadow-md&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;err&quot;&gt;&amp;amp;:hover&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;err&quot;&gt;@media&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;hover&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hover&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;l&quot;&gt;--color-violet-700&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;err&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;err&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I personally would prefer to use &lt;code class=&quot;highlighter-rouge&quot;&gt;calc(var(--spacing) * 5)&lt;/code&gt; rather than &lt;code class=&quot;highlighter-rouge&quot;&gt;--spacing(5)&lt;/code&gt; (a Tailwind-ism) to keep things as vanilla as possible, but that minor quibble aside, gosh darn if it doesn’t feel good to be writing real CSS! Banish &lt;code class=&quot;highlighter-rouge&quot;&gt;@apply ...&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;theme(...)&lt;/code&gt; to the hell from whence they came! (Technically they’re still available, but please for the love of god let’s not keep using them!)&lt;/p&gt;

&lt;p&gt;Tailwind’s new &lt;a href=&quot;https://tailwindcss.com/docs/theme&quot;&gt;CSS-first theming documentation&lt;/a&gt; is quite extensive, and &lt;a href=&quot;https://tailwindcss.com/docs/adding-custom-styles&quot;&gt;you can easily add your own&lt;/a&gt; custom variables, styles, and even utilities all using vanilla or vanilla-adjacent syntax. You quite literally &lt;strong&gt;never ever have to touch a JavaScript config file ever again, ever&lt;/strong&gt;. 🥰&lt;/p&gt;

&lt;p&gt;I can’t even &lt;em&gt;begin&lt;/em&gt; to tell you what a relief this all is. Do you know how much time I’ve spent in the past writing code manually to convert Tailwind’s proprietary JavaScript-based theming system to native CSS variables? Hours. Days. &lt;a href=&quot;https://github.com/thespicyweb/vanilla_breeze&quot;&gt;I even wrote an app that does it.&lt;/a&gt; We can chuck that out the window now! 😄&lt;/p&gt;

&lt;h2 id=&quot;so-whats-the-catch&quot;&gt;So What’s the Catch?&lt;/h2&gt;

&lt;p&gt;There’s not really any catch. I &lt;em&gt;will say&lt;/em&gt; that all my usual caveats about why it’s better for 95%+ of your styling to be authored in real stylesheets instead of littering your HTML with cryptic shorthand class names are still in effect. I’m not saying &lt;em&gt;I, Jared&lt;/em&gt; will personally switch to Tailwind utilities whole hog and stop authoring &lt;code class=&quot;highlighter-rouge&quot;&gt;.css&lt;/code&gt; files.&lt;/p&gt;

&lt;p&gt;But the genius of Tailwind 4 is you don’t have to make those lofty decisions on a project-by-project basis. You can make those decisions on a page-by-page, or component-by-component, or line-by-line basis. You could prototype a new landing page quickly with utility classes, then &lt;em&gt;strip out those utility classes&lt;/em&gt; and use Tailwind’s design tokens in a 100% vanilla &lt;code class=&quot;highlighter-rouge&quot;&gt;.css&lt;/code&gt; file instead. You don’t need to compromise one way or another. You can convert variables-in-stylesheets to utilities-in-HTML and back again, however it makes sense. &lt;strong&gt;This is how it was always supposed to be.&lt;/strong&gt; (As I’ve been shouting from the rooftops for years now!) And boy am I thankful the Tailwind development team decided this was a direction they could go in; which, to be perfectly honest, removes the majority of complaints I have had with Tailwind.&lt;/p&gt;

&lt;p&gt;It’s &lt;em&gt;weird&lt;/em&gt; to no longer be a vocal Tailwind critic, but hey, at least I can still go beat up on the React crowd for &lt;del&gt;breaking web components&lt;/del&gt; oh snap, &lt;a href=&quot;https://thathtml.blog/2024/12/oh-happy-day-react-finally-speaks-web-component/&quot;&gt;that’s been fixed too!&lt;/a&gt; 😂&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hallelujah! 🙌 2025 is a freakin’ incredible year for fans of vanilla-first web development.&lt;/strong&gt;*&lt;/p&gt;

&lt;p&gt;* (Except for the rise of genAI wreaking havoc on our industry, but that’s a tale for another time…)&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;
&lt;p style=&quot;text-align: center; font-size: 200%&quot;&gt;&lt;a href=&quot;https://nonviral-tailwind.site.whitefusion.studio&quot; class=&quot;button&quot;&gt;DEMO&lt;/a&gt; &lt;span style=&quot;opacity: 0.5&quot;&gt;||&lt;/span&gt; &lt;a href=&quot;https://codeberg.org/thespicyweb/nonviral-tailwind&quot; class=&quot;button&quot;&gt;REPO&lt;/a&gt;&lt;/p&gt;</content><author><name>Jared White</name><uri>https://jaredwhite.com</uri></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://thathtml.blog/images/tailwind-plus-css.jpg" /><media:content medium="image" url="https://thathtml.blog/images/tailwind-plus-css.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">More Fun with Invoker Commands and Web Components</title><link href="https://thathtml.blog/2025/07/more-fun-with-invoker-commands-web-components/" rel="alternate" type="text/html" title="More Fun with Invoker Commands and Web Components" /><published>2025-07-25T10:21:22-07:00</published><updated>2025-07-25T10:21:22-07:00</updated><id>repo://posts.collection/_posts/2025-07/2025-07-25-more-fun-with-invoker-commands-web-components.md</id><content type="html" xml:base="https://thathtml.blog/2025/07/more-fun-with-invoker-commands-web-components/">&lt;p&gt;&lt;strong&gt;August 2025 Update:&lt;/strong&gt; I actually overcomplicated my workaround described below. There’s a &lt;em&gt;simpler&lt;/em&gt; workaround, and &lt;a href=&quot;https://codepen.io/jaredcwhite/pen/PwPOYWg&quot;&gt;that’s in this CodePen&lt;/a&gt;—thanks to the &lt;code class=&quot;highlighter-rouge&quot;&gt;commandForElement&lt;/code&gt; property which lets you specify via JavaScript literally any element to be the recipient of a command invocation!&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Yes, I am a web components hammer and every API looks like a shadow DOM nail.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(I’m not sure that analogy quite makes sense, but you get the picture!)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So I thought it would be fun to revisit the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Invoker_Commands_API&quot;&gt;Invoker Commands API (MDN)&lt;/a&gt; which is available in Chromium-based browsers and hopefully coming soon to Safari and Firefox (both are currently in testing).&lt;/p&gt;

&lt;p&gt;I’ve talked about this API before on the blog, and I continue to feel pretty excited about it since it’s the first truly &lt;em&gt;declarative&lt;/em&gt; “click and see something happen!!” API we’ve ever seen on the web other than forms. I mean, think about it for a moment…isn’t it quite odd that there’s &lt;em&gt;never&lt;/em&gt; been a way to write HTML (not JavaScript!) which says “when you click this button, do that thing”?!&lt;/p&gt;

&lt;p&gt;&lt;del&gt;Now we can do that&lt;/del&gt; nope we can’t do that if we’re inside shadow DOM and the button being clicked is supposed to do a thing via its host component. 😡&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Le sigh…&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;But thankfully there’s always a workaround in the Wide World of the Web, so I’ve written up that workaround (and using Keith Cirkel’s cross-browser polyfill currently needed for invokers) and &lt;a href=&quot;https://codepen.io/jaredcwhite/pen/MYaeqXd&quot;&gt;&lt;strong&gt;here’s a CodePen&lt;/strong&gt; demonstrating it in action&lt;/a&gt;. I’m calling my little solution &lt;strong&gt;Invocably&lt;/strong&gt; and maybe some day I’ll package it up as an NPM package so you can put the package in your package, yo dawg. But for now, CodePen it is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A few notes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The way invoker commands work is you need a &lt;code class=&quot;highlighter-rouge&quot;&gt;commandfor&lt;/code&gt; attribute with the ID of another HTML element. The problem is, when you’re inside shadow DOM, there’s no ID available for the host component, as the host lives hoisted up in “light DOM”.&lt;/li&gt;
  &lt;li&gt;Invocably solves this by injecting a tiny little web component inside the shadow DOM with an ID of &lt;code class=&quot;highlighter-rouge&quot;&gt;invocably&lt;/code&gt; that is a &lt;code class=&quot;highlighter-rouge&quot;&gt;CommandEvent&lt;/code&gt; target. It then adds the appropriate &lt;code class=&quot;highlighter-rouge&quot;&gt;commandfor&lt;/code&gt; attribute to every button in the shadow DOM with a &lt;code class=&quot;highlighter-rouge&quot;&gt;command&lt;/code&gt; attribute so the event handler will work.&lt;/li&gt;
  &lt;li&gt;That injected component then forwards the event onto a &lt;code class=&quot;highlighter-rouge&quot;&gt;handleCommand&lt;/code&gt; method in your host component. Bonus points for working even for commands coming elsewhere from light DOM. So you can write a simple command API right there for your component and &lt;em&gt;Bob’s your uncle!&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The only big monkey wrench in these works I can foresee is that if we’re talking about web components with shadow DOM, we might also have a whole design system going on where the buttons are also custom components. And if you’re using &lt;code class=&quot;highlighter-rouge&quot;&gt;groovy-button&lt;/code&gt; instead of a simple &lt;code class=&quot;highlighter-rouge&quot;&gt;button&lt;/code&gt;, I’m guessing this API just isn’t going to work automatically. The design system button would &lt;em&gt;also&lt;/em&gt; need to replicate Invoker Commands click behavior.&lt;/p&gt;

&lt;p&gt;But I don’t see that as a deal-breaker. In fact, I think the whole point of having a web-native API like this is so that even when we need to build more bespoke APIs for one use case or another, &lt;strong&gt;we can build directly on top of platform mechanics&lt;/strong&gt; instead of having to reinvent the wheel from scratch every single time.&lt;/p&gt;

&lt;p&gt;My hope is that in the near future, every vanilla-friendly web app &amp;amp; UI framework will be built out of the idea that invoker commands are how “click and a thing happens” gets done. It’ll just be baked into the system, with “commands” being an API primitive for every interactive component. &lt;strong&gt;This is a huge step forward for the web&lt;/strong&gt; feeling like it’s a bona fide application development platform rather than a glorified document viewer, and I’m so here for it.&lt;/p&gt;</content><author><name>Jared White</name><uri>https://jaredwhite.com</uri></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://thathtml.blog/images/that-html-blog-social.png" /><media:content medium="image" url="https://thathtml.blog/images/that-html-blog-social.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Clever Backdrop Filtering for Eye-catching Glass Effects</title><link href="https://thathtml.blog/2025/07/clever-backdrop-filtering-for-eyecatching-glass-effects/" rel="alternate" type="text/html" title="Clever Backdrop Filtering for Eye-catching Glass Effects" /><published>2025-07-18T08:41:58-07:00</published><updated>2025-07-18T08:41:58-07:00</updated><id>repo://posts.collection/_posts/2025-07/2025-07-18-clever-backdrop-filtering-for-eyecatching-glass-effects.md</id><content type="html" xml:base="https://thathtml.blog/2025/07/clever-backdrop-filtering-for-eyecatching-glass-effects/">&lt;p&gt;Ever since the &lt;code class=&quot;highlighter-rouge&quot;&gt;backdrop-filter&lt;/code&gt; CSS property became a thing, combined with careful usage of gradients and shadowing it’s allowed web designers to make objects appear glassy to varying degrees. In recent years, we’ve even seen the rise of &lt;a href=&quot;https://www.braveachievers.com/post/what-is-glassmorphism-and-why-this-frosted-trend-is-still-heating-up-in-2024&quot;&gt;glassmorphism&lt;/a&gt; as a UI aesthetic.&lt;/p&gt;

&lt;p&gt;Now with Apple’s rollout of &lt;a href=&quot;https://www.apple.com/newsroom/2025/06/apple-introduces-a-delightful-and-elegant-new-software-design/&quot;&gt;Liquid Glass&lt;/a&gt; and an enthusiastic embrace of design details which seem to move us ever further away from “flat design” (a most welcome development in my opinion as a long-time lover of textures, shading, and tasteful 3D), I have no doubt we’ll see more attention paid to bringing elements of glassmorphism to web-based design systems. While I don’t recommend trying to recreate Apple’s Liquid Glass effects verbatim, we can use Liquid Glass as a point of inspiration for designing our own uniquely delightful interfaces.&lt;/p&gt;

&lt;p&gt;To kick off such coverage here on &lt;strong&gt;That HTML Blog&lt;/strong&gt;, I’ll direct you to &lt;a href=&quot;https://www.joshwcomeau.com/css/backdrop-filter/&quot;&gt;this tutorial by Josh Comeau&lt;/a&gt; featuring some clever techniques for making frosted glass effects via &lt;code class=&quot;highlighter-rouge&quot;&gt;backdrop-filter&lt;/code&gt; appear more realistic. As Josh puts it:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Here’s the problem: The backdrop-filter algorithm only considers the pixels that are directly behind the element.&lt;/p&gt;

  &lt;p&gt;By default, the gaussian blur algorithm is applied to all of the pixels behind the element. This means that if a big colorful element is near the element, it won’t have any effect.&lt;/p&gt;

  &lt;p&gt;That’s not really how frosted glass works in real life though. Light bounces off of objects and then goes through the glass. It looks so much better when the blurring algorithm includes nearby content.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Separate from this article, I’ve also been experimenting with adding additional filters besides blur to &lt;code class=&quot;highlighter-rouge&quot;&gt;backdrop-filter&lt;/code&gt; such as adjusting contrast down and saturation up so you can get pretty exciting color effects without the headache of huge light/dark swings (a problem we saw even in Apple’s early Liquid Glass betas before they made tweaks to tame the excess). I hope to write up a demo for that soon.&lt;/p&gt;

&lt;p&gt;Ultimately I think the best designs will pull from a number of graphics techniques to make it seem fun and fresh. Anything to move us past the era of no-depth boring-ass solid colors. (Speaking of which, &lt;a href=&quot;https://www.spicyweb.dev/i-built-a-squricle-button/&quot;&gt;may I interest you in a colorful squircle button?&lt;/a&gt;)&lt;/p&gt;</content><author><name>Jared White</name><uri>https://jaredwhite.com</uri></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://thathtml.blog/images/that-html-blog-social.png" /><media:content medium="image" url="https://thathtml.blog/images/that-html-blog-social.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>