<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Starred Articles</title>
    <description>Starred Articles</description>
    <atom:link href="https://feedbin.com/starred/5Euw2uq5T9oUL7tUbs0_2A.xml" rel="self" type="application/rss+xml"/>
    <link>https://feedbin.com/</link>
    <item>
      <title><![CDATA[Building Websites With LLMS]]></title>
      <description><![CDATA[<p>And by LLMS I mean: (L)ots of (L)ittle ht(M)l page(S).</p>
<p>I recently shipped some updates to my blog. Through the design/development process, I had some insights which made me question my knee-jerk reaction to building pieces of a page as JS-powered interactions on top of the existing document.</p>
<p>With cross-document view transitions getting broader and broader support, I’m realizing that building in-page, progressively-enhanced interactions is <em>more</em> work than simply building two HTML pages and linking them.</p>
<p>I’m calling this approach “lots of little HTML pages” in my head. As I find myself trying to build progressively-enhanced features with JavaScript — like a fly-out navigation menu, or an on-page search, or filtering content — I stop and ask myself: “Can I build this as a separate HTML page triggered by a link, rather than JavaScript-injected content built from a button?”</p>
<p>I kinda love the results. I build separate, small HTML pages for each “interaction” I want, then I let CSS transitions take over and I get something that feels better than its JS counterpart for way less work.</p>
<p>Allow me two quick examples.</p>
<h2 id="example-1-filtering">Example 1: Filtering</h2>
<p>Working on my homepage, I found myself wanting a list of posts filtered by some kind of criteria, like:</p>
<ul>
<li>The most recent posts</li>
<li>The ones being trafficked the most</li>
<li>The ones that’ve had lots of Hacker News traffic in the past</li>
</ul>
<p>My first impulse was to have a list of posts you can filter with  JavaScript. </p>
<p>But the more I built it, the more complicated it got. Each “list” of posts needed a slightly different set of data. And each one had a different sort order. What I thought was going to be “stick a bunch of <code>&lt;li&gt;</code>s in the DOM, and show hide some based on the current filter” turned into lots of <code>data-x</code> attributes, per-list sorting logic, etc. I realized quickly this wasn’t a trivial, progressively-enhanced feature. I didn’t want to write a bunch of client-side JavaScript for what would take me seconds to write on “the server” (my static site generator).</p>
<p>Then I thought: Why don’t I just do this with my static site generator? Each filter can be its own, separate HTML page, and with CSS view transitions I’ll get a nice transition effect for free!</p>
<p>Minutes later I had it all working — mostly, <a href="https://blog.jim-nielsen.com/2025/aspect-ratio-in-css-view-transitions/">I had to learn a few small things about aspect ratio in transitions</a> — plus I had fancy transitions between “tabs” for free!</p>
<p class="image-container"><img src="https://cdn.jim-nielsen.com/blog/2025/lots-of-small-html-filter-transitions.gif" width="480" height="462" alt="Animated gif showing a link that goes to a new document and the list re-shuffles and re-sorts its contents in an animated fashion.">

</p><p>This really feels like a game-changer for simple sites. If you can keep your site simple, it’s easier to build traditional, JavaScript-powered on-page interactions as small, linked HTML pages.</p>
<h2 id="example-2-navigation">Example 2: Navigation</h2>
<p>This got me thinking: maybe I should do the same thing for my navigation?</p>
<p>Usually I think “Ok, so I’ll have a hamburger icon with a bunch of navigational elements in it, and when it’s clicked you gotta reveal it, etc." And I thought, “What if it’s just a new HTML page?”<sup id="fnref:1"><a href="https://blog.jim-nielsen.com/2025/lots-of-little-html-pages/#fn:1">[1]</a></sup></p>
<p>Because I’m using a static site generator, it’s really easy to create a new HTML page. A few minutes later and I had it. No client-side JS required. You navigate to the “Menu” and you get a page of options, with an “x” to simulate closing the menu and going back to where you were.</p>
<p class="image-container"><img src="https://cdn.jim-nielsen.com/blog/2025/lots-of-small-html-menu.gif" width="480" height="375" alt="Anitmated gif of a menu opening on a website (but it’s an entirely new HTML page).">

</p><p>I liked it so much for my navigation, I did the same thing with search. Clicking the icon doesn’t use JavaScript to inject new markup and animate things on screen. Nope. It’s just a link to a new page with CSS supporting a cross-document view transition.</p>
<p class="image-container"><img src="https://cdn.jim-nielsen.com/blog/2025/lots-of-small-html-search.gif" width="480" height="249" alt="">

</p><p>Granted, there are some trade-offs to this approach. But on the whole, I really like it. It was so easy to build and I know it’s going to be incredibly easy to maintain!</p>
<p>I think this is a good example of leveraging the grain of the web. It’s <em>really</em> easy to build a simple website when you can shift your perspective to viewing on-page interactivity as simple HTML page navigations powered by cross document CSS transitions (rather than doing all of that as client-side JS).</p>

    <hr>
     <ol class="footnotes"><li id="fn:1">Jason Bradberry has a neat article that’s tangential to this idea over <a href="https://piccalil.li/blog/in-praise-of-off-screen-menus/">at Piccalil</a>. It’s more from the design standpoint, but functionally it could work pretty much the same as this: your “menu” or “navigation” is its own page. <a href="https://blog.jim-nielsen.com/2025/lots-of-little-html-pages/#fnref:1" title="Jump back to footnote 1 in the text.">⏎</a></li></ol> 

    <p>
      Reply via:
      

      <a href="mailto:jimniels%2Bblog@gmail.com?subject=Re:%20blog.jim-nielsen.com/2025/lots-of-little-html-pages/">Email</a>
      · <a href="https://mastodon.social/@jimniels">Mastodon</a> ·

      <a href="https://bsky.app/profile/jim-nielsen.com">Bluesky</a>
    </p>

    
  ]]></description>
      <pubDate>Tue, 04 Mar 2025 14:38:48 +0000</pubDate>
      <link>https://blog.jim-nielsen.com/2025/lots-of-little-html-pages/</link>
      <dc:creator>Jim Nielsen’s Blog</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/4743046626</guid>
    </item>
    <item>
      <title><![CDATA[Mapping with Leaflet]]></title>
      <description><![CDATA[
<p>It <em>seems</em> relatively simple: you have some data that involves location and you want to display those locations on a map. </p>



<p><em>But how do you choose which service to use? </em></p>



<p>Most folks default to Google Maps, which is a very powerful platform, but also comes with probably way more than you need, and has a cost involved at a certain level. What if there were a simpler, smaller, and dare I say, <em>free and open source</em> alternative?&nbsp;<a href="https://leafletjs.com/">Leaflet.js</a>&nbsp;is exactly that. </p>



<p>Leaflet is incredibly simple and flexible. It lets you quickly add interactive maps to your web pages with little overhead.</p>



<p>Before we jump into code, let’s consider a simple, but realistic example. You run a website that helps prospective cat owners find cats that need a new home. Your data comes from an API (which for our sake will be a JSON file) and includes information like:</p>



<ul class="wp-block-list">
<li>Characterstics of the cat</li>



<li>How to contact the current owner</li>



<li>Where the cat could be picked up, a location in latitude and longitude.</li>
</ul>



<p>What we want to do here is see how much effort is required to add this to a web page. Let’s get started!</p>



<h2 class="wp-block-heading" id="including-the-library">Including the Leaflet Library</h2>



<p>We’ll start with a simple bit of HTML. I’m using CodePen for the samples so this will be a subset of the entire page of course. First, just some introductory content and a place for our map.</p>


<pre class="wp-block-code"><span><code class="hljs language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>Cat Connector<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>
  Use the map below to help find cats who need a new home. 
  Every cat deserves a loving home!
<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"map"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></code></span></pre>


<p>The map will need a size of some sort, so I used the following CSS:</p>


<pre class="wp-block-code"><span><code class="hljs language-css"><span class="hljs-selector-id">#map</span> {
  <span class="hljs-attribute">width</span>: <span class="hljs-number">100%</span>;
  <span class="hljs-attribute">height</span>: <span class="hljs-number">500px</span>;
}</code></span></pre>


<p>If you follow the&nbsp;<a href="https://leafletjs.com/examples/quick-start/">Leaflet Quick Start</a>, you can see two dependencies that need adding, first, a CSS resource:</p>


<pre class="wp-block-code"><span><code class="hljs language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">link</span> 
  <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span> 
  <span class="hljs-attr">href</span>=<span class="hljs-string">"https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"</span>
  <span class="hljs-attr">integrity</span>=<span class="hljs-string">"sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="</span>
  <span class="hljs-attr">crossorigin</span>=<span class="hljs-string">""</span>
/&gt;</span></code></span></pre>


<p>And then the library itself:</p>


<pre class="wp-block-code"><span><code class="hljs language-css">&lt;<span class="hljs-selector-tag">script</span> 
  <span class="hljs-selector-tag">src</span>="<span class="hljs-selector-tag">https</span>://<span class="hljs-selector-tag">unpkg</span><span class="hljs-selector-class">.com</span>/<span class="hljs-selector-tag">leaflet</span><span class="hljs-keyword">@1</span>.9.4/dist/leaflet.js"
  integrity=<span class="hljs-string">"sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="</span>
  crossorigin=<span class="hljs-string">""</span>&gt;
&lt;/script&gt;</code></span></pre>


<p>For CodePen, these  was done in the Pen Settings in the External Stylesheets and External Scripts sections.</p>



<p>Now, we need to actually add the map, and here’s where you need to think a bit. In order to create a map, you’ll need a location to begin with. What this location is depends on a lot of factors. If I were rendering maps in the continental United States, I could center it on America. If I were mapping castles in Germany, obviously I’d pick a location somewhere in the country.</p>



<p>Another factor you have to consider is the initial zoom. How close, or how far, the map initially shows itself will also depend on the kind of data you’re rendering.</p>



<p>For our pretend cat rehoming website, we’ll assume Lafayette, Louisiana, which just so happens to be where I live. The latitude of Lafayette is 30.216667 and the longitude is -92.033333.</p>



<p>This can be done with one line:</p>


<pre class="wp-block-code"><span><code class="hljs language-javascript"><span class="hljs-keyword">let</span> map = L.map(<span class="hljs-string">'map'</span>).setView([<span class="hljs-number">30.216667</span>, <span class="hljs-number">-92.033333</span>], <span class="hljs-number">12</span>);</code></span></pre>


<p>If you were to run this right now, you would see:</p>



<figure class="wp-block-image size-full is-resized"><img data-recalc-dims="1" loading="lazy" decoding="async" width="650" height="512" src="https://i0.wp.com/frontendmasters.com/blog/wp-content/uploads/2025/01/l1.png?resize=650%2C512&amp;ssl=1" alt="Big gray box, useless!" class="wp-image-5059" style="width:424px;height:auto" srcset="https://i0.wp.com/frontendmasters.com/blog/wp-content/uploads/2025/01/l1.png?w=650&amp;ssl=1 650w, https://i0.wp.com/frontendmasters.com/blog/wp-content/uploads/2025/01/l1.png?resize=300%2C236&amp;ssl=1 300w" sizes="auto, (max-width: 650px) 100vw, 650px"></figure>



<p>Where’s the map? The final part you need is a tile provider. Map tiles are the parts that make up any interactive map. As you go to different locations, and different zoom levels, the map is rendered from a set of tiles, based on those factors (location and zoom). <a href="https://www.openstreetmap.org/#map=4/38.01/-95.84">OpenStreetMap</a> provides free map tiles (with attribution!) and can be added to Leaflet like so:</p>


<pre class="wp-block-code"><span><code class="hljs language-javascript">L.tileLayer(<span class="hljs-string">'https://tile.openstreetmap.org/{z}/{x}/{y}.png'</span>, {
  <span class="hljs-attr">maxZoom</span>: <span class="hljs-number">19</span>,
  <span class="hljs-attr">attribution</span>: <span class="hljs-string">'&amp;copy; &lt;a href="http://www.openstreetmap.org/copyright"&gt;OpenStreetMap&lt;/a&gt;'</span>
}).addTo(map);</code></span></pre>


<p>Let’s take a look at what we have so far:</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_VYZdqZL" src="//codepen.io/anon/embed/VYZdqZL?height=700&amp;theme-id=47434&amp;slug-hash=VYZdqZL&amp;default-tab=result" height="700" scrolling="no" frameborder="0" allowfullscreen="" allowpaymentrequest="" name="CodePen Embed VYZdqZL" title="CodePen Embed VYZdqZL" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<p>We haven’t actually added any data yet and it’s possible we may need to tweak multiple things. Maybe the map needs to be bigger? Maybe our centered location isn’t quite right? Our default zoom may not be optimal either. These are all things that will come into play as we start adding data — so let’s do that next!</p>



<h2 class="wp-block-heading" id="our-data">Our Data</h2>



<p>Before we get into how Leaflet can show data, let’s look at our sample data. Our cats will be returned as static data, an array of results. Here’s a portion of the data:</p>


<pre class="wp-block-code"><span><code class="hljs language-javascript"><span class="hljs-keyword">let</span> availableCats = [
  {
    <span class="hljs-attr">cat</span>: {
      <span class="hljs-attr">name</span>: <span class="hljs-string">"Fluffy"</span>,
      <span class="hljs-attr">age</span>: <span class="hljs-number">5</span>,
      <span class="hljs-attr">gender</span>: <span class="hljs-string">"male"</span>,
      <span class="hljs-attr">breed</span>: <span class="hljs-string">"calico"</span>
    },
    <span class="hljs-attr">owner</span>: {
      <span class="hljs-attr">name</span>: <span class="hljs-string">"Raymond Camden"</span>,
      <span class="hljs-attr">contactEmail</span>: <span class="hljs-string">"raymondcamden@gmail.com"</span>,
      <span class="hljs-attr">contactPhone</span>: <span class="hljs-string">"555-555-5555"</span>
    },
    <span class="hljs-attr">location</span>: {
      <span class="hljs-attr">latitude</span>: <span class="hljs-number">30.227394</span>,
      <span class="hljs-attr">longitude</span>: <span class="hljs-number">-92.02909</span>
    }
  },
  {
    <span class="hljs-attr">cat</span>: {
      <span class="hljs-attr">name</span>: <span class="hljs-string">"Pig"</span>,
      <span class="hljs-attr">age</span>: <span class="hljs-number">12</span>,
      <span class="hljs-attr">gender</span>: <span class="hljs-string">"female"</span>,
      <span class="hljs-attr">breed</span>: <span class="hljs-string">"calico"</span>
    },
    <span class="hljs-attr">owner</span>: {
      <span class="hljs-attr">name</span>: <span class="hljs-string">"Lindy Camden"</span>,
      <span class="hljs-attr">contactEmail</span>: <span class="hljs-string">"lindyjcamden@gmail.com"</span>,
      <span class="hljs-attr">contactPhone</span>: <span class="hljs-string">"555-555-5555"</span>
    },
    <span class="hljs-attr">location</span>: {
      <span class="hljs-attr">latitude</span>: <span class="hljs-number">30.231695</span>,
      <span class="hljs-attr">longitude</span>: <span class="hljs-number">-92.007103</span>
    }
  }
];</code></span></pre>


<p>To emulate an API call, we can wrap this up in a function like so:</p>


<pre class="wp-block-code"><span><code class="hljs language-javascript"><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getAvailableCats</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> [
    <span class="hljs-comment">// list of cats here...</span>
  ]
}</code></span></pre>


<p>And then update our code to let us call this asynchronously:</p>


<pre class="wp-block-code"><span><code class="hljs language-javascript"><span class="hljs-built_in">document</span>.addEventListener(<span class="hljs-string">"DOMContentLoaded"</span>, init, <span class="hljs-literal">false</span>);
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">init</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">let</span> cats = <span class="hljs-keyword">await</span> getAvailableCats();

  <span class="hljs-keyword">let</span> map = L.map(<span class="hljs-string">"map"</span>).setView([<span class="hljs-number">30.216667</span>, <span class="hljs-number">-92.033333</span>], <span class="hljs-number">12</span>);

  L.tileLayer(<span class="hljs-string">"https://tile.openstreetmap.org/{z}/{x}/{y}.png"</span>, {
    <span class="hljs-attr">maxZoom</span>: <span class="hljs-number">19</span>,
    <span class="hljs-attr">attribution</span>:
      <span class="hljs-string">'&amp;copy; &lt;a href="http://www.openstreetmap.org/copyright"&gt;OpenStreetMap&lt;/a&gt;'</span>
  }).addTo(map);
}</code></span></pre>


<p>At this point, we’ve got data, but how to add it to the map? Leaflet lets you ‘draw’ on a map in many different ways, but the simplest is by adding a marker:</p>


<pre class="wp-block-code"><span><code class="hljs language-javascript"><span class="hljs-keyword">let</span> marker = L.marker([latitude, longitude]).addTo(map);</code></span></pre>


<p>Since we got the cats earlier in the function, we just need to loop over them:</p>


<pre class="wp-block-code"><span><code class="hljs language-javascript">cats.forEach(<span class="hljs-function"><span class="hljs-params">c</span> =&gt;</span> {
  <span class="hljs-keyword">let</span> marker = L.marker([c.location.latitude, c.location.longitude]).addTo(map);
});</code></span></pre>


<p>You can see this version below:</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_QwLxzEr" src="//codepen.io/anon/embed/QwLxzEr?height=700&amp;theme-id=47434&amp;slug-hash=QwLxzEr&amp;default-tab=result" height="700" scrolling="no" frameborder="0" allowfullscreen="" allowpaymentrequest="" name="CodePen Embed QwLxzEr" title="CodePen Embed QwLxzEr" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<p>Notice that it’s possible that changing the zoom may help here, there’s a lot of space around the data and we could get clodser in. However, maybe we know our organization could, at any time, get more data farther out. This is exactly what I was talking about in terms of understanding your data, both what’s immediately available and what may come later.</p>



<p>Leaflet also supports the idea of taking a set of locations and ensuring they all ‘fit’ by default. That’s just one example of how powerful the API is, but we’ll keep it simple for now.</p>



<h2 class="wp-block-heading" id="adding-interactivity">Adding Interactivity</h2>



<p>Now for the last part of the puzzle. In order for the information on the map to actually be useful, we need to associate the data for those markers and provide some form of UI to share that with the users. In it’s simplest form, Leaflet lets you bind popups to markers in, literally, one simple method call.</p>



<p>You take this:</p>


<pre class="wp-block-code"><span><code class="hljs language-javascript"><span class="hljs-keyword">let</span> marker = L.marker([latitude, longitude]).addTo(map);</code></span></pre>


<p>And add:</p>


<pre class="wp-block-code"><span><code class="hljs language-javascript"><span class="hljs-keyword">let</span> marker = L.marker([latitude, longitude]).addTo(map).bindPopup(<span class="hljs-string">'Set some HTML or just regular text here.'</span>);</code></span></pre>


<p>That’s it! So given our loop over the cat data, here’s a new version with that. I created a template string to more nicely format the information:</p>


<pre class="wp-block-code"><span><code class="hljs language-javascript">cats.forEach(<span class="hljs-function"><span class="hljs-params">c</span> =&gt;</span> {
  <span class="hljs-keyword">let</span> desc = <span class="hljs-string">`
    &lt;h3&gt;<span class="hljs-subst">${c.cat.name}</span>&lt;/h3&gt;
    &lt;p&gt;
      <span class="hljs-subst">${c.cat.name}</span> is a <span class="hljs-subst">${c.cat.gender}</span> <span class="hljs-subst">${c.cat.breed}</span>.
    &lt;/p&gt;
    &lt;p&gt;
      Owner: <span class="hljs-subst">${c.owner.name}</span>&lt;br&gt;
      Email: <span class="hljs-subst">${c.owner.contactEmail}</span>&lt;br&gt;
      Tel: <span class="hljs-subst">${c.owner.contactPhone}</span>&lt;br&gt;
    &lt;/p&gt;
  `</span>;
  L.marker([c.location.latitude, c.location.longitude]).addTo(map).bindPopup(desc);
});</code></span></pre>


<p>This one method handles the click interaction, creating the popup, even automatically closing another popup if you forget to close one. Here’s the complete demo:</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_zxOJNEP" src="//codepen.io/anon/embed/zxOJNEP?height=700&amp;theme-id=47434&amp;slug-hash=zxOJNEP&amp;default-tab=result" height="700" scrolling="no" frameborder="0" allowfullscreen="" allowpaymentrequest="" name="CodePen Embed zxOJNEP" title="CodePen Embed zxOJNEP" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<h2 class="wp-block-heading" id="what-else">What Else?</h2>



<p>This introduction only scratches the surface of what’s possible and you should peruse the&nbsp;<a href="https://leafletjs.com/reference.html">docs</a>&nbsp;for a full idea. Leaflet is great, and free, but, you should also be aware of some of the things you&nbsp;<em>don’t</em>&nbsp;get that you would with Google Maps or <a href="https://maps.here.com/?map=44.09044,-120.73045,8.44">HERE</a>. These include the various ‘services’, like</p>



<ul class="wp-block-list">
<li>Routing</li>



<li>Traffic information</li>



<li>Geocoding (and Reverse Geocoding)</li>
</ul>



<p>This doesn’t mean you can’t make use of those services&nbsp;<em>along</em>&nbsp;with Leaflet. I discussed an example of this late last year:&nbsp;<a href="https://www.raymondcamden.com/2024/10/04/using-geocoding-with-leaflet">“Using Geocoding with Leaflet”</a>. That being said, keep this in mind when planning your projects as it may impact your decision on whether or not to use the library.</p>
]]></description>
      <pubDate>Wed, 29 Jan 2025 19:16:33 +0000</pubDate>
      <link>https://frontendmasters.com/blog/mapping-with-leaflet/</link>
      <dc:creator>Master.dev Blog RSS Feed</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/4706436247</guid>
    </item>
    <item>
      <title><![CDATA[Good forms]]></title>
      <description><![CDATA[<p><a href="https://brian.io/">Brian LeRoux</a> posted <a href="https://indieweb.social/@brianleroux/112932335725534529">a few thoughts about forms</a> and the idea of a “<a href="https://daverupert.com/2024/04/thoughts-on-cosmotechnics/">good form</a>” resonated with me so I dogpiled some of my own thoughts and experiences on it. Here’s a compilation of those ideas. I’m sure this is incomplete and would love to see your list.</p>
<ul>
<li>Good forms work without client JavaScript (Brian LeRoux)</li>
<li>Good forms always submit (Brian LeRoux)</li>
<li>Good forms remember values and display problems inline (Brian LeRoux)</li>
<li>Good login forms work with password managers (<a href="https://elk.zone/oldbytes.space/@mingo/112932355524060403">Mingo Hogan</a>)</li>
<li>Good forms use a <code>&lt;form&gt;</code> tag</li>
<li>Good forms use appropriate input types</li>
<li>Good forms have clear labelled inputs and buttons</li>
<li>Good forms have focus states</li>
<li>Good forms have logical tab order</li>
<li>Good forms allow paste</li>
<li>Good form elements leverage the <code>inputmode</code> attribute</li>
<li>Good form elements leverage the <code>autocomplete</code> attribute</li>
<li>Good search forms wrap the <code>&lt;form&gt;</code> tag in a <code>&lt;search&gt;</code> element</li>
<li>Good forms can be reset with a <code>&lt;button type=reset&gt;</code></li>
<li>Good forms participate in <code>formData</code></li>
<li>Good forms don’t use placeholder as a label</li>
<li>Good forms work on phones</li>
<li>Good forms don’t popup and ask for personal information</li>
<li>Good forms only ask for what is necessary and aren’t too long</li>
<li>Good forms use HTTPS</li>
<li>Good forms use the proper HTTP verbs</li>
<li>Good forms validate on the client AND on the server</li>
<li>Good forms have been driven with a screen reader <em>before</em> going to production</li>
<li>Good forms clearly denote required attributes</li>
<li>Good forms warn about costly or destructive actions</li>
<li>Good forms make the baby Jesus smile</li>
<li>Good authenticated forms should have a server-generated nonce value</li>
<li>Good forms should do a pre-flight check for <code>navigator.onLine</code> before attempting a submit</li>
<li>Good forms start with <code>accent-color</code> for styling and only get more complex if necessary</li>
<li>Good forms see custom controls as a radioactive asset that must be removed at the earliest opportunity</li>
</ul>
<p>Anyways. People should talk about forms more. Here’s some more resources on good form design.</p>
<ul>
<li><a href="https://www.smashingmagazine.com/printed-books/form-design-patterns/">Form Design Patterns</a> by Adam Silver</li>
<li><a href="https://www.lukew.com/resources/web_form_design.asp">Web Form Design</a> by Luke Wroblewski</li>
<li><a href="https://formdesignmastery.com/">Form Design Mastery</a> by Adam Silver</li>
<li><a href="https://abookapart.com/products/better-onboarding">Better Onboarding</a> by Krystal Higgins</li>
</ul>
]]></description>
      <pubDate>Fri, 13 Sep 2024 13:25:00 +0000</pubDate>
      <link>https://daverupert.com/2024/09/good-forms/</link>
      <dc:creator>daverupert.com</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/4566640389</guid>
    </item>
    <item>
      <title><![CDATA[Detect JavaScript Support in CSS]]></title>
      <description><![CDATA[<main id="main" class="breakout flow prose"><figure class="post-timer"> <svg width="24" height="24" class="timer"> <rect width="256" height="256"></rect> <circle></circle> <circle></circle> <line class="timer-hand"></line> <line class="timer-switch"></line> </svg>
</figure> <section class="info | cluster skewer"> <p class="cluster"> <svg width="24" height="24"><rect width="256" height="256"></rect><path></path><rect width="176" height="176"></rect><line></line><line></line><line></line></svg> <span>Posted on <strong>April 20, 2024</strong></span> </p> <p class="cluster"> <svg width="24" height="24"><rect width="256" height="256"></rect><circle></circle><circle></circle><line></line><line></line></svg> <span> Takes about <strong>5 minutes</strong> to read </span> </p>
</section>
<p>I had been aware of the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/scripting"><code>scripting</code> CSS media feature</a> but I was still under the impression that cross-browser support was lacking. What a pleasant surprise to discover that it has been available in all modern browsers as of December 2023 according to <a href="https://caniuse.com/?search=scripting">caniuse.com</a>. With this feature, we can provide alternative CSS rules depending on whether or not JavaScript is available in the user's browser. It can also help reduce flashes of unstyled content or undesirable layout shifts.</p>
<h2 id="usage">Usage</h2>
<p>We can progressively enhance our styles:</p>
<pre class="language-scss"><code class="language-scss"><p class="token"><span class="token rule">@media</span> <span class="token punctuation">(</span><span class="token property">scripting</span><span class="token punctuation">:</span> enabled<span class="token punctuation">)</span></p> <p class="token">{</p> <p class="token">.my-element </p><p class="token">{</p> <p class="token">}</p>
<p class="token">}</p></code></pre>
<p>Or we can gracefully fall back to some alternate styles:</p>
<pre class="language-scss"><code class="language-scss"><p class="token"><span class="token rule">@media</span> <span class="token punctuation">(</span><span class="token property">scripting</span><span class="token punctuation">:</span> none<span class="token punctuation">)</span></p> <p class="token">{</p> <p class="token">.my-element </p><p class="token">{</p> <p class="token">}</p>
<p class="token">}</p></code></pre>
<p>There's also an <code>initial-only</code> value, which is for scripting that is enabled during page load but not after. The <a href="https://www.w3.org/TR/mediaqueries-5/#scripting">Media Queries Level 5 W3C Working Draft</a> includes a couple cases where it can be useful.</p>
<blockquote>
<p>Examples are printed pages, or pre-rendering network proxies that render a page on a server and send a nearly-static version of the page to the user.</p>
</blockquote>
<p>I don't personally imagine using <code>initial-only</code> much, if ever. Although, I'd be interested to find more specific examples of it in practice.</p>
<h2 id="the-time-before-the-query">The time before the query</h2>
<p>Before this feature, one approach for detecting JavaScript support was by setting a custom selector on the opening <code>html</code> tag—a common one seen in the wild is the <code>no-js</code> class name. If JavaScript is supported and enabled, it removes that selector just prior to rendering page content. When JavaScript is disabled, we can supply alternative styles that adapt to the experience.</p>
<pre class="language-html"><code class="language-html"><p class="token"><span class="token tag"><span class="token punctuation">&lt;</span>html</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>no-js<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></p> <p class="token"><span class="token tag"><span class="token punctuation">&lt;/</span>html</span><span class="token punctuation">&gt;</span></p></code></pre>
<pre class="language-scss"><code class="language-scss"><p class="token">.no-js .my-element </p><p class="token">{</p> <p class="token">}</p></code></pre>
<h2 id="is-this-real-life">Is this real life?</h2>
<p>Imagine a new web campaign is on the cusp of going live and it's time to connect with all the key stakeholders. Everything looks great, most of the team satisfied with the result, but then suddenly some hip marketer in the meeting emphatically requests a complex intro animation on the hero component when the page loads. They gesture wildly as they ask for the main headline to fade in, shrink away as if it were being pulled back on a sling shot, and then... at this point they make an explosion noise with their mouth. "Make it pop!" they decree a mere 24 hours before launch.</p>
<p>Woof. Better get started.</p>
<p>To handle the complexity of this work, we might reach for an animation library such as <a href="https://gsap.com/">GSAP</a>. But what does the user see when JavaScript is not available, not to mention if a user's <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion"><code>prefers reduced motion</code></a> setting is enabled? We'll need to consider an alternate version of the hero without all that swooping and scaling.</p>
<p>This media query unlocks the ability to provide CSS rules that are a better fit to the user's experience. In the CodePen demo below, if we disable JavaScript, we'll find that the animation is skipped and the static headline is displayed.</p>
<p class="codepen"> <a href="https://codepen.io/hexagoncircle/pen/NWmEMmJ/e1e382620c5897b4c72fa29b36227fd4"> <svg class="icon" width="24" height="24"><path></path></svg> <span>Open CodePen demo</span> </a>
</p> <h2 id="watch-that-flash">Watch that flash</h2>
<p>To really make the intro animation feel smooth on page load, the demo relies on the <code>scripting</code> media query to hide the headline with CSS. By doing so, we won't catch a flash of unstyled text before the GSAP animation is loaded. Also, we <em>only</em> want to hide the headline if JavaScript <em>is</em> available, otherwise it would be hidden for users when it's disabled.</p>
<p>In the following video, watch what happens when the headline is not hidden on page load. The text flashing is even more glaring when throttling on a slower network.</p>
<figure class="video"> <video> <source src="https://ryanmulligan.dev/videos/detect-js-support-in-css.webm#t=0.001" type="video/webm"> <source src="https://ryanmulligan.dev/videos/detect-js-support-in-css.mp4#t=0.001" type="video/mp4"> <p>Your browser cannot play the provided video file.</p> </video> <figcaption>In the video, the headline is no longer hidden on page load to share that pesky flash of unstyled text. When testing on a slower network, the issue becomes even more egregious.</figcaption></figure>
<h2 id="combining-queries">Combining queries</h2>
<p>In the CSS tab of the demo, notice that the media queries are combined to check both scripting and reduced-motion conditions.</p>
<pre class="language-scss"><code class="language-scss"><p class="token"><span class="token rule">@media</span> <span class="token punctuation">(</span><span class="token property">scripting</span><span class="token punctuation">:</span> enabled<span class="token punctuation">)</span> <span class="token operator">and</span> <span class="token punctuation">(</span><span class="token property">prefers-reduced-motion</span><span class="token punctuation">:</span> no-preference<span class="token punctuation">)</span></p> <p class="token">{</p> <p class="token">}</p> <p class="token"><span class="token rule">@media</span> <span class="token punctuation">(</span><span class="token property">scripting</span><span class="token punctuation">:</span> none<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>prefers-reduced-motion<span class="token punctuation">)</span></p> <p class="token">{</p> <p class="token">}</p></code></pre>
<p>Each condition can surely have exclusive styles if the desired outcome calls for it, but it's nice that we can combine them where there's overlap in rulesets.</p>
<h2 id="helpful-resources">Helpful resources</h2> <p class="cta arrow-start"> <a href="https://ryanmulligan.dev/blog/">Back to all blog posts</a>
</p> </main>]]></description>
      <pubDate>Sun, 21 Apr 2024 06:18:11 +0000</pubDate>
      <link>https://ryanmulligan.dev/blog/detect-js-support-in-css/</link>
      <dc:creator>Pages</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/4431330237</guid>
    </item>
    <item>
      <title><![CDATA[Write Alt Text Like You’re Talking To A Friend]]></title>
      <description><![CDATA[
<figure class="wp-block-image size-large"><img width="1024" height="576" src="https://cloudfour.com/wp-content/uploads/2024/03/alt-text-friend-r1-1024x576.jpg" alt="Batman laughing and describing a meme he saw to Commissioner Gordon, who looks unamused." class="wp-image-7884" srcset="https://cloudfour.com/wp-content/uploads/2024/03/alt-text-friend-r1-1024x576.jpg 1024w, https://cloudfour.com/wp-content/uploads/2024/03/alt-text-friend-r1-300x169.jpg 300w, https://cloudfour.com/wp-content/uploads/2024/03/alt-text-friend-r1-768x432.jpg 768w, https://cloudfour.com/wp-content/uploads/2024/03/alt-text-friend-r1-1536x864.jpg 1536w, https://cloudfour.com/wp-content/uploads/2024/03/alt-text-friend-r1.jpg 1600w" sizes="(max-width: 1024px) 100vw, 1024px"></figure>



<p>If you take nothing else away from this post, I want you to remember this: <strong>Write alternative text as if you’re describing the image to a friend.</strong></p>



<p>I find people often get too wrapped up in what the “rules” are for alternative text. Sure, there are lots of things to be aware of, but almost all of them are covered under this simple guideline. If you were talking to a friend on the phone* and wanted to describe <a href="https://knowyourmeme.com/photos/234739-i-have-no-idea-what-im-doing">a meme you saw</a>, you might say “There was this dog wearing safety glasses, surrounded by chemistry equipment, saying ‘I have no idea what I’m doing.’”</p>



<p>Keep it brief, but informative! Give the most important information and leave out unimportant details.</p>



<p>*I know, I know, no one actually <em>talks</em> on the phone anymore. If the very idea is stressing you out, you can replace “describing a meme over the phone” with “describing a meme to your getaway driver as you flee the scene of your latest heist, pursued by the detective that’s been hot on your heels since the job in Naples, so she’s understandably stressed and doesn’t want to look at a funny dog picture on your phone while she’s driving.”</p>



<h2 class="wp-block-heading">Context matters</h2>



<p>Of course, context informs your description. If I was describing the chemistry dog meme to a chemistry major, I might want to emphasize all the things the dog is doing wrong. If I was describing it to someone who works in a hazardous spill response team, I might mention that the dog is pouring one of the mystery fluids into a coffee mug. As an example, let’s consider this image from a Batman movie.</p>



<figure class="wp-block-image size-large"><img width="1024" height="512" src="https://cloudfour.com/wp-content/uploads/2024/03/Gary-Oldman-Commissioner-Gordon-Batsignal-1-1024x512.jpg" alt="A frame from Christopher Nolan's Batman movies of Commissioner Gordon standing on the rooftop preparing to light the batsignal." class="wp-image-7882" srcset="https://cloudfour.com/wp-content/uploads/2024/03/Gary-Oldman-Commissioner-Gordon-Batsignal-1-1024x512.jpg 1024w, https://cloudfour.com/wp-content/uploads/2024/03/Gary-Oldman-Commissioner-Gordon-Batsignal-1-300x150.jpg 300w, https://cloudfour.com/wp-content/uploads/2024/03/Gary-Oldman-Commissioner-Gordon-Batsignal-1-768x384.jpg 768w, https://cloudfour.com/wp-content/uploads/2024/03/Gary-Oldman-Commissioner-Gordon-Batsignal-1.jpg 1080w" sizes="(max-width: 1024px) 100vw, 1024px"></figure>



<p>A fan-run Batman wiki, where the audience is likely familiar with the characters, might use this as alternative text:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Commissioner Gordon, wearing his signature trenchcoat, stands near the batsignal, considering whether to light it, alerting Batman.</p>
</blockquote>



<p>In contrast, an article about cinematography, where the author chose this image as an example of a framing technique and Batman isn’t the primary focus, might highlight other features of the image.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>In this shot, director Christopher Nolan has framed Commissioner Gordon, played by Gary Oldman, pensively looking away from the unlit batsignal towards the sky, with the city visible behind him, reminding the viewer of the stakes inherent in his decision to summon Batman.</p>
</blockquote>



<p>And if Batman had a social media account, he might take a slightly different approach.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Gordon looking goofy AF after I dipped out while he was talking again LOL</p>
</blockquote>



<p>All of these are valid alternative text choices for audiences in certain contexts.</p>



<h2 class="wp-block-heading">You don’t need to say it’s an image</h2>



<p>Most screenreaders will say “image” or “graphic” before reading the alternative text, so starting with “Image of X” is redundant. The only time you need to mention the image itself is if the medium matters, such as artwork and diagrams. For example, “a charcoal sketch of a cute kitten,” “the blueprints for the mansion we’re going to rob,” or “A chart showing a 50% decline in sales over three years.”</p>



<h2 class="wp-block-heading">But you should include punctuation</h2>



<p>Eric Bailey reminds us to <a href="https://thoughtbot.com/blog/add-punctuation-to-your-alt-text">add punctuation to our alternative text</a>. If your alternative is only a single sentence, it might feel strange to include punctuation. But remember that it won’t be read in isolation, it will be read along with the surrounding text. Ending your sentence with a proper period or other punctuation will communicate to the screen reader how to transition from the alternative text to the following text.</p>



<h2 class="wp-block-heading">Should alternative text describe race?</h2>



<p>Sometimes! Like everything, it’s contextual. I highly recommend reading “<a href="https://ux.shopify.com/the-case-for-describing-race-in-alternative-text-attributes-a093380634f2">The case for describing race in alternative text attributes</a>” by Tolu Adegbite, and “<a href="https://tink.uk/thoughts-on-skin-tone-and-text-descriptions.md-notes-on-synthetic-speech/">Thoughts on skin tone and text descriptions</a>” by Léonie Watson. They both point out that by not mentioning race, we may be unintentionally reinforcing the idea that the unspoken default is white.</p>



<p>Think of it the same way we encourage the adoption of gender pronouns in profiles even for cisgender people. It’s not about whether anyone might be confused about what your pronouns are. It’s about normalizing the idea that everyone has pronouns and they may not match your expectations.</p>



<h2 class="wp-block-heading">Decorative images don’t need alternative text, but your image probably isn’t decorative.</h2>



<p>It’s true that purely decorative images are allowed to use an empty string for their <code>alt</code> attribute. However, as Eric Bailey points out, <a href="https://www.smashingmagazine.com/2021/06/img-alt-attribute-alternate-description-decorative/">your image is probably not decorative</a>. In a nutshell, the term “decorative” means the image <em>does not visually communicate information</em>, not that it is used as decoration.</p>



<p>A spacer GIF is decorative. Image borders are decorative. A button that only contains an icon image is <em>not</em> decorative. This photo of the safe that we’ll be cracking in the mansion is <em>not</em> decorative. A company’s logo is <em>not</em> decorative.</p>



<h3 class="wp-block-heading">What about a person’s avatar, displayed next to their name?</h3>



<p>This is a tricky case. If you have a person’s photo displayed right next to their name, and the alternative text only contains their name, then the screen reader will hear the person’s name twice, which adds no value. For example, at the top of this page, you can see my avatar next to my name. We’ve opted to leave the <code>alt</code> attribute empty on the avatar, because we don’t want screen reader users to hear “Image, Scott Vandehey, Link, Scott Vandehey.”</p>



<p>But… it’s not quite that simple. It depends on what the image shows. In “<a href="https://jakearchibald.com/2021/great-alt-text/">Writing great alt text: Emotion matters</a>,” Jake Archibald makes the case that an avatar photo of himself on a conference site that showed him hiding behind a plant actually contained information that <em>should</em> be expressed in the alternative text, and opted for “Jake Archibald hiding behind a plant.”</p>



<p>As usual, context matters, and when in doubt, try reaching out to real users of assistive technology for opinions.</p>



<h2 class="wp-block-heading">Conclusion</h2>



<p>I know I started out by saying you only need to remember one guideline, and then gave you, like, seven. But I stand by what I said. Don’t stress about crafting the perfect alternative text. Just write it the way you would describe the photo of the bag of glistening diamonds you just stole from the Duke of Chauntelburry as you drive along the coast after finally shaking the detective, the wind blowing through your hair, with your getaway driver still chuckling about that dog-doing-chemistry meme you described to her earlier.</p>



<h2 class="wp-block-heading">Learn More:</h2>



<ul class="wp-block-list">
<li>Perkins School for the Blind, “<a href="https://www.perkins.org/resource/how-write-alt-text-and-image-descriptions-visually-impaired/">How to Write Alt Text and Image Descriptions for the visually impaired</a>”</li>



<li>The A11y Project, “<a href="https://www.a11yproject.com/posts/are-you-making-these-five-mistakes-when-writing-alt-text/">Are You Making These Five Mistakes When Writing Alt Text?</a>”</li>



<li>Axess Lab, “<a href="https://axesslab.com/alt-texts/">Alt-texts: The Ultimate Guide</a>”</li>



<li>Bureau of Internet Accessibility, “<a href="https://www.boia.org/blog/8-common-image-alt-text-mistakes-to-stop-making">8 Common Image Alt Text Mistakes to Stop Making</a>”</li>



<li>Carie Fisher, “<a href="https://www.smashingmagazine.com/2020/05/accessible-images/">Accessible Images For When They Matter Most</a>”</li>



<li>Steve Faulkner, “<a href="https://codepen.io/stevef/pen/XWQWgrj">The Perils of Using Double Quotes Inside Alt Text</a>”</li>



<li>Geoff Graham, “<a href="https://css-tricks.com/just-how-long-should-alt-text-be/">Just How Long Should Alt Text Be?</a>”</li>



<li>Stefan Judis, “<a href="https://www.stefanjudis.com/today-i-learned/css-content-accepts-alternative-text/">The CSS ‘content’ Property Accepts Alternative Text</a>”</li>



<li>Shawn Lauriat, “<a href="https://www.youtube.com/watch?v=gHOYghYYNIM">How Learning ASL Improved My Alt Text</a>”</li>



<li>Veronica Lewis, “<a href="https://veroniiiica.com/seven-myths-about-alt-text/">Seven Myths About Alt Text</a>”</li>



<li>Elaina Natario, “<a href="https://thoughtbot.com/blog/alt-vs-figcaption">Alt vs Figcaption</a>”</li>



<li>Scott O’Hara, “<a href="https://www.scottohara.me/blog/2019/05/22/contextual-images-svgs-and-a11y.html">Contextually Marking Up Accessible Images and SVGs</a>”</li>



<li>Adrian Roselli, “<a href="https://adrianroselli.com/2024/04/long-alt.html">Long Alt</a>”</li>



<li>Wren, “<a href="https://mannequinrentals.help/2024/03/21/an-attempted-guide-to-writing-effective-alt-and-descriptive-text-for-art/">An Attempted Guide to Writing Effective Alt and Descriptive Text for Art</a>”</li>
</ul>

<hr>
<h2>We’re Cloud Four</h2>
<p>We solve complex responsive web design and development challenges for ecommerce, healthcare, fashion, B2B, SaaS, and nonprofit organizations.</p>

<p><a href="https://cloudfour.com/made/"><b>See our work</b></a></p>]]></description>
      <pubDate>Mon, 22 Apr 2024 15:30:00 +0000</pubDate>
      <link>https://cloudfour.com/thinks/write-alt-text-like-youre-talking-to-a-friend/</link>
      <dc:creator>Sharing what we learn about the responsive web – Cloud Four</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/4432562729</guid>
    </item>
    <item>
      <title><![CDATA[How would you build Wordle with just HTML &amp; CSS?]]></title>
      <description><![CDATA[<p>I’ve been thinking about the questions folks are typically asked in front-end interviews these days, and how well those questions assess a candidate's depth of understanding of web standard technologies, and not just their ability to employ JavaScript algorithms and third-party frameworks. It made me think about the sort of questions I would like to hear or ask in an interview myself.</p>
<p>I think this sort of question would be a useful one:</p>
<blockquote>
<p>"How would you attempt to build <a href="https://www.nytimes.com/games/wordle/index.html"><em>Wordle</em></a> (<em>...or some other complex app</em>) if you could only use HTML and CSS? Which features of that app would make more sense to build with JavaScript than with other technologies? And, can you imagine a change or addition to the HTML or CSS standard that could make any of those features more straight-forward to build?"</p>
</blockquote>
<p>Now, to be clear, this is meant to be a contrived exercise. I don't actually think a game app like Wordle <em>should</em> be built without using any JavaScript, <em>(though I do think the app could be better if it functioned at all without it!)</em>. In reality, I feel there are many user experience enhancements to an app like Wordle that <em>only</em> JavaScript is capable of providing.</p>
<p>Still, I like this question as an exercise. Discussing any approaches to this challenge will reveal the candidate's broad knowledge of web standards–including new and emerging HTML and CSS features–and as a huge benefit, it would help select for the type of folks who are best suited to lead us out of the JavaScript over-reliance problems that are <a href="https://infrequently.org/2024/01/performance-inequality-gap-2024/">holding back the web today</a>.</p>
<p>Interview questions aside though, I also thought this would be a fun little experiment to explore myself! So I took a few hours yesterday to do just that, and in the rest of this post I'll cover how I approached it.</p>
<h2>A Quick Attempt</h2>
<p><strong>Here's a CodePen link with the demo and code for my first attempt:</strong></p>
<p><a href="https://codepen.io/scottjehl/details/LYvQxqa">Wordle, just HTML &amp; CSS</a></p>
<p>By the way, if you aren't here to play games (or you want to save some time), you can find the answer to the puzzle in this little disclosure:</p>
<details style="margin-bottom: 1em"><summary>Reveal Answer</summary>PROUD</details>
<p>The approach I took for this example leans hard on HTML form validation for the game logic. I'm using a text <code>input</code> for each letter of the word, with <code>pattern</code> attributes describing the correct letter. The browser can natively use a pattern attribute to determine whether the input's value is "valid," which in the case of this contrived example can mean that a letter is correct.</p>
<p>To give an example, if the correct word was "TIGER" then the first letter's <code>input</code> could have a pattern attribute like this: <code>pattern="[tT]"</code> which would match lowercase and uppercase T. Additionally, the inputs have a <code>maxlength</code> attribute to limit their content length to 1 character.</p>
<p>HTML:</p>
<pre><code>&lt;input type="text" id="input1" name="input1" maxlength="1" pattern="[tT]"&gt;
</code></pre>
<p>Through these two attributes alone, we can already write a CSS selector to visually display whether a typed letter is correct or not, using a gray or green background similar to Wordle itself, like this:</p>
<pre><code>input[type="text"]:invalid {
    background-color: lightgray;
}
input[type="text"]:valid {
    background-color: lightgreen;
}
</code></pre>
<p>Neat! But there are some problems.</p>
<h3>Delaying Validation</h3>
<p>As written above, the validation styles will show immediately as you are writing the letters, rather than after you type the entire word, which makes it too easy to change your answer as you see whether each letter is correct or not. That's not how Wordle works!</p>
<p>Instead, we want the validation to kick in <em>only</em> after a word is entered and complete. You could try to do this in a number of ways. I decided to use the <code>:not()</code> and <code>:has()</code> CSS selectors to qualify the validation styles to only apply when the user is done entering a whole word. The game board grid uses an HTML <code>table</code> element and each "word" row sits in a <code>tr</code> element. The following styles will apply to <code>tr</code> elements that do not contain focused or empty inputs, meaning every input has a character in it and the user is no longer typing in any of the inputs.</p>
<p>To gauge whether the inputs are empty, I added a <code>placeholder</code> attribute to the HTML <code>input</code>, which conveniently pairs with the <code>:placeholder-shown</code> selector that matches empty inputs. Here's the HTML and CSS:</p>
<p>HTML:</p>
<pre><code>&lt;input type="text" id="input1" name="input1" maxlength="1" pattern="[tT]" placeholder=" "&gt;
</code></pre>
<p>CSS:</p>
<pre><code>/* invalidate only non-focused populated row */
tr:not(:has(input:placeholder-shown, input:focus)) input[type="text"]:invalid {
    background-color: lightgray;
}
/* validate only non-focused populated row */
tr:not(:has(input:placeholder-shown, input:focus)) input[type="text"]:valid {
    background-color: lightgreen;
}
</code></pre>
<p>Nice. Now the form validation happens after a complete guess for each row. <em>Small note: iOS Safari seems to only allow this selector to work with placeholder attributes that have at least some sort of string, so I added a blank space to its value, but there's probably a better character that I could use (maybe "A"?).</em></p>
<h3>Disabling Future and Completed Rows</h3>
<p>Another thing I wanted to do to match the game's behavior is to only allow interaction with the inputs in the active row. Admittedly, I didn't fully pull this one off for restricting keyboard focus, but I was at least able to disable touch and click-focus on those inputs by using a similar selector to the prior ones, and disabling pointer events.</p>
<pre><code>/* disallow next row until row is populated */
tr:has(input:placeholder-shown) ~ tr input,
/* disallow revisiting populated row */
tr:not(:has(input:placeholder-shown, input:focus)) input {
    pointer-events: none;
}
</code></pre>
<p>Good enough, moving on.</p>
<h3>Announcing a Win</h3>
<p>When the correct word is typed, it would be nice to hide the rows after the correct guess and display a message that the game is over. To do this, I used a rule similar to the ones above, with some additional generated content to announce the win.</p>
<pre><code>tr:not(:has(input:placeholder-shown, input:invalid, input:focus)) ~ tr {
    display: none;
}
table tr:not(:has(input:placeholder-shown, input:invalid, input:focus)):after {
  content: "You Win!";
  /* ...some additional less critical styles here */
}
</code></pre>
<p><strong>Improvement to test:</strong> This winning announcement should be accessible, and I'm not sure that CSS generated content will be announced in assistive tech, even if I use an aria-live region. Perhaps it'd be better to toggle the CSS display of a status message that follows the table, or have a status column on the right with messages that can be revealed as needed. I'm thinking on this.</p>
<h3>So What About Tan?</h3>
<p>Now, admittedly, so far the game only offers the gray and green states of Wordle, which represent letters that are either not in the word at all (gray), or are entirely correct and in the right place (green). Unlike the real Wordle, it's missing the "tan" state that tells you a letter is in the word, yet in the wrong place. This means that the game is playable, but it's pretty hard!</p>
<p>Unfortunately, HTML validation only offers a binary answer, and what we really want here is a sort of in-between state that's valid but not super-valid.</p>
<h4>Validating for Tan</h4>
<p>So pattern validation only allows us to match for one type of pattern, but that pattern could refer to the whole word if we wanted to use it for the tan state instead of the green state. For example, the following pattern would match every letter in TIGER, both lowercase and uppercase: <code>pattern="[tTiIgGeErR]"</code>.</p>
<p>Once again, our familiar CSS could target that valid pattern to color an input tan if it contains a letter that is in the word at all:</p>
<pre><code>/* validate only non-focused populated row */
tr:not(:has(input:placeholder-shown, input:focus)) input[type="text"]:valid {
  background-color: tan;
}
</code></pre>
<p>So now our problem is tan works, but green doesn't. We need a little help showing the green state for letters that are not ONLY in the word, but in the right place as well.</p>
<h3>Extending with JavaScript</h3>
<p>Okay. Let's add some JavaScript to get 3 states instead of 2.</p>
<p>The following 10 lines add an <code>input</code> event handler (which fires whenever the user changes the input's value) to all of the inputs in the page. It will grab a regular expression pattern in that input's custom <code>data-correct-pattern</code> attribute and add a <code>data-incorrect</code> attribute to the input when the pattern doesn't match the input's current value:</p>
<pre><code>document.querySelectorAll("input").forEach(inpt =&gt; {
  inpt.addEventListener("input", function(e) {
    const correct = this.value.match(new RegExp(this.getAttribute("data-correct-pattern")));
    if (correct) {
      this.removeAttribute("data-incorrect");
    } else {
      this.setAttribute("data-incorrect", true);
    }
  });
});
</code></pre>
<p>With that attribute toggle, I can write some CSS to add the green state! Here are my 3 states, the third of which using that new data-incorrect attribute:</p>
<pre><code>/* invalidate only non-focused populated row */
tr:not(:has(input:placeholder-shown, input:focus)) input[type="text"]:invalid {
  background-color: lightgray;
}
/* validate only non-focused populated row */
tr:not(:has(input:placeholder-shown, input:focus)) input[type="text"]:valid {
  background-color: tan;
}
/* validate only non-focused populated row */
tr:not(:has(input:placeholder-shown, input:focus)) input[type="text"]:not([data-incorrect]) {
  background-color: lightgreen;
}
</code></pre>
<p><strong>You can view the demo of this version here:</strong></p>
<p><a href="https://codepen.io/scottjehl/details/JjVpNEb">Wordle, almost just HTML &amp; CSS</a></p>
<h2>Caveats and Implementation Ideas</h2>
<p>Needless to say, these examples don't do everything that the real Wordle does, so I won't attempt to itemize the differences here. Perhaps the most important difference to note is that there's no validation of real words, so you can enter any old letters you please.</p>
<p>I also want to explore how to make the state of the app persistently make sense to assistive technology, which might mean having an additional table column with cells containing the result of each validation (toggling the display of various possible messages so that they are accessible. Maybe that'll be a followup post (and if you're interested, <a href="https://blog.mayank.co/how-i-built-an-accessible-version-of-wordle">here's a more practical article about improving actual-Wordle's accessibility</a>).</p>
<p>Also, um, the puzzle is always the same answer? True! The real Wordle has a different word every day. A fuller implementation of an approach like this would need some dynamic handling on the server side to deliver different HTML patterns for the word of the day.</p>
<p>Having said all of that, I find it pretty awesome that HTML and CSS alone can get us so far today, and it's totally possible that there are different HTML patterns that could pull off all 3 colors without JS already, as well (perhaps if we built a large QUERTY keyboard out of radio inputs...)</p>
<h2>What if :valid was a function?</h2>
<p>Okay, as for the the last part of my original question. What could be added to HTML or CSS to make that last example possible without JavaScript? One thing comes to mind for me: an ability to validate multiple patterns on one input.</p>
<p>For example, let's revisit that <code>pattern</code> attribute value. The existing <code>pattern="[tTiIgGeErR]"</code> will match for all of the letters in the word Tiger, uppercase or lowercase. From that, you get a simple yes or no match for whether the letter is in the word at all, again like this:</p>
<pre><code>input[type="text"]:valid {
  background-color: tan;
}
</code></pre>
<p>That gives us the tan color, but again we still have the green pattern to worry about. What if that <code>:valid</code> pseudo-class allowed for arguments, though? If it did, then I could pass a string to it, like <code>:valid("[gG]")</code> to specifically match for a g or G. Even better, maybe the value could accept the standard CSS <code>attr()</code> function too, which would allow the HTML to define additional patterns for the CSS to validate. In my second demo, I used a <code>data-correct-pattern</code> attribute to define that second pattern.</p>
<p>Here's how the markup and CSS might look for that dreamed-up feature, which could allow the second demo to work without JS:</p>
<p>HTML:</p>
<pre><code>&lt;input type="text" id="input1" name="input1" maxlength="1" pattern="[tTiIgGeErR]" data-correct-pattern="[tT]" placeholder=" "&gt;
</code></pre>
<p>CSS:</p>
<pre><code>/* invalidate only non-focused populated row */
tr:not(:has(input:placeholder-shown, input:focus)) input[type="text"]:invalid {
  background-color: lightgray;
}
/* validate only non-focused populated row */
tr:not(:has(input:placeholder-shown, input:focus)) input[type="text"]:valid {
  background-color: tan;
}
/* validate only non-focused populated row */
tr:not(:has(input:placeholder-shown, input:focus)) input[type="text"]:valid(attr(data-correct-pattern)) {
  background-color: lightgreen;
}
</code></pre>
<p>JavaScript:</p>
<pre><code>...none needed.
</code></pre>
<p>Maybe I'll file an issue in the CSS standard asking for <code>:valid(attr(...))</code>! :)</p>
<h2>Thanks!</h2>
<p>I hope you enjoyed this post. Please feel free to reach out with ideas or questions or improvements on <a href="https://mstdn.social/@scottjehl">Mastodon</a>. Thanks for reading!</p>
]]></description>
      <pubDate>Fri, 05 Apr 2024 00:00:00 +0000</pubDate>
      <link>https://scottjehl.com/posts/wordleish/</link>
      <dc:creator>Scott Jehl&#39;s Blog</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/4416806994</guid>
    </item>
    <item>
      <title><![CDATA[On popover accessibility: what the browser does and doesn’t do]]></title>
      <description><![CDATA[
      <p>One of the premises of the new <a href="https://html.spec.whatwg.org/multipage/popover.html#the-popover-attribute">popover attribute</a> is that it comes with general accessibility considerations “built in”. What does “built in accessibility” actually mean for browsers that support popover?</p>
<p><em>See also: <a href="https://www.scottohara.me/blog/2025/03/14/popovers.html">Scott's post Popping preconceived popover ponderings</a> and <a href="https://www.youtube.com/watch?v=uZRp7yY8SS0">Hidde's talk on popovers</a>, and other posts about <a href="https://hidde.blog/popover-semantics/">popover semantics</a>, <a href="https://hidde.blog/popover-accessibility/positioning-anchored-popovers/">positioning popovers</a> and the <a href="https://hidde.blog/dialog-modal-popover-differences/">difference with dialogs and other components</a>.</em></p>
<details class="details-note">
  <summary>About this post</summary>
<p><em><strong>NOTE</strong>: except for this note, this whole post was co-written with <a href="https://www.scottohara.me/">Scott O’Hara</a> (thanks Scott!). See also Scott's post, <a href="https://www.scottohara.me/blog/2024/03/22/popover-a11y.html">popover accessibility</a> and his post <a href="https://www.scottohara.me/blog/2025/03/14/popovers.html">Popping preconceived popover ponderings</a>. Whether you're a developer, designer or accessibility specialist, hearing “accessibility is built in” probably makes you want to know what exactly is built-in. For popover, this actually changed quite a bit over time, after discussions at Open UI and with WHATWG. At first, the plan was to</em> <a href="https://www.w3.org/2021/04/08-openui-minutes.html"><em>introduce a <code>popup</code> element</em></a> <em>with built-in roles. Later, an attribute ended up making more sense (more on that in the post). For that attribute, and thanks to the great effort of Scott and others, some “accessibility guardrails” have now emerged. And they shipped in most browsers. I hope this post helps you understand better what accessibility is “built-in” when you use popover, and what is not.</em></p>
</details>
<div class="toc" style="padding-left: 1em; border-left: .75em solid var(--comments-border-color);">
  <style>.toc a { color: var(--text-color); font-weight: bold; } .toc li { margin-left: 0; }</style>
  <h2 style="font-size: 1em; margin-top: 0; text-transform: uppercase; letter-spacing: 2px;display: inline-block;">In this post</h2>
  <ol style="list-style: none; margin-left: 1em;">
    <li><a href="https://hidde.blog/popover-accessibility/#heading-2">Accessibility semantics</a></li>
    <li><a href="https://hidde.blog/popover-accessibility/#heading-3">What browsers do</a> (aria-expanded, aria-details, group, keyboard accessibility)</li>
    <li><a href="https://hidde.blog/popover-accessibility/#heading-4">What browsers don't do</a></li>
    <li><a href="https://hidde.blog/popover-accessibility/#heading-5">Conclusion</a></li>
  </ol>
</div>
<h2><strong>Accessibility semantics</strong></h2>
<p>The “built-in” accessibility of popover is in the addition of <em>guardrails</em>: browsers try to improve accessibility where they can. These guardrails exist mostly in the form of browsers augmenting <em>accessibility semantics</em>. Before we get into what those guardrails are, let's clarify what that term means.</p>
<p>Many features of HTML have some amount of <a href="https://www.w3.org/TR/wai-aria-1.2/#at_support">accessibility semantics</a> associated with them - e.g., roles, states and properties. This is information that a web page exposes, which browsers then pass on to platform accessibility APIs. They do this, so that assistive technologies can build UIs around them (see: <a href="https://hidde.blog/how-accessibility-trees-inform-assistive-tech/">How accessibility trees inform assistive tech</a>). These semantics are sometimes baked into native HTML elements. For instance, headings and lists have implicit roles (heading and list, respectively). Other elements, like the checkbox input type, have an implicit role as well as additional states and properties. Developers can use HTML elements with such “built-in” semantics. But they can also set, overwrite and augment accessibility semantics more directly in their HTML structure, using <a href="https://www.w3.org/TR/wai-aria-1.2/">WAI-ARIA</a>.</p>
<p>The thing with the popover attribute is that it doesn’t have a built-in role. After all, it’s not an element. Its purpose is to only add “popover behaviour”, as discussed in <a href="https://hidde.blog/popover-semantics/">Popover semantics</a>. In that sense, <code>popover</code> is a bit like <a href="https://html.spec.whatwg.org/dev/interaction.html#the-tabindex-attribute">tabindex</a> or <a href="https://html.spec.whatwg.org/dev/interaction.html#contenteditable">contenteditable</a>. These attributes also add behaviour: tabability and editability behaviours, respectively.</p>
<p>A major reason for this choice is that there are a number of components that exhibit popover behaviours. Examples include menus, “toast” messages, sub-navigation lists of links and tooltips. You can use popover on a specific element, then it will get that element's role. Or you can use it with a generic element, and add a <code>role</code> that best matches what you are building.</p>
<p>So, while the default role is ‘generally’ not handled by the attribute (more on that later), there are other semantics (properties and states) that the attribute will expose. Browsers can take care of those with some degree of confidence.</p>
<h2>What browsers do</h2>
<p>There are two semantics that the browser should take care of when you use <code>popover</code>, and its associated <code>popovertarget</code> attribute. Additionally, there is some keyboard focus behaviour that may also be handled automatically, depending on the type of popover you are using.</p>
<h3>The <code>aria-expanded</code> state</h3>
<p>First, <code>aria-expanded</code>. This state is exposed on the element that invokes the popover, currently limited to buttons (for a variety of reasons that would require a whole other article to talk about - so this is all you get right now). When a popover is invoked by / associated with a button with the popovertarget attribute, the browser will automatically convey whether the popover is in the expanded (rendered) state, or if it is in the collapsed (hidden) state. This is implemented in Edge, Chrome, Firefox and Safari.</p>
<p>For the following example, the ‘heyo’ button will automatically convey whether its associated popover list is in the expanded or collapsed state, based on whether the popover list is invoked as a popover.</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">popovertarget</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>p</span><span class="token punctuation">&gt;</span></span>
  Heyo
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>

…

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ul</span> 
  <span class="token attr-name">aria-label</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Heyo subpages<span class="token punctuation">"</span></span> 
  <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>p</span> 
  <span class="token attr-name">popover</span>
<span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">&gt;</span></span></code></pre>
<p><em>Note: the state won’t be applied if script, rather than the declarative attribute, does the opening on click of any button (or any other element). Needless to say: it also doesn’t work if there isn’t an invoking button, for instance, and script invokes this popover (because in that case, there isn’t any expanding going on). Additionally, if you force open your popover using CSS display block, then it will not be rendered as a popover - and thus the button will still communicate that the “popover” is in the collapsed state. Also, if you’re doing that - forcing your popover open with CSS - maybe you have some things you need to reconsider with your UI.</em></p>
<h3>The <code>aria-details</code> relationship</h3>
<p>When the popover doesn’t immediately follow its invoking button in the accessibility tree, browsers are supposed to create an <a href="https://w3c.github.io/aria/#aria-details"><code>aria-details</code></a> relationship on the popover’s invoking button with its associated popover. At the time of writing, this is implemented in Chrome, Edge and Firefox.</p>
<p>For instance, in the following markup snippet an implicit <code>aria-details</code> relationship will be made with the button that invokes the popover, because the button and the popover are not immediate siblings in the accessibility tree.</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">popovertarget</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>foo</span><span class="token punctuation">&gt;</span></span>something<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span>...<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">role</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>whatever</span> <span class="token attr-name">popover</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>foo</span><span class="token punctuation">&gt;</span></span>...<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
</code></pre>
<p>Similarly, an <code>aria-details</code> relationship will be made with the next markup snippet too, because even though the popover and its invoking button are siblings, the popover is a <em>previous</em> sibling to the invoking element, and it might not be understood <em>which</em> element is the popover, because it doesn’t immediately follow the element that invoked it.</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">role</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>whatever</span> <span class="token attr-name">popover</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>foo</span><span class="token punctuation">&gt;</span></span>...<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">popovertarget</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>foo</span><span class="token punctuation">&gt;</span></span>something<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span></code></pre>
<p>In contrast, the next two examples have no aria-details association because that would be unnecessary. For the first, the popover is the immediate next sibling in the accessibility tree (note divs are generic and often ignored when they do not provide information important to accessibility). For the second, the button is a descendant of the popover, so browsers do not need to tell users that the button they are interacting with is about the context they are within. That’d be silly.</p>
<pre class="language-html"><code class="language-html"><span class="token comment">&lt;!--  
  example 1: 
  popover immediate sibling in acc tree 
--&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">popovertarget</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>m</span><span class="token punctuation">&gt;</span></span>something<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>presentational-styles-only</span><span class="token punctuation">&gt;</span></span>
  …
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">role</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>menu</span> <span class="token attr-name">popover</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>m</span><span class="token punctuation">&gt;</span></span>...<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>

<span class="token comment">&lt;!-- 
  example 2:
  button descendant of popoover
--&gt;</span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dialog</span> <span class="token attr-name">popover</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>d</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">popovertarget</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>d</span><span class="token punctuation">&gt;</span></span>close<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
  …
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dialog</span><span class="token punctuation">&gt;</span></span>
</code></pre>
<p>For more information on how <code>aria-details</code> works, check out the <a href="https://w3c.github.io/aria/#aria-details">details in the ARIA spec</a>.</p>
<p>Note: <code>aria-details</code> is often confused with <code>aria-describedby</code>. That makes sense, “details” and “descriptions” are very similar. However, these two properties expose different information. <code>aria-describedby</code> takes the associated element’s content, flattens it into a single text string, and exposes that as the ‘description’ or ‘hint’ of the element which the attribute is specified. In contrast, <code>aria-details</code> only <em>informs</em> a user that there is additional information about the current element. That might not seem useful, until you know that screen readers largely provide quick-keys to navigate to and from that associated element which provides the details.</p>
<p>At the time of writing, navigating into the referenced content using quick-keys is supported by JAWS and NVDA (<a href="https://github.com/nvaccess/addonFiles/pull/392">release notes</a>), but not VoiceOver.</p>
<p>Here’s a quick demo of that with JAWS 2023 in Edge 124. JAWS lets us jump to the details content if we press <code>Alt + Ins + D</code>:</p>
<iframe width="100%" height="315" style="max-width: 100%; aspect-ratio: 16 / 9" src="https://www.youtube-nocookie.com/embed/CTD0deXgfK8?si=MrjoOe7w6PDk2fIS&amp;&amp;disablekb=1" title="JAWS demo of aria-details on YouTube" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen=""></iframe>
<p>In NVDA (2023.3.4), tested with Edge 124, it works slightly differently: when you press the shortcut (<code>NVDA + D</code>), we don't jump to the details content, but it is read out after the shortcut is pressed:</p>
<iframe width="100%" height="315" style="max-width: 100%; aspect-ratio: 16 / 9" src="https://www.youtube-nocookie.com/embed/lE1D1Jnmv6E?si=yDRylZaGCfgooX1E&amp;&amp;disablekb=1" title="NVDA demo of aria-details on YouTube" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen=""></iframe>
<p>(see <a href="https://codepen.io/hidde/pen/abxZVLX/">demo on CodePen</a>; note: announcements and shortcuts depend on the user's settings, versions, etc)</p>
<p>In the following demo, you can see how the <code>aria-details</code> relationship works between popovers and their invokers (in JAWS 2023 with Edge 124):</p>
<iframe width="100%" height="315" style="max-width: 100%; aspect-ratio: 16 / 9" src="https://www.youtube-nocookie.com/embed/8sbF3_KNzUc?si=l5g3vbxdwdKO76IE&amp;disablekb=1" title="An example of the aria-details relationship in JAWS in Edge on YouTube" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen=""></iframe>
<p>(video contains screenshot of code, see <a href="https://codepen.io/scottohara/pen/vYQqjbZ?editors=1000">demo on CodePen</a>)</p>
<p>In summary: the <code>aria-details</code> association is not announced by JAWS when we focus the invoking button for the first time. This is because the corresponding popover is hidden, so the association isn't made yet. After we open the popover, JAWS announces the button's “has details” association while it is open, to hear it we navigate away and back. This is also how it works in NVDA, which, in addition, also requires you to switch to forms mode to actually hear the relationship announced.</p>
<p>Warning: even if the <code>aria-details</code> association is implemented, it may not be completely ironed out in how the UX behaves for people. For instance, there isn't currently a way for  users to find out about the details relationship once it is established, like when the popover opened. It requires for the user to move away from the button and return to it, at which point the relationship is announced. Maybe it would be helpful if the browser would fire some kind of event, to let AT like JAWS know that an element representing details has appeared.</p>
<p>We mention this not to deter you from using popover or to indicate that anyone is doing something “wrong” here. Rather, this is a relatively new feature that people still need to figure out some of the UX kinks around. Feedback is welcome, and to help ensure the best UX is provided, please reach out to the necessary browsers / AT with your thoughts.</p>
<h3>The <code>group</code> role</h3>
<p>As mentioned above, popover can be used on any element, including elements that don’t have a built-in role, like <code>div</code>. But even without a role, it’s likely that the contents of a popover form some kind of meaningful whole. This is why in Chrome, Edge and Firefox, a role of <code>group</code> is automatically added to popovers if they would otherwise have no role, or a role of <code>generic</code> (for instance, divs and spans).</p>
<p>The <code>group</code> role is added, so that assistive technology can have the option to expose the boundaries of the popover that is being displayed. This can be important to users, because a popover is a behavior and visual treatment of its content. How is one to know where such content begins or ends if it doesn’t have boundaries to expose?</p>
<p>It’s important to know that an unnamed group is often ignored by screen readers. This is because otherwise the Internet would be riddled with unhelpful “group” announcements. (See also why Webkit made the decision to remove list semantics from lists that have been styled to <em>not look like lists</em>. These topics are related). Here though, it again comes down to what assistive technology wants to do. By exposing the group role for the popover, now the element can be named by authors, which will force the group role to be exposed in most cases. Then, if AT decided they want to do something special for popover groups, they now have the opportunity to do so.</p>
<h3>Keyboard accessibility</h3>
<p>One more aspect of “built-in accessibility” that browsers do for your popover, is take care of some keyboard behaviors.</p>
<h4>Moving focus back to invoking element</h4>
<p>Edge/Chrome, Firefox and Safari will all return focus to the invoking element when you close the popover (only if you are inside of it). This is useful, because if focus was on an element inside the popover, the default would be to return focus to the start of the document. Various groups of users would get lost, increasingly so on pages with a lot of content. Moving focus back to the invoking element helps ensure people can quickly return to what they were doing, rather than spending time having to re-navigate to where they think they were last.</p>
<h4>Popover content in tab order</h4>
<p>Desktop browsers also do something else: they add the popover content into the tab order just after the invoking button. Even if that’s not where the popover is in the DOM.</p>
<p>Imagine this DOM structure:</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">popovertarget</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>p</span><span class="token punctuation">&gt;</span></span>Open popover<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span>content… content… <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>#<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>link 1<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span>content… content… <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>#<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>link 2<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">popover</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>p<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>#<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>link 3<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span></code></pre>
<p>When the popover opens, and you press <code>Tab</code>, you might think you’d jump to “link 1”, the next interactive element in the DOM. Except, in desktop browsers, you will jump to “link 3” instead. The browser basically moves the popover content’s position in tab order to just after its invoking button. This takes it out of its expected position in the tab order. That improves the user experience, because it is likely that upon opening the popover, users will want to interact with its contents.</p>
<p>Keep in mind: browsers adjust the Tab order for instances like this, but they don't adjust the placement of the content in the accessibility tree. This is why the <code>aria-details</code> association was implemented. This special Tab order behavior helps ensure logical focus order for keyboard accessibility. However, we should still strive to make sure our popovers come <em>after</em> the invoking element in the DOM.</p>
<p>But since there will be times where the exact location of the popover in the DOM may be out of one’s control, this behavior is still quite welcome. For instance, if the popover happens to be far away in the DOM, having to go through the rest of the document before reaching the popover would be a nuisance. It would be highly unexpected and unwanted to have to navigate through all other focusable elements in the DOM, prior to the popover one just opened. WCAG <a href="https://www.w3.org/TR/WCAG22/#focus-order">2.4.3 Focus Order</a> requires focusable elements to receive focus in an order that “preserves meaning and operability”. This special browser Tab restructuring helps ensure that requirement can be met.</p>
<h2>What browsers don’t do</h2>
<p>We can keep this one short: the browser will not do <em>anything</em> apart from the behaviours listed above. Browsers will not add behaviors based on which elements you use or which role you add to them. The <code>popover</code> attribute is merely a first step for us to build new components.</p>
<h2>Conclusion</h2>
<p>The <code>popover</code> attribute provides a starting point for building popover-like interactions on the web. Browsers don't magically make your components accessible, that's not a thing. But there are some specific keyboard behaviours included with popover, as well as these semantics:</p>
<ul>
<li>In specific cases, browsers set the <code>aria-expanded</code> state and/or set the <code>aria-details</code> relationship on the invoker.</li>
<li>Browsers apply the <code>group</code> role to the <code>popover</code> content if it doesn’t have a role of its own, so that assistive technologies can expose their boundaries.</li>
</ul>
<p><em>Browser support note: at the time of writing, Safari only sets <code>aria-expanded</code>, not <code>aria-details</code>. It also doesn't add a <code>group</code> role fallback.</em></p>

      <hr>
      <p>Originally posted as <a href="https://hidde.blog/popover-accessibility/">On popover accessibility: what the browser does and doesn’t do</a> on <a href="https://hidde.blog">Hidde's blog</a>.</p>
      <p><a href="mailto:hidde@hiddedevries.nl?subject=Reply%20to:%20On popover accessibility: what the browser does and doesn’t do">Reply via email</a></p>
    ]]></description>
      <pubDate>Thu, 21 Mar 2024 00:00:00 +0000</pubDate>
      <link>https://hidde.blog/popover-accessibility/</link>
      <dc:creator>Hidde&#39;s blog</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/4402333560</guid>
    </item>
    <item>
      <title><![CDATA[Syntax Highlighting code snippets with Prism and the Custom Highlight API]]></title>
      <description><![CDATA[<p></p><figure><a href="https://www.bram.us/wordpress/wp-content/uploads/2024/02/custom-highlights-cover.png"><img loading="lazy" decoding="async" src="https://www.bram.us/wordpress/wp-content/uploads/2024/02/custom-highlights-cover.png" alt="" width="560" height="293" class="alignnone size-medium wp-image-34327" srcset="https://www.bram.us/wordpress/wp-content/uploads/2024/02/custom-highlights-cover.png 3602w, https://www.bram.us/wordpress/wp-content/uploads/2024/02/custom-highlights-cover-560x293.png 560w, https://www.bram.us/wordpress/wp-content/uploads/2024/02/custom-highlights-cover-1120x585.png 1120w, https://www.bram.us/wordpress/wp-content/uploads/2024/02/custom-highlights-cover-768x401.png 768w, https://www.bram.us/wordpress/wp-content/uploads/2024/02/custom-highlights-cover-1536x803.png 1536w, https://www.bram.us/wordpress/wp-content/uploads/2024/02/custom-highlights-cover-2048x1070.png 2048w, https://www.bram.us/wordpress/wp-content/uploads/2024/02/custom-highlights-cover-1568x819.png 1568w" sizes="(max-width: 560px) 100vw, 560px"></a><figcaption>Screenshot of the <a href="https://www.bram.us/2024/02/18/custom-highlight-api-for-syntax-highlighting/#highlighting-contenteditable">On-the-fly syntax highlighting of <code>[contenteditable]</code> code snippets</a> demo with DevTools open. Thanks to Prism’s Tokenizer and the Custom Highlight API, the code is highlighted without the need for a bunch of <code>&lt;span&gt;</code> elements wrapped around each token.</figcaption></figure>
<p></p>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<div class="intro">
<p>The way syntax highlighters on the web typically work is a to wrap all tokens in <code>&lt;span&gt;</code> elements with a proper class and use CSS to colorize them.</p>
<p>Thanks to the <a href="https://drafts.csswg.org/css-highlight-api-1/">CSS Custom Highlight API</a> you can skip the step where you riddle your DOM tree with a bunch of <code>&lt;span&gt;</code>s to add the color information.</p>
</div>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h3><a name="toc" href="https://www.bram.us/2024/02/18/custom-highlight-api-for-syntax-highlighting/#toc">#</a> Table of Contents</h3>
<ul>
<li>
		<a href="https://www.bram.us/2024/02/18/custom-highlight-api-for-syntax-highlighting/#custom-highlight-api-101">Custom Highlight API 101</a>
	</li>
<li>
		<a href="https://www.bram.us/2024/02/18/custom-highlight-api-for-syntax-highlighting/#browser-support">Browser support</a>
	</li>
<li>
		<a href="https://www.bram.us/2024/02/18/custom-highlight-api-for-syntax-highlighting/#syntax-highlighting-static-code-snippets">Syntax Highlighting Static Code Snippets</a><p></p>
<ul>
<li>
				<a href="https://www.bram.us/2024/02/18/custom-highlight-api-for-syntax-highlighting/#how-it-works">How it works</a><p></p>
<ul>
<li><a href="https://www.bram.us/2024/02/18/custom-highlight-api-for-syntax-highlighting/#how-it-works--step1">Step 1: Setting up shop</a></li>
<li><a href="https://www.bram.us/2024/02/18/custom-highlight-api-for-syntax-highlighting/#how-it-works--step2">Step 2: Tokenizing the code</a></li>
<li><a href="https://www.bram.us/2024/02/18/custom-highlight-api-for-syntax-highlighting/#how-it-works--step3">Step 3: Associating the tokens with the highlights</a></li>
</ul>
</li>
</ul>
</li>
<li>
		<a href="https://www.bram.us/2024/02/18/custom-highlight-api-for-syntax-highlighting/#the-downsides-of-the-custom-highlight-api">The downsides of the Custom Highlight API</a><p></p>
<ul>
<li><a href="https://www.bram.us/2024/02/18/custom-highlight-api-for-syntax-highlighting/#issue--limited-styling">Limited Styling Options</a></li>
<li><a href="https://www.bram.us/2024/02/18/custom-highlight-api-for-syntax-highlighting/#issue--no-textareas">No textareas</a></li>
<li><a href="https://www.bram.us/2024/02/18/custom-highlight-api-for-syntax-highlighting/#issue--no-pointer-events">No pointer events (yet)</a></li>
<li><a href="https://www.bram.us/2024/02/18/custom-highlight-api-for-syntax-highlighting/#issue--text-selection-bug">(Chrome/Firefox Bug) Color information gets lost on selecting text</a></li>
<li><a href="https://www.bram.us/2024/02/18/custom-highlight-api-for-syntax-highlighting/#issue--performance-regression">(Chrome/Safari Bug) Performance regression with many highlights</a></li>
</ul>
</li>
<li>
		<a href="https://www.bram.us/2024/02/18/custom-highlight-api-for-syntax-highlighting/#highlighting-contenteditable">On-the-fly syntax highlighting of <code>[contenteditable]</code> code snippets</a>
	</li>
<li>
		<a name="in-closing" href="https://www.bram.us/2024/02/18/custom-highlight-api-for-syntax-highlighting/#in-closing">In Closing</a></li>
<li>
		<a href="https://www.bram.us/2024/02/18/custom-highlight-api-for-syntax-highlighting/#spread-the-word">Spread the word</a></li>
</ul>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h3><a name="custom-highlight-api-101" href="https://www.bram.us/2024/02/18/custom-highlight-api-for-syntax-highlighting/#custom-highlight-api-101">#</a> Custom Highlight API 101</h3>
<p>The <a href="https://drafts.csswg.org/css-highlight-api-1/">CSS Custom Highlight</a> API offers a mechanism for styling arbitrary ranges of a document identified by script.</p>
<p>Custom highlights are represented by <code>Highlight</code> objects. These objects hold one or more <a href="https://dom.spec.whatwg.org/#concept-range">Range</a>s that identify which parts of the text should be highlighted. Finally, these Highlights need to be registered into the highlight registry so that you can style them from within CSS.</p>
<p>In code:</p>
<pre><code class="language-js" style="tab-size: 2">// Create Highlight
const h = new Highlight();

// Add ranges to the Highlight
const r1 = new Range();
r1.setStart(text.firstChild, 13);
r1.setEnd(text.firstChild, 28);
h.add(r1);

const r2 = new Range();
r2.setStart(text.firstChild, 38);
r2.setEnd(text.firstChild, 56);
h.add(r2);

// Register the highlight into the registry
// This makes the ::highlight() CSS work
CSS.highlights.set('example', h);</code></pre>
<p>That last line enables the use of <code>::highlight(example)</code> in your CSS to style the highlight:</p>
<pre><code class="language-css" style="tab-size: 2;">::highlight(example) {
	color: hotpink;
}</code></pre>
<p>You can see this code in action in this technical demo. Characters 13-28 and characters 38-56 get the <code>hotpink</code> color applied because they got added to the <code>example</code> Custom Highlight.</p>
<p class="codepen" data-height="350" data-default-tab="result" data-slug-hash="WNmmGEb" data-user="bramus" data-token="3b62649444802d7c9d2ee0c4817a72ca" style="height: 350px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>See the Pen <a href="https://codepen.io/bramus/pen/WNmmGEb/3b62649444802d7c9d2ee0c4817a72ca">CSS Custom Highlight API Example</a> by Bramus (<a href="https://codepen.io/bramus">@bramus</a>)on <a href="https://codepen.io">CodePen</a>.</span></p>
<p><script async="" src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script></p>
<p>It is possible to register multiple highlights via <code>CSS.highlights.set</code>, as long as they have a different name. A Range can be added to multiple highlights – so all styles get applied – and ranges can also overlap.</p>
<p>A typical use-case to using the Custom Highlight API would be to highlight search results as you type in a text input. The following demo by <a href="https://front-end.social/@jensimmons">Jen Simmons</a> shows this:</p>
<p class="codepen" data-height="500" data-default-tab="result" data-slug-hash="vYbvrjq" data-user="jensimmons" style="height: 500px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>See the Pen <a href="https://codepen.io/jensimmons/pen/vYbvrjq">Custom Highlights demo</a> by Jen Simmons (<a href="https://codepen.io/jensimmons">@jensimmons</a>)on <a href="https://codepen.io">CodePen</a>.</span></p>
<p><script async="" src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script></p>
<div class="note">
<p>💁‍♂️ For more info on this API, go read <a href="https://blog.logrocket.com/getting-started-css-custom-highlight-api/">Getting started with the CSS Custom Highlight API</a>.</p>
</div>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h3><a name="browser-support" href="https://www.bram.us/2024/02/18/custom-highlight-api-for-syntax-highlighting/#browser-support">#</a> Browser support</h3>
<div class="note note--supergreen">
<p>💡 Although this post was originally published in February 2024, the section below is constantly being updated. <em>Last update: Feb, 2024</em>.</p>
</div>
<p>Here is an up-to-date list of browser support for the <a href="https://drafts.csswg.org/css-highlight-api-1/">CSS Custom Highlight API</a>:</p>
<dl class="grid">
<dt>Chrome <em>(Blink)</em></dt>
<dd>
<p>✅ Supported as of Chrome 105</p>
</dd>
<dt>Firefox <em>(Gecko)</em></dt>
<dd>
<p>👨‍🔬 Experimental support in Firefox Nightly</p>
</dd>
<dt>Safari <em>(WebKit)</em></dt>
<dd>
<p>✅ Supported as of Safari 17.2.</p>
</dd>
</dl>
<p>The pen embedded below will indicate if the browser you are currently using supports the CSS Custom Highlight API or not:</p>
<p class="codepen" data-height="550" data-default-tab="result" data-slug-hash="ExMMKzB" data-user="bramus" style="height: 550px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>See the Pen <a href="https://codepen.io/bramus/pen/ExMMKzB">CSS Custom Highlight API Support Test</a> by Bramus (<a href="https://codepen.io/bramus">@bramus</a>) on <a href="https://codepen.io">CodePen</a>.</span></p>
<p><script async="" src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script></p>
<p>To stay up-to-date regarding browser support, you can follow these tracking issues:</p>
<ul>
<li>Chrome <em>(Blink)</em>: <a href="https://crbug.com/40163546">Issue #40163546</a> — Fixed</li>
<li>Firefox <em>(Gecko)</em>: <a href="https://bugzil.la/1703961">Issue #1703961</a> — ASSIGNED</li>
<li>Safari <em>(WebKit)</em>: <a href="https://bugs.webkit.org/show_bug.cgi?id=204903">Issue #204903</a> — RESOLVED FIXED</li>
</ul>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h3><a name="syntax-highlighting-static-code-snippets" href="https://www.bram.us/2024/02/18/custom-highlight-api-for-syntax-highlighting/#syntax-highlighting-static-code-snippets">#</a> Syntax Highlighting Static Code Snippets</h3>
<p>During one of the conversations at the most recent <a href="https://www.w3.org/Style/CSS/">CSS Working Group</a> F2F meeting in Mountain View, <a href="https://crisal.io/words/about/">Emilio</a> (Firefox) and <a href="https://xanthir.com/">Tab</a> (Google) came up with the idea to use the Custom Highlight API to do syntax highlighting.</p>
<p>Taking their idea for a spin, I whipped up this CodePen. <mark>The Pen acts as its own demo: the CSS and JavaScript you see on the page is the actual code that gets executed and thereby syntax highlighted.</mark></p>
<p class="codepen" data-height="600" data-default-tab="result" data-slug-hash="VwRqGVo" data-user="bramus" style="height: 600px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>See the Pen <a href="https://codepen.io/bramus/pen/VwRqGVo">Syntax highlighting code blocks with Prism and the Custom Highlight API</a> by Bramus (<a href="https://codepen.io/bramus">@bramus</a>) on <a href="https://codepen.io">CodePen</a>.</span></p>
<p><script async="" src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script></p>
<details>
<summary>Huh, you can show CSS and JavaScript directly in the HTML?!</summary>
<div>
<p>In case you’re wondering: the trick to make inlined <code>script</code> and <code>style</code> elements visible here is to apply <code>display: block</code> onto them.</p>
<pre><code class="language-css">script[visible], style[visible] {
	display: block;
	white-space: pre;
	font-family: monospace;
	overflow-x: auto;
	… 
}</code></pre>
<p>Yes, that totally works! 🤩</p>
<p></p></div>
</details>
<p style="text-align: center; font-size: 14px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h4><a name="how-it-works" href="https://www.bram.us/2024/02/18/custom-highlight-api-for-syntax-highlighting/#how-it-works">#</a> How it works</h4>
<p>At its core, the demo consists of three steps:</p>
<ol>
<li>Register Custom Highlights for all possible token types</li>
<li>Tokenize the code</li>
<li>Associate the extracted tokens with the relevant Custom Highlight</li>
</ol>
<div class="note note--info">
<p>The key part here being the tokenization step. Tokenization is the process of taking an input string and identifying the smallest individual parts (tokens) that the language can understand.</p>
<p>For example, when tokenizing <code>const name = "Bramus";</code> as JavaScript, you end up with these tokens <em>(ignoring whitespace)</em>:</p>
<ul>
<li><code>const</code></li>
<li><code>name</code></li>
<li><code>=</code></li>
<li><code>"Bramus"</code></li>
<li><code>;</code></li>
</ul>
<p>Each of these tokens is of a certain type in <a href="https://developer.mozilla.org/docs/Web/JavaScript/Reference/Lexical_grammar">JavaScript’s Lexical Grammar</a>. For example, the <code>const</code> token is a so called <code>keyword</code>.</p>
</div>
<p style="text-align: center; font-size: 14px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h5><a name="how-it-works--step1" href="https://www.bram.us/2024/02/18/custom-highlight-api-for-syntax-highlighting/#how-it-works--step1">#</a> Step 1: Setting up shop</h5>
<p>For each token type that there is in JavaScript and CSS – the two languages the demo needs code highlighting for – the demo sets up a <code>::highlight</code> pseudo:</p>
<pre style="max-height: 300px; overflow-y: scroll;"><code class="language-css" style="tab-size: 2;">/* From prism.css */
::highlight(parameter) {
	color: #1a1a1a;
}

::highlight(comment), ::highlight(prolog), ::highlight(doctype), ::highlight(cdata) {
	color: slategray;
}

::highlight(punctuation) {
	color: #999;
}

::highlight(property), ::highlight(tag), ::highlight(boolean), ::highlight(number), ::highlight(constant), ::highlight(symbol), ::highlight(deleted), ::highlight(class-name) {
	color: #905;
}

::highlight(selector), ::highlight(attr-name), ::highlight(string), ::highlight(char), ::highlight(builtin), ::highlight(inserted) {
	color: #690;
}

::highlight(operator), ::highlight(entity), ::highlight(url) {
	color: #a67f59;
	background: hsla(0, 0%, 100%, 0.5);
}

::highlight(atrule), ::highlight(attr-value), ::highlight(keyword) {
	color: #07a;
}

::highlight(function) {
	color: #dd4a68;
}

::highlight(regex), ::highlight(important), ::highlight(variable) {
	color: #e90;
}

::highlight(important), ::highlight(bold) {
	font-weight: bold;
}

::highlight(italic) {
	font-style: italic;
}

::highlight(entity) {
	cursor: help;
}</code></pre>
<div class="info">
<p>These styles are borrowed from <a href="https://prismjs.com/">Prism.js</a>, which is used to do the tokenization in step 2 further down.</p>
</div>
<p>The CSS is useless without having registered the custom highlights, so to make them usable you need to register 1 highlight for each token type:</p>
<pre><code class="language-js" style="tab-size: 2;">const tokenTypes = [ 'comment', 'prolog', 'doctype', 'cdata', 'punctuation', 'namespace',
	'property', 'tag', 'boolean', 'number', 'constant', 'symbol', 'deleted',
	'selector', 'attr', 'string', 'char', 'builtin', 'inserted', 'operator',
	'entity', 'url', 'string', 'atrule', 'attr', 'keyword', 'function', 'class',
	'regex', 'important', 'variable', 'important', 'bold', 'italic', 'entity',
	'parameter', 'class-name'];

tokenTypes.forEach(tokenType =&gt; {
	CSS.highlights.set(tokenType, new Highlight());
});</code></pre>
<p style="text-align: center; font-size: 14px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h5><a name="how-it-works--step2" href="https://www.bram.us/2024/02/18/custom-highlight-api-for-syntax-highlighting/#how-it-works--step2">#</a> Step 2: Tokenizing the code</h5>
<p><to>To do the tokenization, I rely on the wonderful <a href="https://prismjs.com/">Prism.js</a>. Since I only need the tokenizer, Prism is loaded in <a href="https://prismjs.com/#features-full:~:text=plain%20class%20instead.-,Manual%20highlighting,-If%20you%20want">manual mode</a> so it doesn’t try to do its own thing on load.</to></p>
<pre><code class="language-html">&lt;script src="prism.js" data-manual&gt;&lt;/script&gt;</code></pre>
<p>With Prism loaded in manual mode, you can run its tokenizer on demand:</p>
<pre><code class="language-js" style="tab-size: 2;">// Get all inlined script and style blocks
const codeBlocks = document.querySelectorAll('script[visible], style[visible]');
		
// Loop ’m one by one
for (const codeBlock of codeBlocks) {

	// Tokenize it.
	// When it’s a STYLE element, tokenize it as CSS. Otherwise, assume JavaScript.
	let tokens = Prism.tokenize(
		codeBlock.innerText,
		codeBlock.tagName == 'STYLE' ? Prism.languages.css : Prism.languages.javascript
	);

	// …
}</code></pre>
<p>Calling <code>Prism.tokenize</code> gives back a list of tokens that holds information about which token is where. For example:</p>
<pre><code class="language-js" style="tab-size: 2;">[
	{
		"type": "keyword",
		"content": "const",
		"length": 5
	},
	" name ",
	{
		"type": "operator",
		"content": "=",
		"length": 1
	},
	" ",
	{
		"type": "string",
		"content": "\"Bramus\"",
		"length": 8
	},
	{
		"type": "punctuation",
		"content": ";",
		"length": 1
	}
]</code></pre>
<p style="text-align: center; font-size: 14px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h5><a name="how-it-works--step3" href="https://www.bram.us/2024/02/18/custom-highlight-api-for-syntax-highlighting/#how-it-works--step3">#</a> Step 3: Associating the tokens with the highlights</h5>
<p>To associate the extracted tokens with their relevant highlight, it’s a matter of looking all tokens and determining their start and end position within the original source code.</p>
<pre><code class="language-js" style="tab-size: 2;">// Position within the code
let pos = 0;

// Loop all tokens
for (const token of tokens) {
	if (token.type) {
		// Create new range for the current token
		const range = new Range();
		range.setStart(codeBlock.firstChild, pos);
		range.setEnd(codeBlock.firstChild, pos + token.length);

		// Add the range to the registered highlight
		CSS.highlights.get(token.alias ?? token.type)?.add(range);
	}

	// Update position
	pos += token.length;
}</code></pre>
<p>And with that, the code snippets are syntax highlighted using the CSS Custom Highlight API 🙂</p>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h3><a name="the-downsides-of-the-custom-highlight-api" href="https://www.bram.us/2024/02/18/custom-highlight-api-for-syntax-highlighting/#the-downsides-of-the-custom-highlight-api">#</a> The downsides of the Custom Highlight API</h3>
<p>Unfortunately it’s not all rainbows and unicorns with the Custom Highlight API, as it comes with some limitations and has some implementation bugs.</p>
<p style="text-align: center; font-size: 14px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h4><a name="issue--limited-styling" href="https://www.bram.us/2024/02/18/custom-highlight-api-for-syntax-highlighting/#issue--limited-styling">#</a> Limited Styling Options</h4>
<p>Just like <a href="https://drafts.csswg.org/css-pseudo-4/#highlight-styling">the case with regular highlights</a>, the styling of highlights <a href="https://drafts.csswg.org/css-highlight-api-1/#applicable-properties">is limited to only a small set of CSS properties</a> that do not affect layout. Only allowed properties are <code>color</code>, <code>background-color</code>, <code>text-decoration</code>, <code>text-shadow</code>, <code>stroke-color</code>/<code>fill-color</code>/<code>stroke-width</code>, and custom properties.</p>
<p>That means you can’t change text to be bold or italic, something is often used when highlighting code snippets. At the CSS WG, <a href="https://github.com/w3c/csswg-drafts/issues/8355">w3c/csswg-drafts#8355</a> was raised to see if more styles could be allowed or not.</p>
<p style="text-align: center; font-size: 14px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h4><a name="issue--no-textareas" href="https://www.bram.us/2024/02/18/custom-highlight-api-for-syntax-highlighting/#issue--no-textareas">#</a> No textareas</h4>
<p>Custom Highlights don’t work on <code>textarea</code>s. I’m not entirely sure if this is an implementation bug of a limitation of the spec. I’ve filed <a href="https://github.com/w3c/csswg-drafts/issues/9971">w3c/csswg-drafts#9971</a> at the CSS WG to discuss this.</p>
<div class="note">☝️ You can work around this by highlighting a <code>[contenteditable]</code>, which I explore further down this post: <a href="https://www.bram.us/2024/02/18/custom-highlight-api-for-syntax-highlighting/#highlighting-contenteditable">On-the-fly syntax highlighting of <code>[contenteditable]</code> code snippets</a></div>
<p style="text-align: center; font-size: 14px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h4><a name="issue--no-pointer-events" href="https://www.bram.us/2024/02/18/custom-highlight-api-for-syntax-highlighting/#issue--no-pointer-events">#</a> No pointer events (yet)</h4>
<p>Any of the Custom Highlights don’t have pointer events. That means that if you, for example, hover a Custom Highlight, you can’t show a tooltip when hovering that specific highlight.</p>
<p>The CSS Working Group <a href="https://github.com/w3c/csswg-drafts/issues/7513#issuecomment-1201631397">has resolved on adding <code>CSS.highlights.highlightsFromPoint(x, y)</code></a> that would eventually allow this. It would work something like this:</p>
<pre><code class="language-js" style="tab-size: 2;">document.addEventListener("click", function(e) {
	if (e.defaultPrevented) {
		return;
	}

	for (let highlight of CSS.highlights.highlightsFromPoint(e.clientX, e.clientY)) {
		highlight.dispatchEvent(e);
		if (e.defaultPrevented) return;
	}
});</code></pre>
<p>Problem at the time of writing is that this feature is still in its very early stages: it lacks a specification, tests, and – more importantly – an implementation.</p>
<p style="text-align: center; font-size: 14px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h4><a name="issue--text-selection-bug" href="https://www.bram.us/2024/02/18/custom-highlight-api-for-syntax-highlighting/#issue--text-selection-bug">#</a> (Chrome/Firefox Bug) Color information gets lost on selecting text</h4>
<div class="update">
<p><b>UPDATE 2024.04.22</b> – This bug got fixed in Chrome 126.0.6426.0 <em>(see screenshots in <a href="https://twitter.com/bramus/status/1782354582187761875">this tweet</a>)</em></p>
</div>
<p>Both in Chrome and Firefox there is a loss of color information when selecting text that has custom highlights. Safari is not affected by this.</p>
<div class="table">
<div>
<figure>
<a href="https://www.bram.us/wordpress/wp-content/uploads/2024/02/custom-highlights-chrome.png"><img loading="lazy" decoding="async" src="https://www.bram.us/wordpress/wp-content/uploads/2024/02/custom-highlights-chrome.png" alt="" width="560" height="344" class="alignnone size-medium wp-image-34307" srcset="https://www.bram.us/wordpress/wp-content/uploads/2024/02/custom-highlights-chrome.png 2476w, https://www.bram.us/wordpress/wp-content/uploads/2024/02/custom-highlights-chrome-560x344.png 560w, https://www.bram.us/wordpress/wp-content/uploads/2024/02/custom-highlights-chrome-1120x688.png 1120w, https://www.bram.us/wordpress/wp-content/uploads/2024/02/custom-highlights-chrome-768x471.png 768w, https://www.bram.us/wordpress/wp-content/uploads/2024/02/custom-highlights-chrome-1536x943.png 1536w, https://www.bram.us/wordpress/wp-content/uploads/2024/02/custom-highlights-chrome-2048x1257.png 2048w, https://www.bram.us/wordpress/wp-content/uploads/2024/02/custom-highlights-chrome-1568x963.png 1568w" sizes="(max-width: 560px) 100vw, 560px"></a><figcaption>Selecting text with Custom Highlights in Chrome 122</figcaption></figure>
<p></p></div>
<div>
<figure>
<a href="https://www.bram.us/wordpress/wp-content/uploads/2024/02/custom-highlights-safari.png"><img loading="lazy" decoding="async" src="https://www.bram.us/wordpress/wp-content/uploads/2024/02/custom-highlights-safari.png" alt="" width="560" height="344" class="alignnone size-medium wp-image-34307"></a><figcaption>Selecting text with Custom Highlights in Safari 17.4</figcaption></figure>
<p></p></div>
</div>
<p>For Chrome specifically, I have filed <a href="https://crbug.com/325442893">CrBug 325442893</a> for this.</p>
<p style="text-align: center; font-size: 14px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h4><a name="issue--performance-regression" href="https://www.bram.us/2024/02/18/custom-highlight-api-for-syntax-highlighting/#issue--performance-regression">#</a> (Chrome/Safari Bug) Performance regression with many highlights</h4>
<div class="update">
<p><b>UPDATE 2024.04.09</b> – Chrome 125 sports some improvements to scroll performance with Custom Highlights. While initial paint still does take a lot of time, scrolling itself is much much smoother <em>(see screenshots in <a href="https://twitter.com/bramus/status/1777683455372935393">this tweet</a>)</em>.</p>
</div>
<p>Highlighting something like the jQuery Source Code with Custom Highlights takes its toll on the browser. Highlighting the 63338 tokens that make up <a href="https://code.jquery.com/jquery-3.7.1.js">the unminimized jQuery 3.7.1 source code</a> is possible but when performing a scroll, the performance just tanks.</p>
<p></p><figure>
		<a href="https://www.bram.us/wordpress/wp-content/uploads/2024/02/custom-highlight-api-performance.png"><img loading="lazy" decoding="async" src="https://www.bram.us/wordpress/wp-content/uploads/2024/02/custom-highlight-api-performance.png" alt="" width="560" height="241" class="alignnone size-medium wp-image-34315" srcset="https://www.bram.us/wordpress/wp-content/uploads/2024/02/custom-highlight-api-performance.png 1766w, https://www.bram.us/wordpress/wp-content/uploads/2024/02/custom-highlight-api-performance-560x241.png 560w, https://www.bram.us/wordpress/wp-content/uploads/2024/02/custom-highlight-api-performance-1120x482.png 1120w, https://www.bram.us/wordpress/wp-content/uploads/2024/02/custom-highlight-api-performance-768x331.png 768w, https://www.bram.us/wordpress/wp-content/uploads/2024/02/custom-highlight-api-performance-1536x661.png 1536w, https://www.bram.us/wordpress/wp-content/uploads/2024/02/custom-highlight-api-performance-1568x675.png 1568w" sizes="(max-width: 560px) 100vw, 560px"></a><figcaption>Chrome DevTools recording of highlighting the jQuery Source with Custom Highlights <em>(left half)</em> and then scrolling the page <em>(right half)</em>.</figcaption></figure>
<p></p>
<p>This drop in scroll performance is noticeable in Chrome and Safari; Firefox seems to deal with it pretty much fine. Chrome 122 performs the worst here, but Chrome 124 (current Canary) already sports some good improvements in this area.</p>
<p>For Chrome specifically, I have filed <a href="https://crbug.com/325589486">CrBug 325589486</a> for this.</p>
<div class="note">
<p>In general though, the Custom Highlight API performs better than the heavy DOM tree approach. It’s only when crossing a certain amount of tokens that the old approach turns out to be better. I don’t know the exact number that marks the cutoff point. Feel free to test and let me know 😉</p>
</div>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h3><a name="highlighting-contenteditable" href="https://www.bram.us/2024/02/18/custom-highlight-api-for-syntax-highlighting/#highlighting-contenteditable">#</a> On-the-fly syntax highlighting of <code>[contenteditable]</code> code snippets</h3>
<p>With not being able to use the Custom Highlight API <code>textarea</code> elements, I wondered if we could make a on-the-fly syntax highlighter using a <code>&lt;pre&gt;</code> element with <code>[contenteditable]</code>.</p>
<p>The short answer here is yes, as demonstrated in the following demo. Every time you type in a character, the current highlights get cleared, the code re-tokenized, and the new highlights get applied.</p>
<p class="codepen" data-height="600" data-default-tab="result" data-slug-hash="MWxLjEo" data-user="bramus" data-token="1fc408cf0f0e079596f3cac8efd569f1" style="height: 600px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>See the Pen <a href="https://codepen.io/bramus/pen/MWxLjEo/1fc408cf0f0e079596f3cac8efd569f1">On-the-fly Syntax Highlighting of [contenteditable]</a> by Bramus (<a href="https://codepen.io/bramus">@bramus</a>) on <a href="https://codepen.io">CodePen</a>.</span></p>
<p><script async="" src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script></p>
<p>The long answer is creating this editor came with a set of complications introduced by <code>[contenteditable]</code> itself.</p>
<p>The first thing I had to do was disable rich text editing which <code>[contenteditable]</code> gives you out-of-the-box – e.g. hitting CMD+B allows making text bold. This can easily be disabled by setting the value of <code>[contenteditable]</code> to <code>plaintext-only</code>. Firefox doesn’t support this value but does support <code>[contenteditable=true]</code> so that browser gets a special treatment</p>
<pre><code class="language-js" style="tab-size: 2;">// The pre element that can be edited
const codeBlock = document.querySelector('pre[contenteditable]');

// Allow only plaintext editing
// Firefox doesn’t do 'plaintext-only', but does do 'true'
codeBlock.setAttribute('contenteditable', 'plaintext-only');
if (codeBlock.contentEditable != 'plaintext-only') {
	codeBlock.setAttribute('contenteditable', 'true');
}</code></pre>
<p>The biggest challenge was that <code>[contenteditable="plaintext-only"]</code> still uses HTML breaks from time to time and also creates new <a href="https://developer.mozilla.org/docs/Web/API/Text">text nodes</a> in the <code>&lt;pre&gt;</code> element whenever you hit return.</p>
<p>To cater for this I ended up adding some logic that would flatten the number of text nodes to just 1 before the tokenizing step. This also required me to add some extra logic to make sure the curser position is retained.</p>
<div class="update">
<p><code>UPDATE 2024.04.09</code> Turns out there is a built-in method to achieve what I want: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Node/normalize"><code>Node.normalize();</code></a>. With that, the code becomes as simple as this:</p>
<pre><code class="language-js" style="tab-size: 2;">const flattenTextNodes = codeBlock =&gt; codeBlock.normalize();</code></pre>
<p>Thanks to reader Amin E for <a href="https://twitter.com/amin_e_/status/1777694061069283611">letting me know about this</a>.</p>
</div>
<details>
<summary>For reference, the code I had before Amin E told me about <code>Node.normalize();</code></summary>
<pre><code class="language-js" style="tab-size: 2;">// Helper function to get the caret position inside an element
// Plays nice with an element having multiple text nodes as its children
const getCaretPosition = (el) =&gt; {
	const selectionInfo = window.getSelection(el);
	let node = selectionInfo.anchorNode;
	let pos = selectionInfo.anchorOffset;

	// Need to loop all previous siblings here becase you only get the position
	// in the current text node, so we need to offset the previousSibling here
	// TODO: This might need a rewrite to make sure it only loops textNodes
	while (node.previousSibling) {
		pos += node.previousSibling.length;
		node = node.previousSibling;
	}

	return pos;
}

// Helper function to flatten the text nodes in an element to
// only 1
const flattenTextNodes = (codeBlock) =&gt; {
	if (codeBlock.childNodes.length &gt; 1) {
		// Record current caret position
		const caretPosition = getCaretPosition(codeBlock);

		// Set firstChild to hold the whole text
		codeBlock.firstChild.textContent = codeBlock.firstChild.wholeText;			

		// Remove all other text nodes
		let node = codeBlock.firstChild;
		while (node.nextSibling) {
			codeBlock.removeChild(node.nextSibling);
		}

		// Restore caret position
		// @TODO: There is 1 little bug: when selecting the entire last line and hitting
		// backspace, you end up at position 0;
		window.getSelection(codeBlock).setPosition(codeBlock.firstChild, caretPosition);
	}
}</code></pre>
</details>
<p>This <code>flattenTextNodes</code> helper gets called right before the code gets tokenized. It gets the job done, but also introduces <a href="https://en.wikipedia.org/wiki/Flash_of_unstyled_content">FOUC</a> upon adding lines to the <code>[contenteditable]</code></p>
<p>The combined code to highlight a <code>codeBlock</code> becomes this:</p>
<pre><code class="language-js" style="tab-size: 2;">const highlight = (codeBlock, lang = Prism.languages.javascript) =&gt; {
	// We need the contenteditable to hold only 1 child textNode that
	// contains all the text. If we don’t do this, the highlight ranges
	// might go out of bounds.
	flattenTextNodes(codeBlock);

	// Tokenize the code
	let tokens = Prism.tokenize(
		codeBlock.innerText,
		lang
	);

	// Clear all current highlights
	tokenTypes.forEach(tokenType =&gt; {
		CSS.highlights.get(tokenType).clear();
	});

	// Paint all token highlights
	paintTokenHighlights(codeBlock, tokens);
}</code></pre>
<p>For completeness, I also added a little tad of JS to map the TAB key to actually inserting a TAB character:</p>
<pre><code class="language-js" style="tab-size: 2;">codeBlock.addEventListener('keydown', e =&gt; {
	// The tab key should insert a tab character
	if (e.keyCode == 9) {
		document.execCommand('insertHTML', false, '	');
		e.preventDefault();
	}
});</code></pre>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h3><a name="in-closing" href="https://www.bram.us/2024/02/18/custom-highlight-api-for-syntax-highlighting/#in-closing">#</a> In Closing</h3>
<p>I must say I’m pretty excited about the Custom Highlight API and what it allows to do. However, I do hope some of the current gaps can be fixed to make this API workable under all conditions. <code>[contenteditable]</code> in itself could also use some love and interop-pushing.</p>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h3><a href="https://www.bram.us/2024/02/18/custom-highlight-api-for-syntax-highlighting/#spread-the-word" name="spread-the-word">#</a> Spread the word</h3>
<p>To help spread the contents of this post, feel free to retweet the announcements made on social media:</p>
<blockquote class="twitter-tweet">
<p lang="en" dir="ltr">Y’all seem to like the posts on using the Custom Highlight API with <a href="https://twitter.com/prismjs?ref_src=twsrc%5Etfw">@prismjs</a> to syntax highlight code …</p>
<p>So here’s a full article digging into the details + a new demo that does on-the-fly highlighting of code you type into a [contenteditable] element.<a href="https://t.co/d98PVlHPKh">https://t.co/d98PVlHPKh</a> <a href="https://t.co/bjH3rHhhy6">pic.twitter.com/bjH3rHhhy6</a></p>
<p>— Bramus (@bramus) <a href="https://twitter.com/bramus/status/1759336795077066847?ref_src=twsrc%5Etfw">February 18, 2024</a></p></blockquote>
<p> <script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></p>
<p><iframe src="https://front-end.social/@bramus/111954731763483141/embed" class="mastodon-embed" style="max-width: 550px; display: block; margin: 0 auto; border: 0" width="550" allowfullscreen="allowfullscreen"></iframe><script src="https://front-end.social/embed.js" async="async"></script></p>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<div class="note">
	<p><b>🔥 Like what you see? Want to stay in the loop? Here's how:</b></p>
	<ul>
            <li><a href="https://twitter.com/bramus">Follow @bramus on Twitter</a></li>
			<li><a href="https://front-end.social/@bramus">Follow @bramus on Mastodon</a></li>
            <li><a href="https://twitter.com/bramusblog">Follow @bramusblog on Twitter</a></li>
            <li><a href="https://bram.us/feed">Follow bram.us using RSS</a></li>
        </ul>
</div>
]]></description>
      <pubDate>Sun, 18 Feb 2024 21:48:46 +0000</pubDate>
      <link>https://www.bram.us/2024/02/18/custom-highlight-api-for-syntax-highlighting/</link>
      <dc:creator>Bram.us</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/4370857614</guid>
    </item>
    <item>
      <title><![CDATA[The Cost of Avoiding Annoyance]]></title>
      <description><![CDATA[<p><strong>tl;dr</strong> Living with annoying technological constraints might be less costly than maintaining the sophisticated workarounds we create to avoid them.</p>
<p>I really enjoyed this <a href="https://htmx.org/essays/no-build-step/">well-reasoned article</a> explaining why htmx, the open-source hypermedia JS framework, does not have a build step.</p>
<p>I liked that the premise – “we don’t have a build step” — is not presented as a universal truth for its readers — “therefore <em>you</em> shouldn’t have a build step”.</p>
<p>Instead, the author outlines the benefits <em>and tradeoffs</em> of their approach. The article is not, “This is what everyone should do”, but rather, “This is what <em>we</em> do for <em>this project</em> for <em>these reasons</em> and with <em>these trade-offs</em>, which fits our context. Perhaps you might be in a similar situation.”</p>
<blockquote>
<p>The htmx DX is very simple—your browser loads a single file, which in every environment is the exact same file you wrote. The tradeoffs required to maintain that experience are real, but they’re tradeoffs that make sense for this project.</p>
</blockquote>
<p>Granted, <a href="https://blog.jim-nielsen.com/2020/cheating-entropy-with-native-web-tech/">the power to cheat entropy</a> by writing code as it will be run comes with a cost — and that cost is often paid by <em>you</em> the programmer. Perhaps that is why we have so much “innovation” in the DX space: to make <em>our</em> lives easier (but not necessarily our websites better).</p>
<p>In fact, it almost makes you wonder if all the innovation in DX and web tech has a parallel to those startups which solve trivial problems that only address the superficial annoyances of our lives, like being able to order <em>any</em> food at <em>any</em> hour and have it delivered directly into your lap. Or <a href="https://www.theguardian.com/technology/2017/sep/01/juicero-silicon-valley-shutting-down">a machine to squeeze juice out of a bag</a>. Living without those is annoying to my laziness, but in lived experience it’s not that big of a deal.  </p>
<blockquote>
<p>every line of htmx has to be written in IE11-compatible JavaScript, which means no ES6...While this is incredibly annoying, in practice it is not a huge impediment.</p>
</blockquote>
<p>I love the pragmatism here. “While not being able to use <em>shiny new thing</em> is incredibly annoying, in practice it’s not that big of a deal.”</p>
<p>And I haven’t even got to the “no build step” part yet!</p>
<p>Ok so why is writing code <em>as it will be run</em> so amazing? Well, for one, vanilla JavaScript lasts forever:</p>
<blockquote>
<p>The best reason to write [anything] in plain JavaScript is that it lasts forever. This is arguably JavaScript’s single most underrated feature…JavaScript from 1999 that ran in Netscape Navigator will run unaltered, alongside modern code, in Google Chrome downloaded yesterday</p>
</blockquote>
<p>Contrast that with JavaScript (or CSS) that requires a compilation/transpilation step. Those often have the shelf-life of a dairy product.</p>
<blockquote>
<p>most people’s experience with JavaScript is that it ages like milk. Reopen a node repository after 3 months and you’ll find that your project is mired in a flurry of security warnings, backwards-incompatible library “upgrades,” and a frontend framework whose cultural peak was the exact moment you started the project and is now widely considered tech debt</p>
</blockquote>
<p>There’s a cost to writing code that must be transformed before it can be run, and (<a href="https://notes.jim-nielsen.com/#2023-09-18T2029">as Rich said</a>) I think people are in denial about the costs toolchains impose on them — or maybe even unaware of them.</p>
<blockquote>
<p>Maintenance is a cost paid for with labor, and open-source codebases are the projects that can least afford to pay it</p>
</blockquote>
<p>As the author notes, a big reason we’re (willingly or unknowingly) blind to the costs of our toolchains is because we hold this unspoken belief in technology that change, <em>any change</em>, constitutes progress.</p>
<blockquote>
<p>the tendency of software engineers to view progress as a teleology of capability rather than choices with tradeoffs sometimes blinds them </p>
</blockquote>
<p>But that blinds us to so many of our own ailments — “Doctor it hurts when I touch here / then don’t touch there!” For example, sometimes we are so willing to pickup and use the gateway building blocks to complexity in order to avoid the slightest bit of annoyance.</p>
<blockquote>
<p>Modules are really useful.</p>
<p>Sometimes, however, you want to solve simple problems, or at least relatively simple problems. In those cases, it can be helpful not to use the building blocks of more complex software, lest you emulate their complexity without creating commensurate value. </p>
</blockquote>
<p>Perhaps a little constraint could go a long way. As <a href="https://blog.jim-nielsen.com/2023/precarious-modern-computing/">Devine said</a>, “I don't think we've even begun to scratch the surface with what can be done with little.” Especially at the cost of a little annoyance to ourselves.</p>

    <hr>
    <ul>
      <li>
        Reply via:
        
        <a href="mailto:jimniels%2Bblog@gmail.com?subject=Re:%20blog.jim-nielsen.com/2023/the-cost-of-avoiding-annoyance/">Email</a>, <a href="https://twitter.com/jimniels">Twitter</a>,
        <a href="https://mastodon.social/@jimniels">Mastodon</a>
      </li>
      
      
    </ul>
    <br>
  ]]></description>
      <pubDate>Sun, 15 Oct 2023 19:00:00 +0000</pubDate>
      <link>https://blog.jim-nielsen.com/2023/the-cost-of-avoiding-annoyance/</link>
      <dc:creator>Jim Nielsen’s Blog</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/4247982703</guid>
    </item>
    <item>
      <title><![CDATA[How to Clear Cache and Cookies on a Customer’s Device]]></title>
      <description><![CDATA[There’s a super quick and easy way to clear cache on your customers’ devices. Are you using it yet?]]></description>
      <pubDate>Mon, 02 Oct 2023 13:30:10 +0000</pubDate>
      <link>https://csswizardry.com/2023/10/clear-cache-on-customer-device/</link>
      <dc:creator>CSS Wizardry</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/4233028753</guid>
    </item>
    <item>
      <title><![CDATA[CSS Solves Auto-Expanding Textareas (Probably, Eventually)]]></title>
      <description><![CDATA[
<p>I’ll cut to the chase before I type too many more words:</p>


<pre class="wp-block-code" data-shcb-language-name="CSS" data-shcb-language-slug="css"><span><code class="hljs language-css"><span class="hljs-selector-tag">textarea</span> {
  <span class="hljs-attribute">form-sizing</span>: normal;
}</code></span></pre>


<p>I came across it via <a href="https://www.amitmerchant.com/textarea-auto-increase-height/">an Amit Merchant blog post</a>. Then followed the thread a little. It all <a href="https://github.com/argyleink/open-props/issues/416">started with a discussion</a>, as these things tend to in this modern world. I think it’s a super weird name choice, but I’m sure if you were super involved in that discussion it would all make sense.</p>



<p>I was surprised to see it already in Chrome Canary with the “web experiments” flag enabled. Of course <a href="https://codepen.io/argyleink/pen/WNLzyJK">Adam is all over it</a>:</p>





<p>So useful, good job CSS. </p>



<p>Who knows when that will be usable across all browsers, or if it will definitely ship just like that. I tend to agree with Lea Verou earlier in the discussion that <code>height: max-content</code> feels like a very elegant choice, but I’m not privy to why they didn’t go that way. Also like Lea, if this opens the door for horizontally growing single-line inputs, that’s a win.</p>



<p>My favorite trick of doing this <em>before</em> was using CSS grid. You’d take the text inside the textarea and propagate it to a hidden psuedo element overlaid exactly on top. That stack technique is a classic:</p>


<pre class="wp-block-code" data-shcb-language-name="CSS" data-shcb-language-slug="css"><span><code class="hljs language-css"><span class="hljs-selector-class">.grid</span> {
  <span class="hljs-attribute">display</span>: grid;
  <span class="hljs-attribute">grid</span>: stack;
  &gt; *, &amp;::after {
    <span class="hljs-attribute">grid-area</span>: stack;
  }
}</code></span></pre>


<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_XWKEVLy" src="//codepen.io/anon/embed/XWKEVLy?height=450&amp;theme-id=1&amp;slug-hash=XWKEVLy&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen="" allowpaymentrequest="" name="CodePen Embed XWKEVLy" title="CodePen Embed XWKEVLy" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<p>Credit to Stephen Shaw on the original idea for that. </p>
]]></description>
      <pubDate>Fri, 29 Sep 2023 17:07:37 +0000</pubDate>
      <link>https://chriscoyier.net/2023/09/29/css-solves-auto-expanding-textareas-probably-eventually/</link>
      <dc:creator>Chris Coyier</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/4230341000</guid>
    </item>
    <item>
      <title><![CDATA[Robots.txt]]></title>
      <description><![CDATA[<p>A few weeks ago, I saw a flurry of conversation  about how you can now disallow OpenAI from indexing your personal website using <code>robots.txt</code>:</p>
<pre><code>User-agent: GPTBot
Disallow: /
</code></pre>
<p>That felt a bit “ex post facto“ as they say. Or, <a href="https://adactio.com/links/20380">as Jeremy put it</a>, “Now that the horse has bolted—and ransacked the web—you can shut the barn door.”</p>
<p>But folks seemed to be going ahead and doing it anyway and I thought to myself, “Yeah, I should probably do that too…” (especially given how <a href="https://chriscoyier.net/2023/04/21/the-secret-list-of-websites/">“fucking rude”</a> AI is in <a href="https://blog.jim-nielsen.com/2023/cite-your-sources-ai/">not citing its sources</a>).</p>
<p>But I never got around to it.</p>
<p>Tangentially, Manuel asked: what if you updated your <code>robots.txt</code> and blocked all bots? What would happen? Well, he did it and after a week he followed up. <a href="https://manuelmoreale.com/@/page/uwGISnOGX0zwjr7P">His conclusion</a>?</p>
<blockquote>
<p>the vast majority of automated tools out there just don't give a fuck about what you put in your robots.txt</p>
</blockquote>
<p>That’s when I realized why I hadn’t yet added any rules to my <code>robots.txt</code>: I have zero faith in it.</p>
<p>Perhaps that faith is not totally based in reality, but this is what I imagine a <code>robots.txt</code> file doing for my website:</p>
<p class="image-container"><img src="https://cdn.jim-nielsen.com/blog/2023/robots-txt-signs-rock.jpg" width="320" height="427" alt="Photograph of a “DO NOT ENTER” sign on a rock cliff and people have passed it and are standing out on the edge of the cliff.">

</p><p class="image-container"><img src="https://cdn.jim-nielsen.com/blog/2023/robots-txt-signs-beach.jpg" width="400" height="267" alt="Photograph at a beach with a sign that says “POISONOUS SPECIES DO NOT STEP INTO WATER” and people are all standing in the surf.">

</p><p class="image-container"><img src="https://cdn.jim-nielsen.com/blog/2023/robots-txt-signs-dog.jpg" width="500" height="346" alt="Photograph of a sign painted on the ground that says “NO DOGS ALLOWED” and there’s an adorable puppy sitting on the “NO” looking at the camera.">
</p>
    <hr>
    <ul>
      <li>
        Reply via:
        
        <a href="mailto:jimniels%2Bblog@gmail.com?subject=Re:%20blog.jim-nielsen.com/2023/robots-txt/">Email</a>, <a href="https://twitter.com/jimniels">Twitter</a>,
        <a href="https://mastodon.social/@jimniels">Mastodon</a>
      </li>
      
      
    </ul>
    <br>
  ]]></description>
      <pubDate>Wed, 27 Sep 2023 15:11:45 +0000</pubDate>
      <link>https://blog.jim-nielsen.com/2023/robots-txt/</link>
      <dc:creator>Jim Nielsen’s Blog</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/4227763139</guid>
    </item>
    <item>
      <title><![CDATA[Why are websites embarrassing?]]></title>
      <description><![CDATA[<p>A few years ago I walked into a bookstore and noticed something peculiar: I found that every book was okay. In fact, books had gotten pretty damn good! A random book in a random bookstore is likely to have an interesting cover with good typography inside. They’re not beautiful objects or anything and, sure, the paper is ehhhh and okay yes they’re using the same boring book fonts that you’ve seen ten million times but there’s nothing about these books that get in the way of my reading. So the baseline for modern book design is pretty high.</p>
<p>This made me wonder why the same isn’t true for websites.</p>
<p>The baseline for websites is not great. Okay, fine: most websites are pure, unadulterated, straight-up bad. Bad from top to bottom, bad from left to right. There’s no denying it, as much as I might want to. And I do want to! I want to hold up the field of web design and say that it’s en par with what musicians and painters and everyone else is doing in every other field. I’ve dedicated my life to this thing and so when I find a beautiful website, something that pushes the whole thing forwards, I want to loudly celebrate it.</p>
<p>I truly want every website to be worthy of our browsers.</p>
<p>But modern websites are not worthy. They’re slow, hard to navigate, and plagued with visual crap; pop-ups, bad typography, newsletter modals, and everything else imaginable. And that’s just the baseline. When I use a website on my phone I likely won’t trust it to show me the same information, I won’t trust interactions when I click buttons or fill in forms or even when I try to navigate elsewhere.</p>
<p>I don’t even trust the back button any more.</p>
<p>It’s so bad that visiting a website in 2023 is like falling into a blackhole and being hit by a bunch of random junk on your way to being crushed into an infinite nothing in the center. No, I don’t want to give you permission about cookies, no I don’t want to sign up to your newsletter, no I don’t want to talk with some half-baked chatbot.</p>
<p>No, no, no.</p>
<p>This low bar for web design is what makes me embarrassed to call myself a web designer. If you tell a stranger that you make websites for a living then their first thought is likely “<em>that sounds lame.</em>” Would they think the same of a book designer? Or a graphic designer? They might not be interested, they might yawn, but they wouldn’t be embarrassed for you.</p>
<p>And yet! I do truly believe that a website can be as well designed as any book, just as thoughtful, just as brilliant. Yet in 2023 it feels like we’ve let websites be one of two things: either confused, junky bloatware or simple white posters with black text and a big checkout button. But the web can be so much more!</p>
<p>When I find a website that doesn’t hijack the scroll, or a website with pleasantly sized text, or a website that loads in under 300ms then it makes me bolt upright in my chair. I wonder at what tech they’re using under the hood, what kind of conversations they had in those rooms, I try to imagine what kind of grueling process the team went through to make something so quiet and simple. All the things they had to say no to.</p>
<p>The other day Chris Coyier mentioned that <a href="https://chriscoyier.net/2023/08/01/other-peoples-busted-software-is-an-opportunity/">bad software is an opportunity</a>: if everyone else is making stuff that sucks and you don’t, you’ll have a leg up. And I get that, I think it’s true. If people look at your software as stable and reliable and trustworthy then you’ve already kinda won. But still, I can’t help feel that bad software is also an excuse. Everyone else’s websites don’t load in under 1 second, so why should mine? I don’t use my website on mobile, so who cares for those who do? Fixing this accessibility problem will only help one person, how does that make us money?</p>
<p>It matters because of the baseline! Every decision we make sets expectations of what is acceptable and what ain’t. Every time we say yes to shitty design and inaccessible text inputs we lower the floor for the whole profession, the whole art form of web design. Saying yes to one terrible new analytics script opens the door to ten thousand more.</p>
<p>So why is web design embarrassing? Why is the floor so low? And why do folks not see the web the same way I do, as a place to be treasured and cared for in the same way as a beautiful book?</p>
<p>I have two reasons beyond the obvious answers.</p>
<p>First, I think great web design requires discipline. And that’s really the hardest thing about making a website. In this field there are a thousand yes’s for every no because, and here’s the second reason: it’s impossible to hold a website in your hand. It’s hard and sometimes tedious to see all the variations of your work across the infinitude of devices and circumstances that might touch your site. When making a website it sometimes feel like you’re thinking in four dimensions instead of three.</p>
<p>But really the baseline of web design is so low because there’s a lack of tenderness, care, and empathy. It’s because we don’t see the making of a website as a worthy profession. It’s because we hope to squeeze the last bit of juice from the orange by mulching people in between modals and pop ups and cookie banners.</p>
<p>So how do we do better? How do we take better care of our websites?</p>
]]></description>
      <pubDate>Tue, 29 Aug 2023 17:07:16 +0000</pubDate>
      <link>https://robinrendle.com/notes/why-are-websites-embarrassing/</link>
      <dc:creator>Starred Articles</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/4200293988</guid>
    </item>
    <item>
      <title><![CDATA[The virtual keyboard API]]></title>
      <description><![CDATA[]]></description>
      <pubDate>Mon, 31 Jul 2023 00:00:00 +0000</pubDate>
      <link>http://ishadeed.com/article/virtual-keyboard-api/</link>
      <dc:creator>Ahmad Shadeed</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/4174140951</guid>
    </item>
    <item>
      <title><![CDATA[Perceived affordances and the functionality mismatch - Tink - Léonie Watson]]></title>
      <description><![CDATA[<div class="[ post__body ] [ flow ] [ flow-space-700 ]"><p>Using one element or set of elements (usually because of their functionality) and styling them to look like something else is a common pattern. A recent <a href="https://twitter.com/LeaVerou/status/1545712667515654144">conversation on Twitter</a> about using radio buttons styled to look like buttons highlighted the essential problem with this approach - it creates a mismatch between the actions people expect they can take and the ones they actually can.</p><h2>Affordance</h2><p>The concept of "affordance" was first described by JJ Gibson in the late 1970s and Don Norman applied it to design in the late 1980s. Norman later used the phrase <a href="https://jnd.org/affordances_and_design/">"perceived affordance"</a> to clarify the use of affordance in interface design. Perceived affordance, as Norman puts it, is "whether the user perceives that some action is possible (or in the case of perceived non-affordances, not possible)".</p><p>For example, a button is intended to be pressed with the expectation that something will happen when it is. When a button is not pressed it stands proud of the surrounding surface, and when it is pressed it's flat. This means you can tell that it's a button and what state it's in by touching it or by looking at it. In other words, a button has both physical and perceived affordances. The knowledge of what to do with it comes from your prior experience of using buttons that conform to the same convention.</p><p>The concepts of affordance and convention were so familiar to most people, they were transferred from the physical world to digital. Radio buttons, for example, are so called because of car radios from the days before digital broadcasting.</p><p>Finding a radio station used to involve twiddling a knob to slowly move through the different radio bands until you found a station you wanted to listen to. Doing this whilst driving and, presumably, wanting to keep your eyes on the road, was not altogether conducive to an accident free life, so radio buttons were invented. Car owners could pre-tune their radio to a few favourite stations and assign each station to a button on the front of the radio. Since listening to one station at a time is enough for most people, the radio buttons were designed so that only one could be pressed at a time.</p><h2>Perception</h2><p>The digital world still hasn't really figured out physical affordance, at least not to the extent it's useful across websites and applications. This means we depend on perceived rather than physical affordance.</p><p>Buttons and radio buttons are not the only User Interface (UI) components to make the transition from the physical to digital world (think restaurant menus or the tabs in filing cabinets and telephone organisers), but let's stick with them for now.</p><p>Even though digital radio buttons look nothing like their physical counterparts used to, a common visual convention for digital buttons and radio buttons has emerged over the past three decades or so. If you're sighted and you've used any form of Graphical User Interface (GUI) in that time, you'll probably be able to recognise a button or set of radio buttons by their visual appearance. if you can't see and use a screen reader instead, then you'll recognise them based on their <a href="https://tink.uk/understanding-semantics/">semantics</a> and the way your screen reader announces that information.</p><h2>Mismatch</h2><p>Which brings us to the mismatch problem.</p><p>Using a set of radio buttons for a component that lets people select one thing at a time makes sense - it's what they're for after all. It's when the radio buttons don't look like radio buttons that the mismatch happens.</p><p>If you're a sighted mouse user, you'll see a set of buttons and your prior experience will tell you that you can point at the one you want and click on it with the expectation that something will happen when you do - and indeed it does.</p><p>If you're a sighted keyboard user, you'll also see a set of buttons, and your prior experience will tell you that you can repeatedly use the <kbd>Tab</kbd> key to navigate to the button you want, then the <kbd>Enter</kbd> or <kbd>Space</kbd> keys to activate it - except you can't because it isn't a set of buttons, it's a set of radio buttons, and the keyboard interaction is not the same.</p><p>You can use the <kbd>Tab</kbd> key to move to the first radio button in the set, but if you press it again, you'll navigate to the next focusable element <strong>after</strong> the radio buttons, not to the next radio button in the set. In other words, unless you happen to guess you're really using a set of radio buttons and you realise you should be using the arrow keys not the <kbd>Tab</kbd> key to navigate between them, you're likely to think the component is broken because you can't reach any other buttons in the set.</p><p><strong>Note:</strong> Firefox is the exception because it does allow you to use the <kbd>Tab</kbd> key to navigate between all radio buttons in a set.</p><p>If you're a <a href="https://webaim.org/projects/screenreadersurvey9/#disabilitytypes">sighted screen reader user</a>, you too will see the set of buttons, and your prior experience will tell you that you can use your screen reader's shortcut key for navigating to and between buttons to reach them - except you can't because it isn't a set of buttons, it's a set of radio buttons, and the screen reader shortcut is not the same.</p><p>One of the <a href="https://inclusivedesignprinciples.org">Inclusive Design Principles</a> is:</p><blockquote><p>Be consistent: Use familiar conventions and apply them consistently.</p></blockquote><p>It goes on to explain:</p><blockquote><p>Familiar interfaces borrow from well-established patterns. These should be used consistently within the interface to reinforce their meaning and purpose. This should be applied to functionality, behaviour, editorial, and presentation. You should say the same things in the same way and users should be able to do the same things in the same way.</p></blockquote><p>Good design means following that principle and making sure that the functionality of your component matches the perceived affordances of your visual design, or that your visual design matches the functionality of your component.</p></div>]]></description>
      <pubDate>Sat, 29 Apr 2023 08:02:39 +0000</pubDate>
      <link>https://tink.uk/perceived-affordances-and-the-functionality-mismatch/</link>
      <dc:creator>Pages</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/4086673835</guid>
    </item>
    <item>
      <title><![CDATA[sizes=auto is a great idea]]></title>
      <description><![CDATA[
<p>The attributes of responsive <code>&lt;img&gt;</code>s are pretty intense!</p>


<pre class="wp-block-code" data-shcb-language-name="HTML, XML" data-shcb-language-slug="xml"><span><code class="hljs language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">img</span> 
  <span class="hljs-attr">alt</span>=<span class="hljs-string">"A baby smiling with a yellow headband."</span>
  <span class="hljs-attr">srcset</span>=<span class="hljs-string">"
    baby-s.jpg  300w,
    baby-m.jpg  600w,
    baby-l.jpg  1200w,
    baby-xl.jpg 2000w
  "</span>
  <span class="hljs-attr">sizes</span>=<span class="hljs-string">"
    (min-width: 2420px) 2000px, 
    (min-width: 720px) calc(94.76vw - 274px), 
    (min-width: 520px) calc(100vw - 96px), 
    calc(100vw - 32px)
  "</span>
&gt;</span></code></span></pre>


<p>You need to create/serve/cache multiple versions of images, know exactly how wide they are, and then accurately put that into the <code>srcset</code> attribute in the HTML syntax. Hopefully, you don’t need to change that system very often if ever. </p>



<p>I find <code>sizes</code> even more difficult, as it needs to accurately describe how wide the image will render at different potential breakpoints in the CSS. Even if you’re intimately familiar with your CSS, it’s mind-bending to figure out. A lot of sites just kinda punt on it with something very vague like <code>sizes="(max-width: 340px) 100vw, 340px"</code>, which assumes you’ll be rendering the image at full width if the browser window is smaller than the image; otherwise at the full width of the browser window (rarely true). Plus, CSS changes! </p>



<p>Not trying to crap on the responsive images syntax; it’s just hard. Hard to get right off the bat, hard to automate, and hard to keep right over time. </p>



<p>The irony of the <code>sizes</code> attribute, in particular, is… doesn’t the browser already know how big an image will render at? Well, it does, but only after layout happens. The point of the HTML attribute is that if the information it has is correct, it can make decisions about what image to download <em>before</em> that and be efficient about it (the whole point of responsive images). </p>



<p>But what if the browser <em>doesn’t need to download the image right away, anyway?</em> That’s what happens with <code>&lt;img loading="lazy" ... /&gt;</code>, which is certainly a best practice for doing images efficiently. Well if the browser isn’t bothering to download an image until later, then the layout rendering step has already been done, and the browser <em>really does know</em> how big the image is on the page. Thus… <code>sizes</code> doesn’t really matter! In that case, wouldn’t this be nice?</p>


<pre class="wp-block-code" data-shcb-language-name="HTML, XML" data-shcb-language-slug="xml"><span><code class="hljs language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">img</span> 
  <span class="hljs-attr">alt</span>=<span class="hljs-string">"A baby smiling with a yellow headband."</span>
  <span class="hljs-attr">srcset</span>=<span class="hljs-string">"
    baby-s.jpg  300w,
    baby-m.jpg  600w,
    baby-l.jpg  1200w,
    baby-xl.jpg 2000w
  "</span>
  <span class="hljs-attr">loading</span>=<span class="hljs-string">"lazy"</span>
  <span class="hljs-attr">sizes</span>=<span class="hljs-string">"auto"</span>
&gt;</span></code></span></pre>


<p><a href="https://github.com/whatwg/html/pull/8008">Looks like that’s happening</a> and that’s great. I wish I could tell you what browsers it’s landing in and when, but I have no idea. I just like that it has legs. </p>
]]></description>
      <pubDate>Fri, 23 Jun 2023 16:38:19 +0000</pubDate>
      <link>https://chriscoyier.net/2023/06/23/sizesauto-is-a-great-idea/</link>
      <dc:creator>Chris Coyier</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/4139563978</guid>
    </item>
    <item>
      <title><![CDATA["(Ab)use the Platform!" by Jon Jensen at #RemixConf 2023 💿]]></title>
      <description><![CDATA[As any Remixologist will tell you, modern web apps are just pages/links/forms layered in spinners/latency/jank. If that's true, we should use Remix and web fundamentals everywhere to build better experiences! But what about highly interactive apps with streaming data and visualizations? Just how far should we push the envelope?

Let's find out! In this talk, we'll implement a multi-user musical instrument with Remix, (ab)using things like:

- Forms and FormData
- Web Audio
- Browser history
- Progressive enhancement
- Full stack components
- Response and Server-sent events

Speaker Bio: Jon Jensen is a Senior Software Engineer at Netflix working to improve the developer experience for other UI engineers. Previously he worked at Amazon and Instructure, working on all things software-y. In his spare time he's usually in the mountains, playing Minecraft with his kids, or following the Red Food Truck around Salt Lake City. https://remix.run/conf/2023/speakers/jon-jensen]]></description>
      <pubDate>Mon, 15 May 2023 19:14:35 +0000</pubDate>
      <link>https://www.youtube.com/watch?v=lbzNnN0F67Y</link>
      <dc:creator>Remix</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/4101982795</guid>
    </item>
    <item>
      <title><![CDATA[Getting started with View Transitions on multi-page apps]]></title>
      <description><![CDATA[<p>Spurred by <a href="https://shoptalkshow.com/565/">last week’s ShopTalk</a> I rolled out View Transitions here on my static Jekyll site. I hadn’t realized View Transitions for multi-page apps (MPAs) and static sites are ready for testing behind a flag in Chrome 113+. View Transitions for MPAs are a feature that’s high on my CSS wishlist, so I got to it. It took less than an hour to do, requires zero JavaScript, and two lines of CSS. I’m pleased with the results.</p>
<p><video autoplay="" loop="" muted="" src="https://cdn.daverupert.com/posts/2023/view-transitions.mp4"></video></p>
<p>⚠️&nbsp;At the time of writing this only work well in Chrome Canary 115+. I cannot fix this for you in other browsers. For browsers that don’t have multi-page View Transitions turned on by default (<em>literally all of them</em>) your pages will load like normal pages. This technique is a ✨&nbsp;wonderful progressive enhancement.</p>
<p>Let’s get started adding View Transitions to our site…</p>
<h2>1. Enable flags in Chrome Canary</h2>
<p>To get started with View Transitions, enable them in Chrome Canary.</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="html">chrome://flags#view-transition
chrome://flags#view-transition-on-navigation
</code></pre></div></div>
<p>⚠️&nbsp;These flags also exist in Chrome Stable and Arc (but not Edge?) but the page will randomly lock up, so stick with Canary or until Chrome Stable hits v115 around Jul 12, 2023. It’s not announced when/if they will unflag this in future releases.</p>
<p>Now we get to the fun part…</p>
<h2>2. Include the view-transition <code>meta</code> tag</h2>
<p>Add the following to the global <code>&lt;head&gt;</code> of your layout template.</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="html"><span class="nt">&lt;meta</span> <span class="na">name=</span><span class="s">"view-transition"</span> <span class="na">content=</span><span class="s">"same-origin"</span> <span class="nt">/&gt;</span>
</code></pre></div></div>
<p>Congrats, you now have page transitions! This one-liner applies a crossfade effect across your entire site. Your website is now a PowerPoint. It’s unclear to me if the <code>&lt;meta&gt;</code> tag will be the final solution to enable this functionality, but I’m happy to see it works.</p>
<h2>3. Add individual element transitions</h2>
<p>You’re probably asking yourself two questions:</p>
<ul>
<li>How do I create an effect more exciting than a crossfade?</li>
<li>How do I transition specific elements?</li>
</ul>
<p>The answer to that is: named view transitions in CSS. Give the element you want to transition from (on <code>page1.html</code>) and element you want to transition to (on <code>page2.html</code>) the same unique <code>view-transition-name</code>. Let’s use a Bootstrap-style “<a href="https://getbootstrap.com/docs/4.0/components/jumbotron/">Jumbotron</a>” as an example.</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="html"><span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"jumbotron"</span> <span class="na">style=</span><span class="s">"view-transition-name: hero"</span><span class="nt">&gt;</span>
  <span class="c">&lt;!-- content goes here --&gt;</span>
	<span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">"/product.html"</span><span class="nt">&gt;</span>Buy Now<span class="nt">&lt;/a&gt;</span>
<span class="nt">&lt;/div&gt;</span>
</code></pre></div></div>
<p>On <code>product.html</code> find the target element you want your element to morph into and give it the same <code>view-transition-name</code></p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="html"><span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"product-header"</span> <span class="na">style=</span><span class="s">"view-transition-name: hero"</span><span class="nt">&gt;</span>
  <span class="c">&lt;!-- content goes here --&gt;</span>
<span class="nt">&lt;/div&gt;</span>
</code></pre></div></div>
<p>Voilà! This gives you a “morph” effect between the two pages. If you hate the inline styles, this can be done in a CSS file as well.</p>
<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="css"><span class="nc">.jumbotron</span> <span class="p">{</span> <span class="py">view-transition-name</span><span class="p">:</span> <span class="n">hero</span> <span class="p">}</span>
<span class="nc">.product-header</span> <span class="p">{</span> <span class="py">view-transition-name</span><span class="p">:</span> <span class="n">hero</span> <span class="p">}</span>
</code></pre></div></div>
<p>And if you want to be a real champ, disable the morph effect for <a href="https://www.a11yproject.com/posts/understanding-vestibular-disorders/">people who get motion sickness</a>. 🏆</p>
<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="css"><span class="k">@media</span> <span class="n">not</span> <span class="p">(</span><span class="n">prefers-reduced-motion</span><span class="p">:</span> <span class="n">reduce</span><span class="p">)</span> <span class="p">{</span>
	<span class="nc">.jumbotron</span> <span class="p">{</span> <span class="py">view-transition-name</span><span class="p">:</span> <span class="n">hero</span> <span class="p">}</span>
	<span class="nc">.product-header</span> <span class="p">{</span> <span class="py">view-transition-name</span><span class="p">:</span> <span class="n">hero</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Before jumping into making our own custom transitions, let’s pause for a minute and look at how this all works and learn about any limitations in the underlying technology…</p>
<h3>How does this all work?</h3>
<p>Behind the scenes the browser is rasterizing (read: <em>making and image of</em>) the before and after states of the DOM elements you’re transitioning. The browser figures out the differences between those two snapshots and tweens between them similar to Apple Keynote’s “Magic Morph” feature, the liquid metal T-1000 from <em>Terminator 2: Judgement Day</em>, or the 1980s cartoon series <em>Turbo Teen</em>.</p>
<p><img src="https://cdn.daverupert.com/posts/2023/turboteen.gif" alt="Title series for the 80s animated cartoon series Turbo Teen featuring a boy turning into a hot rod car"></p>
<p>If those references are lost on you, how about the popular kids book series <em>Animorphs</em>?</p>
<p><img src="https://cdn.daverupert.com/posts/2023/animorphs.jpg" alt="animorphs book cover of a boy slowly fading into a tiger"></p>
<p>This is <em>exactly how it works</em> and no I won’t be taking any further questions. Anecdotally, it appears to negotiate the X,Y position and the height/width of the before and after states. It will then hot swap your DOM for rasterized images (like transitions), resize transform the height/width, translate the position, and crossfade animation between the old and new states. Basic but convincing.</p>
<h3>Unique limitations for lists of many items</h3>
<p><strong>Right now MPA view-transitions need to be unique!</strong> If you have two elements on the same page with the same transition name (e.g. <code>view-transition-name: post</code>) the browser gets confused and will bail on the view transition, falling back to cross-fade.</p>
<p>Thankfully there’s a workaround if you’re trying to animate from a list-view to a details-view and back by adding a unique <code>view-transition-name</code> in your template. This example is a list of posts:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="html"><span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"posts"</span><span class="nt">&gt;</span>
{% foreach post in posts %}
  <span class="nt">&lt;a</span> <span class="na">style=</span><span class="s">"view-transition-name: post-{{ post.id }}"</span> <span class="na">href=</span><span class="s">"{{ post.url }}"</span><span class="nt">&gt;</span>{{ post.title }}<span class="nt">&lt;/a&gt;</span>
{% endforeach %}
<span class="nt">&lt;/div&gt;</span>
</code></pre></div></div>
<p>This view transition will have the name of <code>post-123</code> in your rendered HTML. You then add the same “slug” to your post template to morph the link in to the article header.</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="html"><span class="nt">&lt;article&gt;</span>
  <span class="nt">&lt;header</span> <span class="na">style=</span><span class="s">"view-transition-name: post-{{ post.id }}"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;h1&gt;</span>{{ post.title }}<span class="nt">&lt;/h1&gt;</span>
  <span class="nt">&lt;/header&gt;</span>
  ...
<span class="nt">&lt;/article&gt;</span>
</code></pre></div></div>
<p>Now you’ve hacked the system and are able to transition as you’d expect. One side effect to be aware of is that this creates a lot of pseudo-elements in your Web Inspector…</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="html"><span class="cp">&lt;!DOCTYPE html&gt;</span>
<span class="nt">&lt;html</span> <span class="na">lang=</span><span class="s">"en-us"</span><span class="nt">&gt;</span>
  ::view-transition-group(root)
  ::view-transition-group(post-123)
  ::view-transition-group(post-124)
  ... etc
	::view-transition-image-pair(root)
	::view-transition-image-pair(post-123)
	::view-transition-image-pair(post-124)
	... etc
	::view-transition-old(root)
	::view-transition-old(post-123)
	::view-transition-old(post-124)
  ... etc
	::view-transition-new(root)
	::view-transition-new(post-123)
	::view-transition-new(post-124)
	... etc
</code></pre></div></div>
<p>I imagine this is fairly moot, but could also be similar to a “too many DOM nodes” performance and debugging issue. You do what you want, but I’m probably going to be conservative about this and avoid applying it on gigantic lists. I’m hoping a better, less manual/explicit API for list-to-detail transitioning comes along, but for now this works.</p>
<h3>4. Customize your view transitions</h3>
<p>What if the “morph” transition isn’t what you want? You can control your named view transition’s old (outgoing) state and the new (incoming) state using the <code>::view-transition-old()</code> and <code>::view-transition-new()</code> pseudo elements.</p>
<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="css"><span class="c">/* Old stuff going out */</span>
<span class="nd">::view-transition-old</span><span class="o">(</span><span class="nt">hero</span><span class="o">)</span> <span class="p">{</span>
  <span class="nl">animation</span><span class="p">:</span> <span class="n">fade</span> <span class="m">0.2s</span> <span class="n">linear</span> <span class="n">forwards</span><span class="p">;</span>
<span class="p">}</span>

<span class="c">/* New stuff coming in */</span>
<span class="nd">::view-transition-new</span><span class="o">(</span><span class="nt">hero</span><span class="o">)</span> <span class="p">{</span>
  <span class="nl">animation</span><span class="p">:</span> <span class="n">fade</span> <span class="m">0.3s</span> <span class="n">linear</span> <span class="n">reverse</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">@keyframes</span> <span class="n">fade</span> <span class="p">{</span>
  <span class="nt">from</span> <span class="p">{</span>
    <span class="nl">opacity</span><span class="p">:</span> <span class="m">1</span><span class="p">;</span>
  <span class="p">}</span>
  <span class="nt">to</span> <span class="p">{</span>
    <span class="nl">opacity</span><span class="p">:</span> <span class="m">0</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>It’s a little duplicative, but nice to have control over the incoming and outgoing content. Small subtle tweaks like making the old content fade out a little faster than the new content comes in can make your app feel uniquely yours.</p>
<p>I mostly use the default morph transition, but ran into a problem on <a href="https://daverupert.com/bookshelf/">my About section subnav</a> where I didn’t like how the morph transition was scaling my text due to a difference in content container sizes between pages. Adding <code>width: fit-content</code> to the transition worked wonders and I got a quick horizontal resize effect I’ve wanted for a long time.</p>
<p>Sky’s the limit on how far you want to go with this. Get ready for egregious animation wipes coming to a website near you.</p>
<h2>Have fun, progressively</h2>
<p>I think the most telling predictor for the success of the multi-page View Transitions API – compared to all other proposals and solutions that have come before it – is that I actually implemented this one. Despite animations being my bread and butter for many years, I couldn’t be arsed to even try any of the previous generation of tools.</p>
<p>The ability to easily – with a <code>&lt;meta&gt;</code> tag and a handful of CSS properties –  create quality 60 FPS page transitions is a game changer and I look forward to what more creative people will do with this technology. My head is already spinning with ideas. The best part about MPA View Transitions, is that under the hood it’s just normal “dumb” page loads and we’re progressively enhancing our way into smart, sleek transitions all with zero JavaScript required.</p>
<p>I sure hope other browsers hop on board. 👀</p>
]]></description>
      <pubDate>Thu, 18 May 2023 23:03:00 +0000</pubDate>
      <link>https://daverupert.com/2023/05/getting-started-view-transitions/</link>
      <dc:creator>daverupert.com</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/4105452451</guid>
    </item>
    <item>
      <title><![CDATA[Tech CEOs Rank Web Browsers (AI Tier List)]]></title>
      <description><![CDATA[Which web browser is the best? Mark Zuckerberg, Bill Gates, and Elon Musk (or at least the AI versions of them) put Chrome, Safari, Firefox, Brave, Arc, and Edge head to head and try to get to the bottom of it. 

Try Arc from The Browser Company 👉 https://arc.net // "the Chrome replacement I've been waiting for" - The Verge

AI Presidents tier lists are out, AI CEO tier list videos are in.]]></description>
      <pubDate>Sun, 14 May 2023 16:55:59 +0000</pubDate>
      <link>https://www.youtube.com/watch?v=NhHi0OjHUMQ</link>
      <dc:creator>The Browser Company</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/4100955211</guid>
    </item>
    <item>
      <title><![CDATA[Sticky Page Header Shadow on Scroll]]></title>
      <description><![CDATA[<main id="main" class="content wrapper flow"> <p>We've seen it plenty of times around the web where a website's page header follows us as we scroll down the page. CSS makes doing this a breeze with <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/position#sticky_positioning">sticky positioning</a>:</p>
<pre class="language-css"><code class="language-css"><p class="token">.page-header</p> <p class="token">{</p><br> <p class="token">position</p><p class="token">:</p> sticky<p class="token">;</p><br> <p class="token">top</p><p class="token">:</p> 0<p class="token">;</p><br><p class="token">}</p></code></pre>
<p>What if we desired something a little bit extra, like applying a <code>box-shadow</code> to the sticky header as soon as the page is scrolled? I thought it was worth sharing one solution that has worked well for me to accomplish this goal. Check out the following CodePen demo. As soon as the page is scrolled, a shadow fades in below the header.</p>
<p class="codepen"> <span><a href="https://codepen.io/hexagoncircle/pen/qBMeWqo">See the pen</a> (<a href="https://codepen.io/hexagoncircle">@hexagoncircle</a>) on <a href="https://codepen.io">CodePen</a>.</span>
</p> <p>An element that I've decidedly dubbed an "intercept"—naming is hard and this felt right in the moment—is created and inserted above the page header at the top of the page. If we open the browser dev tools and inspect the <abbr>DOM</abbr>, we'll find:</p>
<pre class="language-html"><code class="language-html"> <p class="token"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">data-observer-intercept</span><span class="token punctuation">&gt;</span></p><p class="token"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></p><br> <p class="token"><span class="token tag"><span class="token punctuation">&lt;</span>header</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>page-header<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></p> //...<p> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>header</span><span class="token punctuation">&gt;</span></span></p></code></pre>
<p>The <a href="https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API">Intersection Observer API</a> is being used to observe when the intercept is no longer appearing in the visible viewport area which happens as soon as the page scrolls. So when the intercept is <em>not</em> intersecting, a class is applied to the header element.</p>
<pre class="language-js"><code class="language-js"><p class="token">const</p> observer <p class="token">=</p> <p class="token">new</p> <p class="token">IntersectionObserver</p><p class="token">(</p><p class="token">(</p><p class="token"><span class="token punctuation">[</span>entry<span class="token punctuation">]</span></p><p class="token">)</p> <p class="token">=&gt;</p> <p class="token">{</p><br> header<p class="token">.</p>classList<p class="token">.</p><p class="token">toggle</p><p class="token">(</p><p class="token">"active"</p><p class="token">,</p> <p class="token">!</p>entry<p class="token">.</p>isIntersecting<p class="token">)</p><p class="token">;</p><br><p class="token">}</p><p class="token">)</p><p class="token">;</p><p>observer<span class="token punctuation">.</span><span class="token function">observe</span><span class="token punctuation">(</span>intercept<span class="token punctuation">)</span><span class="token punctuation">;</span></p></code></pre>
<p>Inspecting the DOM again, we'll catch the <code>active</code> class name on the page header element toggling on and off as we scroll down and back up.</p>
<h2>Delay that shadow</h2>
<p>It's also possible to wait on when the shadow should appear by offsetting the intercept element. Try editing the above demo on CodePen. In the CSS panel add the following ruleset:</p>
<pre class="language-css"><code class="language-css"><p class="token">[data-observer-intercept]</p> <p class="token">{</p><br> <p class="token">position</p><p class="token">:</p> absolute<p class="token">;</p><br> <p class="token">top</p><p class="token">:</p> 300px<p class="token">;</p><br><p class="token">}</p></code></pre>
<p>This will push the intercept down from the top of the page by 300 pixels. When scrolling the page again, notice that the shadow doesn't appear right away, waiting until the page has been scrolled passed the offset value.</p>
<p>Have questions? Other ways to handle this? I'd love to hear about it! Reach out to me on <a href="https://fosstodon.org/@hexagoncircle">Mastodon</a> with your ideas.</p> </main>]]></description>
      <pubDate>Wed, 05 Apr 2023 17:24:49 +0000</pubDate>
      <link>https://ryanmulligan.dev/blog/sticky-header-scroll-shadow/</link>
      <dc:creator>Pages</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/4064237538</guid>
    </item>
    <item>
      <title><![CDATA[Types in JavaScript With Zod and JSDoc]]></title>
      <description><![CDATA[<p>There are cases where I like types in JavaScript. And I don’t mind Typescript, especially for bigger projects — as long as somebody more knowledgable than me sets it up and maintains it.</p>
<p>When I want type hints in VSCode for smaller, personal projects, I use <a href="https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html">JSDoc</a> which lets you declare types inside plain, old JavaScript comments — you can even share them across files!</p>
<pre><code class="language language-js"><span class="hljs-comment">// file: file-1.js</span>
<span class="hljs-comment">/**
 * <span class="hljs-doctag">@typedef</span> <span class="hljs-variable">Candy</span>
 * <span class="hljs-doctag">@property</span> {<span class="hljs-type">string</span>} <span class="hljs-variable">name</span>
 * <span class="hljs-doctag">@property</span> {<span class="hljs-type">"chocolate"|"gummy"</span>} <span class="hljs-variable">type</span>
 */</span>
 
<span class="hljs-comment">// file: file-2.js</span>
<span class="hljs-comment">/** <span class="hljs-doctag">@type</span> {<span class="hljs-type"> import('./file-1').Candy[] </span>} */</span>
<span class="hljs-keyword">const</span> candy = [
  { <span class="hljs-attr">name</span>: <span class="hljs-string">"Kit Kat"</span>, <span class="hljs-attr">type</span>: <span class="hljs-string">"chocolate"</span> },
  { <span class="hljs-attr">name</span>: <span class="hljs-string">"Peach Rings"</span>, <span class="hljs-attr">type</span>: <span class="hljs-string">"gummy"</span> },
];
</code></pre>
<p>If you’re using VSCode, that means no compiling/transpiling/configuration necessary. Type hints work out of the box.</p>
<p class="image-container"><img src="https://cdn.jim-nielsen.com/blog/2023/zod-jsdoc-1.png" width="690" height="193" alt="Screenshot of code in VSCode with type hints on a JSDoc comment.">

</p><p>In addition, sticking <code>@ts-check</code> at the top of the file will give you type checking on code in the file, red squiggles and all.</p>
<p class="image-container"><img src="https://cdn.jim-nielsen.com/blog/2023/zod-jsdoc-2.png" width="829" height="360" alt="Screenshot of code in VSCode with type error checking enabled via `@ts-check`.">

</p><p>This is great because it gives me type hints and checks as I code. But sometimes I also want the benefits of my types <em>at runtime</em>. JSDoc won’t give me that. Not even Typescript will give me that, as it only works up to compile time.</p>
<p>For runtime, you need something like <a href="https://zod.dev/">Zod</a>, which lets you write your type schemas, then infer them as Typescript types automatically.</p>
<pre><code class="language language-ts"><span class="hljs-comment">// Declare your schema with zod</span>
<span class="hljs-keyword">const</span> <span class="hljs-title class_">CandySchema</span> = z.<span class="hljs-title function_">object</span>({
  <span class="hljs-attr">name</span>: z.<span class="hljs-title function_">string</span>(),
  <span class="hljs-attr">type</span>: z.<span class="hljs-title function_">enum</span>([<span class="hljs-string">"chocolate"</span>, <span class="hljs-string">"gummy"</span>]),
});

<span class="hljs-comment">// Extract the type for typescript</span>
<span class="hljs-keyword">type</span> <span class="hljs-title class_">Candy</span> = z.<span class="hljs-property">infer</span>&lt;<span class="hljs-keyword">typeof</span> <span class="hljs-title class_">CandySchema</span>&gt;;

<span class="hljs-comment">// Instead of re-writing it like this</span>
<span class="hljs-keyword">type</span> <span class="hljs-title class_">Candy</span> = {
  <span class="hljs-attr">name</span>: <span class="hljs-built_in">string</span>;
  <span class="hljs-attr">type</span>: <span class="hljs-string">"chocolate"</span> | <span class="hljs-string">"gummmy"</span>;
};
</code></pre>
<p>But what if you don’t want to write <code>.ts</code> files? What if you’re writing regular old <code>.js</code> files with JSDoc comments? Can you infer your Zod types in JSDoc?</p>
<p>It appears<sup id="fnref:1"><a href="https://blog.jim-nielsen.com/2023/types-in-jsdoc-with-zod/#fn:1">[1]</a></sup> you can!</p>
<pre><code class="language language-js"><span class="hljs-comment">// Declare your schema with zod</span>
<span class="hljs-keyword">const</span> <span class="hljs-title class_">CandySchema</span> = z.<span class="hljs-title function_">object</span>({
  <span class="hljs-attr">name</span>: z.<span class="hljs-title function_">string</span>(),
  <span class="hljs-attr">type</span>: z.<span class="hljs-title function_">enum</span>([<span class="hljs-string">"chocolate"</span>, <span class="hljs-string">"gummy"</span>]),
});

<span class="hljs-comment">// Extract the inferred type as a JSDoc type</span>
<span class="hljs-comment">/** <span class="hljs-doctag">@typedef</span> {<span class="hljs-type"> z.infer&lt;typeof CandySchema&gt; Candy </span>} */</span>

<span class="hljs-comment">// Instead of re-writing it as a JSDoc type like this</span>
<span class="hljs-comment">/**
 * <span class="hljs-doctag">@typedef</span> <span class="hljs-variable">Candy</span>
 * <span class="hljs-doctag">@property</span> {<span class="hljs-type">string</span>} <span class="hljs-variable">name</span>
 * <span class="hljs-doctag">@property</span> {<span class="hljs-type">"chocolate"|"gummy"</span>} <span class="hljs-variable">type</span>
 */</span>
</code></pre>
<p>With this approach, you “declare your types” with Zod in regular JavaScript files and then you can 1) use them at runtime and 2) extract them as JSDoc types and use them as hints in your editor.</p>
<pre><code class="language language-js"><span class="hljs-comment">// file: file-1.js</span>
<span class="hljs-keyword">const</span> <span class="hljs-title class_">CandySchema</span> = z.<span class="hljs-title function_">object</span>({
  <span class="hljs-attr">name</span>: z.<span class="hljs-title function_">string</span>(),
  <span class="hljs-attr">type</span>: z.<span class="hljs-title function_">enum</span>([<span class="hljs-string">"chocolate"</span>, <span class="hljs-string">"gummy"</span>]),
});
<span class="hljs-comment">/** <span class="hljs-doctag">@typedef</span> {<span class="hljs-type"> z.infer&lt;typeof CandySchema&gt; Candy </span>} */</span>

<span class="hljs-comment">// file: file-2.js</span>
<span class="hljs-comment">/** <span class="hljs-doctag">@type</span> {<span class="hljs-type"> import('./file-1').Candy[] </span>} */</span>
<span class="hljs-keyword">const</span> candy = [
  { <span class="hljs-attr">name</span>: <span class="hljs-string">"Kit Kat"</span>, <span class="hljs-attr">type</span>: <span class="hljs-string">"chocolate"</span> },
  { <span class="hljs-attr">name</span>: <span class="hljs-string">"Peach Rings"</span>, <span class="hljs-attr">type</span>: <span class="hljs-string">"gummy"</span> },
];
</code></pre>
<p>A <code>@ts-check</code> at the top of the file gives you red squiggles type checking in the file too — no <code>.ts</code> files required!</p>
<p>Again, the beauty of this approach is that <em>it’s all JavaScript</em>. You don’t have to compile/transpile/configure anything. The code you write is 100% valid JavaScript you can dump straight into the browser, but it also provides you a nice DX in VSCode as you program.</p>
<p>In my particular case, I use this on a SSG project where I want to 1) ensure the data flowing through my site generator is correct by using runtime (i.e. <em>build time</em>) type checking, and 2) get nice hints directly in my editor as to the shape of my data as well as in-file squiggles for any errors.</p>
<p>It’s just enough to help me be productive without requiring I setup and maintain Typescript tooling over time.</p>
<hr><ol class="footnotes"><li id="fn:1">It was harder than I thought to find out about JSDoc + Zod online, but thanks to <a href="https://gist.github.com/pmuellr/60668d33049f96ce7323f5eab648f468">this gist</a> I was able to figure it out. So, I figured I’d write a quick blog post in case anyone in the future ever wants to know if you can infer types using Zod with JSDoc comments in plain JavaScript files. <a href="https://blog.jim-nielsen.com/2023/types-in-jsdoc-with-zod/#fnref:1" title="Jump back to footnote 1 in the text.">↩</a></li></ol>
    <hr>
    <ul>
      <li>
        Reply via:
        
        <a href="mailto:jimniels%2Bblog@gmail.com?subject=Re:%20blog.jim-nielsen.com/2023/types-in-jsdoc-with-zod/">Email</a>, <a href="https://twitter.com/jimniels">Twitter</a>,
        <a href="https://mastodon.social/@jimniels">Mastodon</a>
      </li>
      
      
    </ul>
    <br>
  ]]></description>
      <pubDate>Mon, 27 Mar 2023 14:37:57 +0000</pubDate>
      <link>https://blog.jim-nielsen.com/2023/types-in-jsdoc-with-zod/</link>
      <dc:creator>Jim Nielsen’s Blog</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/4054115048</guid>
    </item>
    <item>
      <title><![CDATA[Visually hidden content is a hack that needs to be resolved, not enshrined]]></title>
      <description><![CDATA[<style>
figure {
	padding: .5em;
	border: 1px solid #444;
	font-size: .825em;
	background: #fafafa
}
  @media screen and (prefers-color-scheme: dark) {
  figure { background: #2a2a2a; }
  }
</style>

<p>This will retread and extend on my previous <a href="https://www.scottohara.me/blog/2017/04/14/inclusively-hidden.html">Inclusively Hidden</a> post. Specifically the parts about “visually hidden” content.</p>

<p>But in lieu of reading that, the reasons one would visually hide content in the development of a website or web application is generally to include extra content for accessibility. For instance, to mitigate against specific design choices, where visually something may be apparent, but programmatically not so much. Or, portions of UI are only need to be visible when needed, but otherwise they need to be hidden <em>but</em> remain accessible so that those using keyboard or assistive technology can find them.</p>

<p>The way to visually hide content, but keep it programmatically available, that is probably the most familiar to many developers is with the following CSS ruleset, or a variation of it:</p>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">/* sometimes referred to as .sr-only */</span>
<span class="nc">.visually-hidden</span><span class="nd">:not</span><span class="o">(</span><span class="nd">:focus</span><span class="o">)</span><span class="nd">:not</span><span class="o">(</span><span class="nd">:active</span><span class="o">)</span><span class="nd">:not</span><span class="o">(</span><span class="nd">:focus-within</span><span class="o">)</span> <span class="p">{</span>
  <span class="nl">clip-path</span><span class="p">:</span> <span class="nb">inset</span><span class="p">(</span><span class="m">50%</span><span class="p">);</span>
  <span class="nl">height</span><span class="p">:</span> <span class="m">1px</span><span class="p">;</span>
  <span class="nl">overflow</span><span class="p">:</span> <span class="nb">hidden</span><span class="p">;</span>
  <span class="nl">position</span><span class="p">:</span> <span class="nb">absolute</span><span class="p">;</span>
  <span class="nl">white-space</span><span class="p">:</span> <span class="nb">nowrap</span><span class="p">;</span> 
  <span class="nl">width</span><span class="p">:</span> <span class="m">1px</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>For a breakdown of what each CSS property is doing, check out this <a href="https://www.tpgi.com/the-anatomy-of-visually-hidden/">dissection of the ruleset, by TPGi</a>. <small>Tbh, depending on your browser support matrix, I’ve found that there a few property declarations that can be removed with no negative impact for modern browsers/screen readers. Just something to think about. Maybe <a href="https://codepen.io/scottohara/pen/QWVOqNY">test this out n’ see if you can find something broken</a>? Not endorsing this method, but also, presently unaware of issues - so long as you’re using this to visually hide static content. As in, this ruleset <strong>not intended</strong> to hide interactive controls. But just static content <em>within</em> an interactive control.</small></p>

<p>At the end of that TPGi article, the idea of consolidating this ruleset into a single CSS declaration, for example <code class="language-plaintext highlighter-rouge">display: visually-hidden</code>, was mentioned. This idea was also a brought up in <a href="https://benmyers.dev/blog/native-visually-hidden/">“The Web Needs a Native .visually-hidden”, by Ben Myers</a>. But this is not a new topic, and has been brought up over the years. For instance, <a href="https://github.com/whatwg/html/issues/4623">as a request for an HTML feature</a>, and as different CSS WG issues from 2016.</p>

<p>Full disclosure, I have frequently used and recommended the use of visually hidden content. It’s an important tool that we presently need to help mitigate accessibility gaps for content (design) and features of and related to common web UI. However, while a frequent recommendation, it’s one I would rather I never had to mention again. But, before we get into that, and why I find the idea of the technique becoming any sort of web ‘standard’ rather icky, let’s review some situations where this ruleset is commonly used.</p>

<h2 id="examples-of-visually-hiding-content">Examples of visually hiding content</h2>

<p>Often, one will need a <a href="https://www.w3.org/WAI/WCAG21/Techniques/general/G1">skip to main content link</a> on their website or web application in order to adhere to <a href="https://www.w3.org/WAI/WCAG21/Understanding/bypass-blocks.html">WCAG’s 2.4.1 Bypass Block</a>. The most common way of implementing this instance of a skip link is to visually hide it until it receives keyboard focus. Because a skip link is a navigational mechanism for a sighted keyboard user, after all. <a href="https://www.w3.org/WAI/WCAG21/Understanding/focus-visible.html">So one needs to be able to see what they’ve focused</a> to know whether or not to try and interact with it.</p>

<figure aria-label="Fig1: npr skip link">
	<img src="https://scottohara.me/assets/img/articles/skipnpr.png" alt="NPR.org has a skiplink, displayed atop the global banner of their website">
	<p>Not all news websites have a skip to main content link, but <a href="https://www.npr.org">NPR.org</a> does. They have a couple which become visible when they receives keyboard focus.</p>
	<p>NPR uses a variation of the visually hidden class.</p>
</figure>

<p>Or, one might visually hide pesky form controls so that they are still available to screen reader and keyboard users. But maybe you wanted to replace them with a <a href="https://scottaohara.github.io/a11y_styled_form_controls/src/radio-button--rating/">fancy ‘custom’ control(s)</a>, allowing for more styling options.</p>

<figure aria-label="Fig2: star rating radios">
	<img src="https://scottohara.me/assets/img/articles/starradio.png" alt="radio buttons styled to look like stars. 'rate my rating system' with cancel icon for no selection, and 5 stars with a popup that appears on focus to display the accessible name - e.g., 2 stars">
	<p>Visually styled to look like stars and a cancel icon, these are all native HTML radio buttons and would be exposed as such if styles were turned off or blocked.</p>
	<p>Fun fact, these radio buttons don't use the 'typical' visually hidden class.</p>
</figure>

<p>Alternatively, maybe you don’t need to restyle a control, but only want it available on hover and focus. That’s a thing we do to make it more difficult for sighted touch, switch device and speech recognition users to find initially hidden controls. I mean, to visually declutter UI.</p>

<p>But snark aside, there is a constant push and pull between overloading UI with too many controls or too much content. If the UI has too many competing visual features at one time, it can be overwhelming for some users. However, hiding important UI can result in higher difficulty for other users to interact with the page. The struggle is real.</p>

<figure aria-label="Fig3: star rating radios">
	<img src="https://scottohara.me/assets/img/articles/plusbutton.png" alt="a line of code in a GitHub pull request showing a 'plus' button that appears on hover/focus to add a comment">
	<p>Each line of code can be commented on with a GitHub pull request. It would be overwhelmingly redundant to visually have a comment button on each line, so it is visually hidden until the line is hovered, or the button receives keyboard focus.</p>
	<p>These buttons also don't use the typical visually hidden class.</p>
</figure>

<p>Or or, content might need to be visually hidden because someone wrote some rubbish call-to-action copy and the web page would otherwise be riddled with <a href="https://www.nngroup.com/articles/learn-more-links/">“learn more” or “read more” links</a> that have <a href="https://www.w3.org/WAI/WCAG21/Understanding/link-purpose-in-context.html">no immediate programmatic context to their purpose</a>.</p>

<p>Maybe a web app was designed in a way where one can infer the current page’s purpose due to the styling of the “current page link” in the primary nav - so there’s no visible heading to introduce the primary content of the page.  Oh, and adding that heading in there would “degrade the design”, so cool. Guess we can insert a visually hidden heading to help screen reader users, at least, without disrupting the design which would be absolutely ruined and unusable if a heading was added. <a href="https://webaim.org/projects/screenreadersurvey9/#heading">Horrible, horrible headings…</a></p>

<figure aria-label="Fig4: visually hidden h1">
	<img src="https://scottohara.me/assets/img/articles/homeh1.png" alt="facebook's h1 is visually hidden for their home screen">
	<p>Rather than display the primary heading of the page, Facebook visually hides it.</p>
	<p>The visually hidden technique appears to be used here, but has been obfuscated into various single-purpose compiled classes. <code>.x1heor9g</code> is a real banger of a utility class, if I do say so myself.</p>
</figure>

<p>Or or or, a website or web application requires a ‘global’ live region component. For instance, rather than having multiple live regions on a web page, a single live region is used to expose dynamic updates to a page.  <a href="https://www.scottohara.me/blog/2022/02/05/are-we-live.html">Live regions can be quite annoying to get right</a>, so a persistent visually hidden live region is often used for complex web apps, so as to work around having to have tons of empty live regions in a screen, waiting for content to be added to them so they can inform screen reader users that “You must fill out your name. It is REQUIRED!!!! SURE I KNOW YOU’RE JUST TABBING THROUGH THE PAGE RIGHT NOW TO CHECK OUT WHAT THIS FORM INCLUDES, BUT DAMNIT I NEED YOUR NAME! I NEED IT NOW!!!! NOW!!!!!”  <small>I may have some thoughts about how aggressive web forms are with on-blur error messaging</small>.</p>

<figure aria-label="Fig5: screen reader only live region">
	<img src="https://scottohara.me/assets/img/articles/sronly.png" alt="visually hidden live region appearing at the bottom of the DOM">
	<p>The most on the nose instance of the <code>.sr-only</code> class, being used to visually hide a global ARIA live region as the last element in a web page.</p>
</figure>

<p><span title="or-n't you glad I didn't say 'or' again?">There are other instances</span> of where you may want content to be hidden but available only for people using assistive technology, or only when the hidden content is actually relevant (e.g., a sub-navigation list of links). These would be different from the previously mentioned examples, as these would not be situations where the content should be treated as visually hidden. Rather, they would generally need to be hidden to everyone. Again, see my <a href="https://www.scottohara.me/blog/2017/04/14/inclusively-hidden.html">previous blog post</a> for more information on stuff like that.</p>

<h2 id="sure-does-seem-we-need-to-visually-hide-stuff-quite-a-bit">Sure does seem we need to visually hide stuff quite a bit?</h2>

<p>If there’s a need for so many instances of visually hidden content, why wouldn’t it be standardized? Well, I mean standardized beyond the already existing ways to visually hide content:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">color: transparent</code></li>
  <li><code class="language-plaintext highlighter-rouge">opacity: 0</code></li>
  <li><code class="language-plaintext highlighter-rouge">transform: scale(0)</code></li>
  <li><code class="language-plaintext highlighter-rouge">clip-path: inset(50%)</code></li>
  <li>etc.</li>
</ul>

<p>By themselves, each of these properties be used to visually hide content per the situation, but someone might have to add a few extra property declarations, e.g., <code class="language-plaintext highlighter-rouge">position: absolute</code>, to get the content to no longer take up visual space in the document layout. Or, a combination of some of the above or other properties may be necessary to fit one’s exact visually-hidden needs. For instance, if creating a custom checkbox style, one might think to just use the standard visually hidden class.</p>

<p id="checkbox-hack">
	However, as <a href="https://scottaohara.github.io/a11y_styled_form_controls/src/checkbox/">I've been noting for some time now</a> and others have since mentioned, using the standard visually hidden class will introduce problems due to the 1px by 1px nature of the beast/ruleset. 
</p>

<p>Remember, not all screen reader users are completely blind. Some of these users, for instance, might be able to see or partially see a custom checkbox on their iOS or Android device. They might then think to press or drag their finger across their screen to <a href="https://support.apple.com/guide/iphone/learn-voiceover-gestures-iph3e2e2281/ios">“explore by touch”</a> while the screen reader is enabled, only to then be seemingly unable find this checkbox that visually appears to be there, but is really shoved off into a 1px by 1px square somewhere on the page. Fun!</p>

<p>
	Heck, this problem would exist even for instances of not completely restyling a checkbox from the ground up (which also, unless you're going way out there with a custom style, <a href="https://www.scottohara.me/blog/2021/09/24/custom-radio-checkbox-again.html">we don't really need to do anymore</a>.
</p>

<p>Consider a button (such as a “more options”, or “edit”, “delete”, etc.) that is visually hidden in a ‘card’ or ‘row’ of content, where it only becomes visible on hover (generally of the whole container), or focus/focus-within the container.  If the standard visually hidden class was used here, this too would suffer from the 1px by 1px problem. (You may remember I mentioned GitHub has a visually hidden comment button for reviewing PRs, but it doesn’t use the standard visually hidden technique. They don’t have this 1px by 1px problem!)</p>

<p>A standardization of the ruleset wouldn’t help with these situations. Actually, it’d probably make the problem more prevalent, since many developers would (understandably) think all they needed to do was to set this fabled single property, and all their accessibility problems concerning content that needed to be hidden would go away. Just like ARIA, it’s there to make your content <em>more accessible</em>.</p>

<p>Wait… what?  ARIA gets used in situations where it is either unnecessary or not appropriate for the use case? Shocking.</p>

<h3 id="what-about-fixing-the-underlying-problems-instead">What about fixing the underlying problems instead?</h3>

<p>Getting caught up in how to make visually hiding content easier for developers to implement (you know, so they don’t have to copy/paste a class name), imo, is missing the mark on solving the real issue. We “need” to visually hide content because of gaps in the design of the UI, or because of features which are lacking from browsers.</p>

<p>For many of the examples called out earlier, what if those gaps were solved instead?</p>

<h4 id="skip-to-content-links-are-also-a-dirty-hack">Skip to content links are also a dirty hack</h4>

<p>We shouldn’t need skip to content links.  Rather, what would be way more useful that including a visually hidden link at the top of every website, is <a href="https://github.com/whatwg/html/issues/5813">if browsers would support navigation by elements that are exposed as landmark roles</a>. At the very least, allow for a mechanism to directly move focus to the content of the main element/landmark. Alternatively, allowing for quick navigation by headings would be pretty awesome, too.</p>

<p>Implementing one of those ideas could negate the need for skip to main content links, at least.  You don’t need to visually hide something that isn’t necessary to implement, after all.</p>

<h4 id="styling-form-controls-isnt-as-bad-as-it-used-to-be">Styling form controls isn’t as bad as it used to be</h4>

<p>Yes, visually hiding a form control so that a custom styled ‘faux’ control can be rendered in its place is rather common. <a href="https://scottohara.me//blog/2023/03/21/visually-hidden-hack.html#checkbox-hack">But as mentioned</a>, the standard visually hidden ruleset is not what you should be using to effectively hide the few controls that even allow for this hack.  Also, wouldn’t it be far better if time was instead spent trying to push <a href="https://open-ui.org/components/checkbox/">proposals like a new checkbox element</a> over the line?</p>

<p>IMO, new checkbox and radio elements are not the most important features we need on the web.  But if given the option between those and a standardized visually hidden feature, yes. Please. New checkboxes and radios. Solve for the problem, not the symptom.</p>

<h4 id="regarding-aria-label-and-insufficient-ctas">Regarding <code class="language-plaintext highlighter-rouge">aria-label</code> and insufficient CTAs</h4>

<p>A web page containing a bunch of buttons or links with, for example, “edit” or “learn more” visible text, and thus accessible names, can be rather rubbish depending on the way one is navigating the web page.</p>

<p>For instance, a page listing article or product previews, each with with a “learn more” call to action link. Without extra development effort, all these links will simply be exposed as “learn more” when someone navigates by focusable elements, or uses their screen reader to navigable specifically  by links.</p>

<figure aria-label="Fig6: learn more rotor">
	<img src="https://scottohara.me/assets/img/articles/learnmore.png" alt="VoiceOver rotor showing a skip to main, back to top, and several 'learn more' links">
	<figcaption><p>Before you follow these links to learn more, you're going to need to learn more about what you'd be learning more about.</p></figcaption>
</figure>

<p>Code for what could produce the above link listing might look something like this:</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"card-or-whatever"</span><span class="nt">&gt;</span>
  <span class="nt">&lt;h3&gt;</span>Here's some descriptive text but lol, I'm not a link!<span class="nt">&lt;/h3&gt;</span>
  <span class="nt">&lt;p&gt;</span>Fluffy fluff fluff tells you stuff about the fluff.<span class="nt">&lt;/p&gt;</span>
  <span class="nt">&lt;p&gt;&lt;a</span> <span class="na">href=</span><span class="s">...</span><span class="nt">&gt;</span>Learn more!<span class="nt">&lt;/a&gt;&lt;/p&gt;</span>
<span class="nt">&lt;/div&gt;</span>
</code></pre></div></div>

<p>In these contexts where the link serves as a standalone CTA outro to UI elements like ‘cards’, these can be flagged as <a href="https://www.w3.org/WAI/WCAG21/Understanding/link-purpose-in-context.html">2.4.4 Link Purpose (In Context)</a> failures. This is because the CTA is not programmatically associated with other text (e.g., a paragraph) to help provide the context as to what one would be learning more about.</p>

<p>This is why such CTAs are commonly recommended to use the visually-hidden technique to provide more context. For instance,</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">...</span><span class="nt">&gt;</span>
  Learn more
  <span class="nt">&lt;span</span> <span class="na">class=</span><span class="s">visually-hidden</span><span class="nt">&gt;</span>about whatever yada yada<span class="nt">&lt;/span&gt;</span>
<span class="nt">&lt;/a&gt;</span>
</code></pre></div></div>

<p>However, while often used to alleviate this lack of context, there are other avenues we could be considering, instead.</p>

<p>For example, <code class="language-plaintext highlighter-rouge">aria-label</code> and <code class="language-plaintext highlighter-rouge">aria-labelledby</code> can allow for the same context to be provided, without the need for the visually hidden hack.</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">"..."</span> <span class="na">aria-label=</span><span class="s">"Learn more about something"</span><span class="nt">&gt;</span>
  Learn more
<span class="nt">&lt;/a&gt;</span>

...

<span class="nt">&lt;p</span> <span class="na">id=</span><span class="s">thing</span><span class="nt">&gt;</span>My address, or whatever<span class="nt">&lt;/p&gt;</span>
...
<span class="nt">&lt;button</span> <span class="na">id=</span><span class="s">self</span> <span class="na">aria-labelledby=</span><span class="s">"self thing"</span><span class="nt">&gt;</span>
  Edit
<span class="nt">&lt;/button&gt;</span>

</code></pre></div></div>

<p>Granted, right now <a href="https://adrianroselli.com/2019/11/aria-label-does-not-translate.html#Update04">translation services and <code class="language-plaintext highlighter-rouge">aria-label</code> continue to have issues</a>, and using <code class="language-plaintext highlighter-rouge">aria-labelldby</code> has the problem that it necessitates the use of IDs, and developers hate IDs.</p>

<p>So, maybe ARIA isn’t an option for you.  But you know what that leaves?  Making a better design.</p>

<p>For instance, I’ve already referenced the Nielsen Group article <a href="https://www.nngroup.com/articles/learn-more-links/">“Learn More” Links: You Can Do Better</a>, but I will do so again because it is worth repeating.</p>

<p>Take a look at many popular news aggregate websites <small>(well, look at them just in the context of this train of thought… I can’t endorse them otherwise, unless you’re looking to significantly up your anxiety and tank your mood.  News sites do that great.)</small>. Many of which I’ve reviewed show they’ve since moved away from the “learn/read more” CTA approach and instead treat the concise but descriptive titles/headings of their article previews as the links to their content.</p>

<p>“But Scott, user testing has shown that CTAs get more clicks”</p>

<p>Well sure, I didn’t think we needed a test to determine if someone sees a big button-looking control that screams “click me”, they will click it. But visual cues to visually promote “click me” behaviors don’t “need” to impact the accessibility of the content.</p>

<p>For instance, there’s no reason one couldn’t implement a CTA as more of a decorative element that still allowed for pointer events to activate it.  A very quick example (and one of the demos even still has the lovely ‘read more’ appear on hover/focus):</p>

<p class="codepen" data-height="522" data-default-tab="html,result" data-slug-hash="LYJgbrd" data-user="scottohara" style="height: 522px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/scottohara/pen/LYJgbrd">
  simple cta article 'card'</a> by Scott (<a href="https://codepen.io/scottohara">@scottohara</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async="" src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

<p>I’m sure many could come up with more creative designs than that five minute codepen.  But maybe take this as a challenge. Accessible design doesn’t need to be boring, rather good accessible design demands creativity.  So, be creative and move away from the uninspiring learn more links and visually hidden hack.</p>

<h4 id="we-need-a-notification-api">We need a notification API</h4>
<p><a href="https://www.scottohara.me/blog/2022/02/05/are-we-live.html">Live regions are finicky</a>, and having to implement a visually hidden live region to act as a notification center for a website/web application is yet another hack built atop a shouty house of cards.</p>

<p>Rather, if we had <a href="https://wicg.github.io/aom/notification-api.html">a notification API</a>, we wouldn’t need to visually hide live regions at all.  Rather, we would be able to dynamically render the content we want on our web pages, and then use the API to ensure the rendered text content, or maybe even something more concise/informative than what is visually rendered, could be exposed to the accessibility tree.  No reliance on DOM elements, no problems where the live region that should have handled this is within an <code class="language-plaintext highlighter-rouge">inert</code> or <code class="language-plaintext highlighter-rouge">aria-hidden=true</code> subtree because a modal dialog opened and blew your aforementioned shouty house of cards down.</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;dialog&gt;</span>
  Imma be a modal dialog!

  <span class="nt">&lt;button&gt;</span>
  	Press me and make a toast show up,
  	but LOL don't expect to hear it!
  <span class="nt">&lt;/button&gt;</span>
<span class="nt">&lt;/dialog&gt;</span>

...

<span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">visually-hidden</span> <span class="na">aria-live=</span><span class="s">polite</span><span class="nt">&gt;</span>
  Inject whatever you want into me, and I'll typically
  announce it. But, I'm not saying 
  "boo" if that modal dialog is open! 
<span class="nt">&lt;/div&gt;</span>
</code></pre></div></div>

<p>You want to get around this?  Either don’t use the native dialog element (which would be quite a shame, as using it responsibly can be far better than creating your own).  Or, put <strong>another</strong> live region into your dialog, so your page can continue to shout things while the modal dialog is open and everything outside of it is treated as inert.</p>

<h2 id="wrapping-up">Wrapping up</h2>

<p>For better and worse, we will have instances where we need to visually hide content to mitigage a gap in the visual design. That’s not to say that every gap is necessarily due to a failure to incorporate accessibility in the design process. Rather, because there is a balance that needs to be maintained between what is visually presented, vs what is programmatically conveyed, sometimes the best way to work around that is to inject content that is specifically for people using assistive technology.</p>

<p>Them’s the breaks.</p>

<p>But, as I’ve hopefully illustrated by now, while there is a common ruleset for visually hiding content, codifying that to me seems more of a silent acknowledgement that a hack is the best we can do. I don’t agree. The web should be better than that.</p>

<ul>
  <li>We <em>should</em> be able to navigate to landmarks (e.g., skip to main content) by default browser commands.</li>
  <li>We <em>should</em> be able to send dynamic messages to assistive technology, without having to rely on fragile, invisible, live regions.</li>
  <li>We <em>should</em> be able to use <code class="language-plaintext highlighter-rouge">aria-label</code> (and also <code class="language-plaintext highlighter-rouge">aria-description</code>) without having to worry about translation services.</li>
  <li>We <em>should</em> be able to fully and easily style common form controls.</li>
  <li>And we <em>shouldn’t</em> need to introduce a native way to do all these things, which covers various use cases for visually hidden content, but not all of them.</li>
</ul>

<p>Introducing a native mechanism to save developers the trouble of having to use a wildly available CSS ruleset doesn’t solve any of those underlying issues. It just further pushes them under the rug.  Additionally, it introduces an “official” way to hide content which at best, might not be the right way to hide content for a particular use case. And at worse, it provides a hiding mechanism for people to misuse. Content that <em>should</em> be hidden to everyone could get the “well, isn’t it <em>more accessible</em> if we never truly hide content, so that screen reader users can always have access to everything at all times?”</p>

<p>No. That’d be quite problematic. And while paraphrased, that <em>is</em> a question I’ve been asked, when having to mention to someone that they’ve incorrectly hidden content. While this thinking comes from a good place, it is exactly what we <em>don’t</em> want to happen.</p>

<p>Now, one might say that education and examples are the ways to promote correct usage. And that person would be right. But also, if documentation and providing official examples would mitigate misuse and misunderstandings, then we wouldn’t need stack overflow or slack channels for people to post questions, daily, or online courses to explain to developers what existing free resources already cover.</p>

<p>Anyway, that’s it.  Those are my very not-hidden thoughts on the topic.  Thanks for reading all these yucky words. So so many words…</p>]]></description>
      <pubDate>Tue, 21 Mar 2023 00:00:00 +0000</pubDate>
      <link>https://scottohara.me//blog/2023/03/21/visually-hidden-hack.html</link>
      <dc:creator>Scott O’Hara - Accessibility engineer, UX developer and designer</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/4046315337</guid>
    </item>
    <item>
      <title><![CDATA[When is :focus-visible visible?]]></title>
      <description><![CDATA[<p>Focus outlines (and their styling) have frequently been a point of contention between aesthetics and usability. On the one hand, focus outlines are incredibly important for navigation, particularly by users navigating via keyboard or other non-pointer devices. On the other hand, for certain mouse or touchscreen users they can be unnecessary and detract from the design.</p>
<p>Before the <code>:focus-visible</code> CSS pseudo-class was introduced, there was no middle ground to this debate - you either sided with design or accessibility (hopefully the latter). But now, with <code>:focus-visible</code>, we can have focus styles that apply only when the browser / user agent determines that focus should be indicated on the element.</p>
<p>So, when <em>do</em> user agents determine that focus should be indicated? According to the <a href="https://www.w3.org/TR/selectors-4/#the-focus-visible-pseudo">:focus-visible specification</a>, user agents are free to choose their own heuristics for indicating focus. However, the spec includes some suggestions, which most browsers have adopted. These suggestions can be broken down into four questions -</p>
<ol>
<li>Has the user expressed a preference for always visible focus?</li>
<li>Does the focused element support keyboard input?</li>
<li>Is the user interacting with the focused element with a non-pointing device?</li>
<li>Did a script cause focus to move from a previously visibly-focused element?</li>
</ol>
<p><picture><source type="image/avif" srcset="https://bitsofco.de/img/z3uJeVgO1Y-814.avif 814w"><source type="image/webp" srcset="https://bitsofco.de/img/z3uJeVgO1Y-814.webp 814w"><img alt="Flowchart showing when :focus-visible applies" loading="lazy" decoding="async" src="https://bitsofco.de/img/z3uJeVgO1Y-814.png" width="814" height="1019"></picture></p>
<p>Let’s break each of these questions down.</p>
<h2 id="1-has-the-user-expressed-a-preference-for-always-visible-focus" tabindex="-1">1. Has the user expressed a preference for always visible focus? <a class="header-anchor" href="https://bitsofco.de/when-is-focus-visible-visible/">#</a></h2>
<p>This is a simple one. If a user has expressed a preference for focus to always be visible, then the <code>:focus-visible</code> pseudo-class should apply to the focused element. Fin.</p>
<h2 id="2-does-the-focused-element-support-keyboard-input" tabindex="-1">2. Does the focused element support keyboard input? <a class="header-anchor" href="https://bitsofco.de/when-is-focus-visible-visible/">#</a></h2>
<p>If the focused element supports keyboard input, the <code>:focus-visible</code> pseudo-class should apply. Elements that support keyboard input are typically form elements, such as <code>&lt;input&gt;</code> or <code>&lt;textarea&gt;</code>, basically any element that would trigger a virtual keyboard to be shown in the absence of a physical one.</p>
<p>In the example below, we have both a  <code>&lt;button&gt;</code> and an <code>&lt;input&gt;</code>. For the <code>&lt;button&gt;</code>, whether the <code>:focus</code> or <code>:focus-visible</code> style is applied depends on how you interact with the element (more on that in the next section). But regardless of how you interact with the <code>&lt;input&gt;</code> element, it always displays both the <code>:focus</code> style and the <code>:focus-visible</code> style of the red border.</p>
<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="YzOvBMP" data-user="ire" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/ire/pen/YzOvBMP">
  Untitled</a> by Ire Aderinokun (<a href="https://codepen.io/ire">@ire</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<h2 id="3-is-the-user-interacting-with-the-focused-element-with-a-non-pointing-device" tabindex="-1">3. Is the user interacting with the focused element with a non-pointing device? <a class="header-anchor" href="https://bitsofco.de/when-is-focus-visible-visible/">#</a></h2>
<p>Next, if the user is interacting with the focused element using a keyboard or another non-pointing device, the <code>:focus-visible</code> pseudo-class should apply. So regardless of the type of element it is, focus should be visible.</p>
<p>In the example below, the <code>&lt;button&gt;</code> elements display the  <code>:focus</code> and/or <code>:focus-visible</code> styles depending on what device you use to interact with it.</p>
<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="qBMKgGG" data-user="ire" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/ire/pen/qBMKgGG">
  :focus-visible - Is the user interacting with a non-pointing device?</a> by Ire Aderinokun (<a href="https://codepen.io/ire">@ire</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<h2 id="4-did-a-script-cause-focus-to-move-from-a-previously-visibly-focused-element" tabindex="-1">4. Did a script cause focus to move from a previously visibly-focused element? <a class="header-anchor" href="https://bitsofco.de/when-is-focus-visible-visible/">#</a></h2>
<p>Finally, this question is to do with what happens if focus is moved via a script instead of user input. According to this guideline, the <code>:focus-visible</code> pseudo-class should apply based on the state of the previously focused element. If <code>:focus-visible</code> applied to the previously focused element, then it should also apply to the currently focused element.</p>
<p>We can see this play out in the example below. Depending on how you interact with the “reset” button, the <code>:focus</code> and/or <code>:focus-visible</code> styles will apply to the test button when focus is programatically moved.</p>
<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="mdGKobP" data-user="ire" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/ire/pen/mdGKobP">
  :focus-visible - script</a> by Ire Aderinokun (<a href="https://codepen.io/ire">@ire</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<h2 id="bonus-user-agent-wildcard" tabindex="-1">Bonus - User agent wildcard <a class="header-anchor" href="https://bitsofco.de/when-is-focus-visible-visible/">#</a></h2>
<p>As a reminder, these are mostly guidelines/suggestions and user agents are still able to make decisions about when they want <code>:focus-visible</code> to apply. From my tests, I haven’t seen any of the major browsers respond in a different way, but it’s something to remember.</p>
<p>Because of this, best practice is always to only remove <code>:focus</code> styles if <code>:focus-visible</code> is <em>not</em> also applied to the element. Here’s what that looks like:</p>
<pre class="language-css" tabindex="0"><code class="language-css"><span class="token comment">/* Apply focus styles */</span><br><span class="token selector">:focus-visible, :focus</span> <span class="token punctuation">{</span><br>  <span class="token property">outline</span><span class="token punctuation">:</span> 1px solid red<span class="token punctuation">;</span><br><span class="token punctuation">}</span><br><br><span class="token comment">/* Remove the focus outline */</span><br><span class="token selector">:focus:not(:focus-visible)</span> <span class="token punctuation">{</span><br>  <span class="token property">outline</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span><br><span class="token punctuation">}</span></code></pre>
]]></description>
      <pubDate>Tue, 21 Mar 2023 00:00:00 +0000</pubDate>
      <link>https://bitsofco.de/when-is-focus-visible-visible/</link>
      <dc:creator>bitsofcode</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/4045774687</guid>
    </item>
    <item>
      <title><![CDATA[The Birth of “Disable JavaScript”]]></title>
      <description><![CDATA[<p>Did you know: “Disable JavaScript” as a browser feature was born out of an exasperation with plugging security holes?</p>
<p>I didn’t. But then <a href="https://blog.jim-nielsen.com/2023/hipster-history-of-cors/">I watched “A Hipster History of CORS”</a> and the speaker, Devdatta Akhawe, references the history of the feature as outlined in the book  <em>JavaScript: The Definitive Guide</em>. </p>
<p>Security in JavaScript in those early days was WDD: whack-a-mole driven development. Find a mole (security hole) and whack it (plug it). <a href="https://docstore.mik.ua/orelly/web/jscript/ch20_02.html">From the book</a>:</p>
<blockquote>
<p>The approach to JavaScript security in Navigator 2.0 and 3.0 has been to first identify security holes through which private information could be exported, and then to plug those holes…The problem with an identify-and-patch approach is that it can be difficult to identify security holes, and that there is no way of knowing when you've found all possible holes</p>
</blockquote>
<p>A conundrum indeed. If you patch security holes as you find them, how will you ever know if you’ve plugged all the holes?</p>
<p>Well that’s easy, my friend. Just get rid of what’s causing the holes.</p>
<p class="image-container"><img src="https://cdn.jim-nielsen.com/blog/2023/7a0cqa.jpg" width="702" height="395" alt="Roll safe meme guy with the words, “you can't have a security hole in javascript if there is no javasript”">

</p><p>These security fixes were known as “hobbles” and Netscape came up with the “ultimate hobble”: disable JavaScript.</p>
<p>If you read that chapter, some of the early documented security holes are pretty wild, like this one:</p>
<blockquote>
<p>[people used] the file:/// URL to discover the contents of the root directory of the client's system, and could recursively proceed to determine the client's entire directory structure </p>
</blockquote>
<p>Of course, if you use node, this particular exploit is still feasible by merely running <code>npm i</code> and having a malicious package somewhere deep in your dependencies — but I digress.</p>
<p>Turning off JavaScript is nice and all, but having JavaScript on is also nice. So “Disable JavaScript” as a feature wasn’t a permanent solution.</p>
<p>Eventually they came up with an idea that would hobble an entire class of security holes: cross-origin restrictions on JavaScript window APIs.</p>
<blockquote>
<p>Because of the continuing problem with security holes, and because of the resulting bad press, Netscape soon released Navigator 2.0.2, which fixed all known security-related bugs and implemented a very general hobble that would, hopefully, spell an end to security holes. With this hobble implemented, a JavaScript program is not allowed to read the properties of any window (or frame) or the properties of any objects within a window if the contents of that window were loaded from a different web server than the JavaScript program itself.</p>
</blockquote>
<p>This hobble birthed the idea of cross-origin restrictions and eventually made it into <code>XmlHTTPRequest</code> and later everything CORS.</p>
<p>It’s all a fascinating history. Go <a href="https://www.youtube.com/watch?v=0YJ-yhoJh2I">listen to the talk</a> if this sounds like your cup of tea.</p>

    <hr>
    <ul>
      <li>
        Reply via:
        
        <a href="mailto:jimniels%2Bblog@gmail.com?subject=Re:%20blog.jim-nielsen.com/2023/the-birth-of-disable-javascript/">Email</a>, <a href="https://twitter.com/jimniels">Twitter</a>,
        <a href="https://mastodon.social/@jimniels">Mastodon</a>
      </li>
      
      
    </ul>
    <br>
  ]]></description>
      <pubDate>Mon, 06 Feb 2023 13:53:16 +0000</pubDate>
      <link>https://blog.jim-nielsen.com/2023/the-birth-of-disable-javascript/</link>
      <dc:creator>Jim Nielsen’s Blog</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/3985693994</guid>
    </item>
    <item>
      <title><![CDATA[The case for Flex applications]]></title>
      <description><![CDATA[<p>One day my friend Bryan told me to come look at something on his computer. I respected Bryan, he was a bit older, and his opinions always weighed heavily on me. This seemed urgent, so I shuffled in his office as quick as I could.</p>
<p>On the screen was a grey’ish looking website. Bryan made websites. I made websites. We had that in common. And playing guitar. He looked me in the eyes and said “This is Flex. It’s the future of websites and you need to learn it.”  And from what he was showing me, I was impressed and astounded. A fully rich internet application. XML-powered tables and lists with sorting and animations that you’d spend months building in Flash. I had never seen anything this professional before.</p>
<p><img src="https://cdn.daverupert.com/posts/2023/flex.png" alt="Screenshot of all the UI widgets in Flex"></p>
<p>Flex was the successor to Flash. Or a better way to put it, Flex was Flash but for applications. It came stock with an entire UI kit (aka design system) with form inputs, textareas, form validation patterns, buttons, tabs, accordions, tree browsers, scrollbars, loading bars, and even a calendar. A UI kit in a box. Everything you needed for serious enterprise-grade business applications.</p>
<p>And when I mean applications, I mean applications. Flex worked with Adobe AIR, so you could install your Flex app on people’s computers in a distributable “native” application. The one true write once, run anywhere framework.</p>
<p>The demo-of-all-demos for Flex had to be the drill-down pie chart on the budgeting app Mint. I would spend hours zooming in my categorized spending to get breakdowns by subcategory and back out. Smooth buttery transitions and animations without a single page load. Mint go acquired by Intuit, so Flex appears to be a kingmaker. YouTube wouldn’t exist without Flash either. Flash and Flex were so popular, Microsoft wrote a competing rich internet application development framework called Silverlight.</p>
<p><a href="https://en.wikipedia.org/wiki/List_of_Flex_frameworks">Flex had meta-frameworks</a> too to help people build Flex sites even faster. They embraced the super popular MVC pattern. Frameworks make sites better because developers can work faster and that trickles down to the user. Flex and Flash tooling is superior, so you’re able to make experiences you could never dream of making in an HTML5 website.</p>
<p>That’s Flex. It’s built for real applications, not websites. It has a thriving ecosystem. Backed by a big company like Adobe. Regular updates. It’s built on Flash and used by the majority of serious web developers. It’s great, no problems.</p>
<p>Okay. I’ll admit making Flash sites accessible is notoriously difficult. And SEO for Flash sites is also difficult. But beyond that, perfect.</p>
<p>Okay, okay. If you want to be a little more critical… one problem with Flex applications is that they don’t work well on phones. They only worked on one phone to be exact; the Motorola Droid. My brother had one of those phones. He could play tower defense Flash games on it. He made a lot more Flash websites than I did (I got into custom <a href="https://cakephp.org/">faux-Rails PHP sites</a>), so it made sense why he liked the non-censored version of the web.</p>
<p>Steve Jobs didn’t have to write <a href="https://web.archive.org/web/20170615060422/https://www.apple.com/hotnews/thoughts-on-flash/">a mean blog post about Flash</a>. A lot of people make a living doing Flash sites, so that’s pretty rude of him. Who cares if Flash was extremely impractical for phones because it shredded the battery and had a bunch of security holes. Flash was popular and people liked it and people got paid to make Flash and Flex sites, ergo, it’s good. But it didn’t matter if Steve didn’t like Flash because the majority of my users were on desktop.</p>
<p>Over the years I tried again and again to pick up Flex. I had okay ActionScript skills but the nuances of Flex applications escaped me. Every site I made back then had Flash; band sites, church sites, Homestar Runner clones, zoomy portfolios, and even real estate websites. But it felt like whenever I tried to learn Flex for serious web applications, it had changed enough that I spent all my energy relearning the basics and I could never get to the interesting parts, like the drill-down pie chart I loved so much.</p>
<p>Eventually I read Jeffrey Zeldman’s book and settled on making boring websites that anyone could use. Flex and Flash meanwhile died slow deaths because Steve was a meanie. As time marched on, whenever you ended up on a Flex site you could tell. It wasn’t the loading bars, but the buttons and UI that felt frozen in time. They weren’t nearly as fluid and fast as the new-fangled <a href="http://script.aculo.us">Script.aculo.us</a>-powered sites.</p>
<p>I was real happy when CSS animations came to Webkit, the browser I had on my Steve Jobs phone, because it meant I could animate and tween again. What a joy!</p>
<p>As for Bryan, he moved away to a farm in Washington and I think he makes boring websites now too. I imagine he’s still the same guy, out there on that farm summoning infinite stamina to stay awake for five days straight to finish his projects. He may have been wrong about Flex, but he was good at guitar.</p>
]]></description>
      <pubDate>Sun, 12 Feb 2023 22:02:00 +0000</pubDate>
      <link>https://daverupert.com/2023/02/the-case-for-flex-applications/</link>
      <dc:creator>daverupert.com</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/3994710021</guid>
    </item>
    <item>
      <title><![CDATA[ElementInternals and Form-Associated Custom Elements]]></title>
      <description><![CDATA[<p>In <a href="https://webkit.org/blog/13703/release-notes-for-safari-technology-preview-162/">Safari Technology Preview 162</a> we enabled the support for <a href="https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals"><code>ElementInternals</code></a> and the form-associated custom elements by default. <a href="https://webkit.org/blog/7027/introducing-custom-elements/">Custom elements</a> is a feature which lets web developers create reusable components by defining their own HTML elements without relying on a JavaScript framework. <a href="https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals"><code>ElementInternals</code></a> is a new addition to custom elements API, which allows developers to manage a custom element’s internal states such as default ARIA role or ARIA label as well as having custom elements participate in form submissions and validations.</p>
<h2>Default ARIA for Custom Elements</h2>
<p>To use <code>ElementInternals</code> with a custom element, call <code>this.attachInternals()</code> in a custom element constructor just the same way we’d call <code>attachShadow()</code> as follows:</p>
<pre><code class="js"><span class="keyword type">class</span> <span class="identifier">SomeButtonElement</span> <span class="keyword modifier">extends</span> <span class="identifier">HTMLElement</span> {
    #<span class="identifier">internals</span>;
    #<span class="identifier">shadowRoot</span>;
    <span class="keyword">constructor</span>()
    {
        <span class="keyword">super</span>();
        <span class="keyword">this</span>.#<span class="identifier">internals</span> <span class="operator">=</span> <span class="keyword">this</span>.<span class="identifier">attachInternals</span>();
        <span class="keyword">this</span>.#<span class="identifier">internals</span>.<span class="identifier">ariaRole</span> <span class="operator">=</span> <span class="char">'button'</span>;
        <span class="keyword">this</span>.#<span class="identifier">shadowRoot</span> <span class="operator">=</span> <span class="keyword">this</span>.<span class="identifier">attachShadow</span>({<span class="identifier">mode</span><span class="operator">:</span> <span class="char">'closed'</span>});
        <span class="keyword">this</span>.#<span class="identifier">shadowRoot</span>.<span class="identifier">innerHTML</span> <span class="operator">=</span> <span class="char">'&lt;slot&gt;&lt;/slot&gt;'</span>;
    }
}
<span class="identifier">customElements</span>.<span class="identifier">define</span>(<span class="char">'some-button'</span>, <span class="identifier">SomeButtonElement</span>);
</code></pre>
<p>Here, <code>#internals</code> and <code>#shadowRoot</code> are <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_class_fields">private member fields</a>. The above code will define a simple custom element whose <a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles">ARIA role</a> is <code>button</code> by default. Achieving the same effect without using <code>ElementInternals</code> required sprouting ARIA content attribute on the custom element itself like this:</p>
<pre><code class="js"><span class="keyword type">class</span> <span class="identifier">SomeButtonElement</span> <span class="keyword modifier">extends</span> <span class="identifier">HTMLElement</span> {
    #<span class="identifier">shadowRoot</span>;
    <span class="keyword">constructor</span>()
    {
        <span class="keyword">super</span>();
        <span class="keyword">this</span>.#<span class="identifier">shadowRoot</span> <span class="operator">=</span> <span class="keyword">this</span>.<span class="identifier">attachShadow</span>({<span class="identifier">mode</span><span class="operator">:</span> <span class="char">'closed'</span>});
        <span class="keyword">this</span>.#<span class="identifier">shadowRoot</span>.<span class="identifier">innerHTML</span> <span class="operator">=</span> <span class="char">'&lt;slot&gt;&lt;/slot&gt;'</span>;
        <span class="keyword">this</span>.<span class="identifier">setAttribute</span>(<span class="char">'role'</span>, <span class="char">'button'</span>);
    }
}
<span class="identifier">customElements</span>.<span class="identifier">define</span>(<span class="char">'some-button'</span>, <span class="identifier">SomeButtonElement</span>);
</code></pre>
<p>This code is problematic for a few reasons. For one, it’s surprising for an element to automatically add content attributes on itself since no built-in element does this. But more importantly, the above code prevents users of this custom element to override ARIA role like this because the constructor will override the role content attribute upon upgrades:</p>
<pre><code class="html"><span class="tag">&lt;<span class="keyword">some-button</span> <span class="keyword attribute">role</span>=<span class="attribute value string">"switch"</span>&gt;</span><span class="tag">&lt;/<span class="keyword">some-button</span>&gt;</span>
</code></pre>
<p>Using <code>ElementInternals</code>’s <code>ariaRole</code> property as done above, this example works seamlessly. <code>ElementInternals</code> similarly allows specifying the default values of other ARIA features such as <a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-label">ARIA label</a>.</p>
<h2>Participating in Form Submission</h2>
<p><code>ElementInternals</code> also adds the capability for custom elements to participate in a form submission. To use this feature of custom elements, we must declare that a custom element is associated with forms as follows:</p>
<pre><code class="js"><span class="keyword type">class</span> <span class="identifier">SomeButtonElement</span> <span class="keyword modifier">extends</span> <span class="identifier">HTMLElement</span> {
    <span class="keyword modifier">static</span> <span class="identifier">formAssociated</span> <span class="operator">=</span> <span class="keyword literal">true</span>;
    <span class="keyword modifier">static</span> <span class="identifier">observedAttributes</span> <span class="operator">=</span> [<span class="char">'value'</span>];
    #<span class="identifier">internals</span>;
    <span class="keyword">constructor</span>()
    {
        <span class="keyword">super</span>();
        <span class="keyword">this</span>.#<span class="identifier">internals</span> <span class="operator">=</span> <span class="keyword">this</span>.<span class="identifier">attachInternals</span>();
        <span class="keyword">this</span>.#<span class="identifier">internals</span>.<span class="identifier">ariaRole</span> <span class="operator">=</span> <span class="char">'button'</span>;
    }
    <span class="identifier">attributeChangedCallback</span>(<span class="identifier">name</span>, <span class="identifier">oldValue</span>, <span class="identifier">newValue</span>)
    {
        <span class="keyword">this</span>.#<span class="identifier">internals</span>.<span class="identifier">setFormValue</span>(<span class="identifier">newValue</span>);
    }
}
<span class="identifier">customElements</span>.<span class="identifier">define</span>(<span class="char">'some-button'</span>, <span class="identifier">SomeButtonElement</span>);
</code></pre>
<p>With the above definition of a <code>some-button</code> element, <code>some-button</code> will submit the value of the <code>value</code> attribute specified on the element for the <code>name</code> attribute specified on the same element. E.g., if we had a markup like <code>&lt;some-element name="some-key" value="some-value"&gt;&lt;/some-element&gt;</code>, we would submit <code>some-key=``some-value</code>.</p>
<h2>Participating in Form Validation</h2>
<p>Likewise, <code>ElementInternals</code> adds the capability for custom elements to participate in form validation. In the following example, <code>some-text-field</code> is designed to require a minimum of two characters in the <code>input</code> element inside its shadow tree. When there are less than two characters, it reports a validation error to the user using the browser’s native UI using <code>setValidity()</code> and <code>reportValidity()</code>:</p>
<pre><code class="js"><span class="keyword type">class</span> <span class="identifier">SomeTextFieldElement</span> <span class="keyword modifier">extends</span> <span class="identifier">HTMLElement</span> {
    <span class="keyword modifier">static</span> <span class="identifier">formAssociated</span> <span class="operator">=</span> <span class="keyword literal">true</span>;
    #<span class="identifier">internals</span>;
    #<span class="identifier">shadowRoot</span>;
    <span class="keyword">constructor</span>()
    {
        <span class="keyword">super</span>();
        <span class="keyword">this</span>.#<span class="identifier">internals</span> <span class="operator">=</span> <span class="keyword">this</span>.<span class="identifier">attachInternals</span>();
        <span class="keyword">this</span>.#<span class="identifier">shadowRoot</span> <span class="operator">=</span> <span class="keyword">this</span>.<span class="identifier">attachShadow</span>({<span class="identifier">mode</span><span class="operator">:</span> <span class="char">'closed'</span>, <span class="identifier">delegatesFocus</span><span class="operator">:</span> <span class="keyword literal">true</span>});
        <span class="keyword">this</span>.#<span class="identifier">shadowRoot</span>.<span class="identifier">innerHTML</span> <span class="operator">=</span> <span class="char">'&lt;input autofocus&gt;'</span>;
        <span class="keyword type">const</span> <span class="identifier">input</span> <span class="operator">=</span> <span class="keyword">this</span>.#<span class="identifier">shadowRoot</span>.<span class="identifier">firstChild</span>;
        <span class="identifier">input</span>.<span class="identifier">addEventListener</span>(<span class="char">'change'</span>, () <span class="operator">=</span><span class="operator">&gt;</span> {
            <span class="keyword">this</span>.#<span class="identifier">internals</span>.<span class="identifier">setFormValue</span>(<span class="identifier">input</span>.<span class="identifier">value</span>);
            <span class="keyword">this</span>.<span class="identifier">updateValidity</span>(<span class="identifier">input</span>.<span class="identifier">value</span>);
        });
    }
    <span class="identifier">updateValidity</span>(<span class="identifier">newValue</span>)
    {
        <span class="keyword control">if</span> (<span class="identifier">newValue</span>.<span class="identifier">length</span> <span class="operator">&gt;</span><span class="operator">=</span> <span class="number">2</span>) {
            <span class="keyword">this</span>.#<span class="identifier">internals</span>.<span class="identifier">setValidity</span>({ });
            <span class="keyword control">return</span>;
        }
        <span class="keyword">this</span>.#<span class="identifier">internals</span>.<span class="identifier">setValidity</span>({<span class="identifier">tooShort</span><span class="operator">:</span> <span class="keyword literal">true</span>}, 
            <span class="char">'value is too short'</span>, <span class="keyword">this</span>.#<span class="identifier">shadowRoot</span>.<span class="identifier">firstChild</span>);
        <span class="keyword">this</span>.#<span class="identifier">internals</span>.<span class="identifier">reportValidity</span>();
    }
}
<span class="identifier">customElements</span>.<span class="identifier">define</span>(<span class="char">'some-text-field'</span>, <span class="identifier">SomeTextFieldElement</span>);
</code></pre>
<p>With this setup, <code>:invalid</code> pseudo class will automatically apply to the element when the number of characters user typed is less than 2.</p>
<h2>Form-Associated Custom Element Callbacks</h2>
<p>In addition, form-associated custom elements provide the following set of new custom element reaction callbacks:</p>
<ul>
<li><code>formAssociatedCallback(form)</code> – Called when the associated form element changes to <code>form</code>. <code>ElementInternals.form</code> returns the associated from element.</li>
<li><code>formResetCallback()</code> – Called when the form is being reset. (e.g. user pressed <code>input[type=reset]</code> button). Custom element should clear whatever value set by the user.</li>
<li><code>formDisabledCallback(isDisabled)</code> – Called when the disabled state of the element changes.</li>
<li><code>formStateRestoreCallback(state, reason)</code> – Called when the browser is trying to restore element’s state to <code>state</code> in which case <code>reason</code> is “restore”, or when the browser is trying to fulfill autofill on behalf of user in which case <code>reason</code> is “autocomplete”. In the case of “restore”, <code>state</code> is a string, <code>File</code>, or <code>FormData</code> object previously set as the second argument to <code>setFormValue</code>.</li>
</ul>
<p>Let’s take a look at <code>formStateRestoreCallback</code> as an example. In the following example, we store <code>input.value</code> as state whenever the value of input element inside the shadow tree changes (second argument to <code>setFormValue</code>). When the user navigates away to some other page and comes back to this page, browser can restore this state via <code>formStateRestoreCallback</code>. Note that WebKit currently has a limitation that only string can be used for the state, and  “autocomplete” is not supported yet.</p>
<pre><code class="js"><span class="keyword type">class</span> <span class="identifier">SomeTextFieldElement</span> <span class="keyword modifier">extends</span> <span class="identifier">HTMLElement</span> {
    <span class="keyword modifier">static</span> <span class="identifier">formAssociated</span> <span class="operator">=</span> <span class="keyword literal">true</span>;
    #<span class="identifier">internals</span>;
    #<span class="identifier">shadowRoot</span>;
    <span class="keyword">constructor</span>()
    {
        <span class="keyword">super</span>();
        <span class="keyword">this</span>.#<span class="identifier">internals</span> <span class="operator">=</span> <span class="keyword">this</span>.<span class="identifier">attachInternals</span>();
        <span class="keyword">this</span>.#<span class="identifier">shadowRoot</span> <span class="operator">=</span> <span class="keyword">this</span>.<span class="identifier">attachShadow</span>({<span class="identifier">mode</span><span class="operator">:</span> <span class="char">'closed'</span>, <span class="identifier">delegatesFocus</span><span class="operator">:</span> <span class="keyword literal">true</span>});
        <span class="keyword">this</span>.#<span class="identifier">shadowRoot</span>.<span class="identifier">innerHTML</span> <span class="operator">=</span> <span class="char">'&lt;input autofocus&gt;'</span>;
        <span class="keyword type">const</span> <span class="identifier">input</span> <span class="operator">=</span> <span class="keyword">this</span>.#<span class="identifier">shadowRoot</span>.<span class="identifier">querySelector</span>(<span class="char">'input'</span>);
        <span class="identifier">input</span>.<span class="identifier">addEventListener</span>(<span class="char">'change'</span>, () <span class="operator">=</span><span class="operator">&gt;</span> {
            <span class="keyword">this</span>.#<span class="identifier">internals</span>.<span class="identifier">setFormValue</span>(<span class="identifier">input</span>.<span class="identifier">value</span>, <span class="identifier">input</span>.<span class="identifier">value</span>);
        });
    }
    <span class="identifier">formStateRestoreCallback</span>(<span class="identifier">state</span>, <span class="identifier">reason</span>)
    {
        <span class="keyword">this</span>.#<span class="identifier">shadowRoot</span>.<span class="identifier">querySelector</span>(<span class="char">'input'</span>).<span class="identifier">value</span> <span class="operator">=</span> <span class="identifier">state</span>;
    }
}
<span class="identifier">customElements</span>.<span class="identifier">define</span>(<span class="char">'some-text-field'</span>, <span class="identifier">SomeTextFieldElement</span>);
</code></pre>
<p>In summary, <code>ElementInternals</code> and form-associated custom elements provide an exciting new way of writing reusable component that participates in form submission and validation. <code>ElementInternals</code> also provides the ability to specify the default value of ARIA role and other ARIA properties for a custom element. We’re excited to bring these features together to web developers.</p>
]]></description>
      <pubDate>Mon, 06 Feb 2023 20:51:56 +0000</pubDate>
      <link>https://webkit.org/blog/13711/elementinternals-and-form-associated-custom-elements/</link>
      <dc:creator>WebKit</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/3986254111</guid>
    </item>
    <item>
      <title><![CDATA[Nothing’s Bulletproof]]></title>
      <description><![CDATA[<p><a href="https://tylergaw.com/">Tyler</a> is a super smart web designer. I’ve known him for a long time and have always admired his work. I mean, just look at the fun on his home page:</p>
<p class="image-container"><img src="https://cdn.jim-nielsen.com/blog/2023/tyler-gaw-website.gif" width="396" height="472" alt="Animation showing the squiggling line and rotating circle on tylergaw.com">

</p><p>So when <a href="https://mastodon.social/@tylergaw/109773405716485185">he tagged me on Mastodon</a> asking about an AVIF/iOS bug — which I was not familiar with — I was super intrigued. Here’s Tyler:</p>
<blockquote>
<p>iOS 16 added support avif images, but seems like there could be bugs? Seeing support for the image/avif image type, so avif version loads, but then displays as a broken image. <a href="https://tests.caniuse.com/avif">https://tests.caniuse.com/avif</a> </p>
</blockquote>
<p>What could it be?</p>
<p>I went down <a href="https://blog.jim-nielsen.com/2019/down-the-rabbit-hole-of-image-optimization-tooling/">a rabbit hole of image optimization</a> a while back, trying to decide if I should support newer image formats on my <a href="https://www.iosicongallery.com">icon</a> <a href="https://www.macosicongallery.com">gallery</a> <a href="https://www.watchosicongallery.com">sites</a>. The one takeaway I remember was this: AVIF seemed incredibly promising in its capability to preserve quality while drastically decreasing file size. However, browser support at the time was not great.</p>
<p>Fast-forward to 2023 and I still haven’t seriously considered revisiting AVIF, though I know <a href="https://www.coywolf.news/webdev/safari-supports-avif-in-macos-ventura-and-ios-16/">support has been broadening</a>.</p>
<p>So hearing what Tyler had to say about his own mileage with AVIF piqued my interest. In general, I like to let other people smarter than me bleed on the edge. Then I catch the second wave and learn from all their cuts and bruises.</p>
<p>Thankfully, Tyler <a href="https://tylergaw.com/blog/ios-16-avif-fix/">blogged about what he found out</a>. What struck me most in his post was how proper he served his images yet still had a nasty bug.</p>
<blockquote>
<p>I've been using AVIF images throughout this site since about 2020. I use the <code>source</code> element with <code>srcset</code> to provide AVIF, WEBP, and JPG or PNG formats for most images. This worked until iOS shipped support for AVIF. Before then, iOS would just not recognize the AVIF format and use WEBP instead.</p>
<p>With AVIF support, iOS started recognizing and loading AVIF images…[but] there would be a missing image. Either blank, in Chrome iOS, or the little blue question mark in Safari iOS.</p>
</blockquote>
<p>It seems like he did all the right things: multiple image formats (old and new) with semantic markup allowing the client to choose the best supported experience. Yet he still had issues — on a modern browser nonetheless.</p>
<p>The beautiful theory of new image formats like AVIF is that you can leverage HTML to provide a baseline experience that works across the most clients possible (using an old format like PNG) and then, for clients that support it, provide additional markup for the new fancy formats of the present, like WEBP or AVIF.</p>
<pre><code class="language language-html"><span class="hljs-tag">&lt;<span class="hljs-name">picture</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">source</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"image/avif"</span> <span class="hljs-attr">srcset</span>=<span class="hljs-string">"…"</span> /&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">source</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"image/webp"</span> <span class="hljs-attr">srcset</span>=<span class="hljs-string">"…"</span> /&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"….png"</span> <span class="hljs-attr">srcset</span>=<span class="hljs-string">"…"</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">"…"</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">picture</span>&gt;</span>
</code></pre>
<p>In this way, modern clients that support a new format like AVIF can fetch those resources and ignore the others while older browsers will just fetch the PNG image.</p>
<p>In Tyler’s case, he did everything right but the modern clients were still failing. They were looking at the HTML and saying, “Oh yeah, we support AVIF. Let’s fetch that!” But then the AVIF images were failing to render in the browser.</p>
<p>Why? Here’s Tyler:</p>
<blockquote>
<p>TL;DR: [You’ll have to] re-encode any AVIF images that aren't working. There have been AVIF spec changes and there were bugs in popular encoders that caused AVIF not to work in iOS.</p>
</blockquote>
<p>I was a little surprised when I first saw this was the problem. <a href="https://mastodon.social/@jimniels/109773680963077592">I joked</a> that, “images are software now. you have to update (e.g. re-encode) between major versions or things break.”</p>
<p>But in all seriousness, there’s a good reminder in Tyler’s experience for me.</p>
<p>To keep things from breaking on the web, the idea is to build with new technologies (e.g. client APIs or image formats) by layering experiences on top of each other. Start with a baseline and enhance. If you build this way, you can watch support become more ubiquitous and not have to change a thing (or at least very little).</p>
<p>However, as Tyler’s example shows, just because you layer in a new technology doesn’t mean it’ll work for any browser that adopts support for it. Burgeoning technologies change constantly and even layered experiences can break. Not because the experience isn’t supported. On the contrary, it very well might be supported, just in an earlier, non-compatible form.</p>
<p>Resiliency is important, but it’s not being bullet proof. Nothing’s bullet proof, even with modern clients. </p>

    <hr>
    <ul>
      <li>
        Reply via:
        
        <a href="mailto:jimniels%2Bblog@gmail.com?subject=Re:%20blog.jim-nielsen.com/2023/nothings-bulletproof/">Email</a>, <a href="https://twitter.com/jimniels">Twitter</a>,
        <a href="https://mastodon.social/@jimniels">Mastodon</a>
      </li>
      
      
    </ul>
    <br>
  ]]></description>
      <pubDate>Sun, 29 Jan 2023 19:00:00 +0000</pubDate>
      <link>https://blog.jim-nielsen.com/2023/nothings-bulletproof/</link>
      <dc:creator>Jim Nielsen’s Blog</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/3975387723</guid>
    </item>
    <item>
      <title><![CDATA[Using AI to appease Jest]]></title>
      <description><![CDATA[<p>Friday evening at 5pm on my way out of work I was about to push a feature fix up to add some encryption and decryption using <code>crypto-js</code>.  The API looks like this:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="js"><span class="k">import</span> <span class="nx">CryptoJS</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">crypto-js</span><span class="dl">'</span>

<span class="kd">const</span> <span class="nx">encryptedThing</span> <span class="o">=</span> <span class="nx">CryptoJS</span><span class="p">.</span><span class="nx">AES</span><span class="p">.</span><span class="nx">encrypt</span><span class="p">(</span><span class="dl">"</span><span class="s2">message</span><span class="dl">"</span><span class="p">,</span> <span class="nx">SECRET_KEY</span><span class="p">).</span><span class="nx">toString</span><span class="p">()</span>
<span class="kd">const</span> <span class="nx">decryptedThing</span> <span class="o">=</span> <span class="nx">CryptoJS</span><span class="p">.</span><span class="nx">AES</span><span class="p">.</span><span class="nx">decrypt</span><span class="p">(</span><span class="nx">encryptedThing</span><span class="p">,</span> <span class="nx">SECRET_KEY</span><span class="p">).</span><span class="nx">toString</span><span class="p">(</span><span class="nx">CryptoJS</span><span class="p">.</span><span class="nx">enc</span><span class="p">.</span><span class="nx">Utf8</span><span class="p">)</span>
</code></pre></div></div>
<p>It’s an AES cipher block that puts a message in a cipher, and decrypts it on the other end. Cool. It uses math, like popular cryptographic technology Bitcoin, so you know it’s good.</p>
<p>As I was ready to push the code up, my tests were failing in the <code>prepush</code> hook. I knew the code was working but I’d have to appease Jest. Jest’s issue was it didn’t know about the <code>import CryptoJS from 'crypto-js'</code> part because it only understands <code>require()</code>. I’ve dealt with this problem before. I had some other module mocks in that file, so I copied them verbatim.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="js"><span class="c1">// This didn't work.</span>
<span class="kd">const</span> <span class="nx">mockAESInstance</span> <span class="o">=</span> <span class="p">{</span>
	<span class="na">decrypt</span><span class="p">:</span> <span class="nx">jest</span><span class="p">.</span><span class="nx">fn</span><span class="p">().</span><span class="nx">mockReturnThis</span><span class="p">(),</span>
<span class="p">}</span>

<span class="nx">jest</span><span class="p">.</span><span class="nx">mock</span><span class="p">(</span><span class="dl">'</span><span class="s1">crypto-js</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
	<span class="k">return</span> <span class="p">{</span>
		<span class="na">AES</span><span class="p">:</span> <span class="nx">jest</span><span class="p">.</span><span class="nx">fn</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="nx">mockAESInstance</span><span class="p">)</span>
		<span class="p">...</span>
	<span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>
<p>It didn’t work. Jest kept saying <code>decrypt is not a function</code>. I’ve also dealt with this problem before. So I tried…</p>
<ul>
<li><code>decrypt: jest.fn()</code> - surely this is a function? nope.</li>
<li><code>decrypt: jest.fn().mockReturnValue('decryptedThing')</code> = also not a function</li>
<li>And then I tried every combination based on every amalgamation of every StackOverflow answer I could find to mock a nested object function.</li>
</ul>
<p>…and it still didn’t work.</p>
<p>I used VS Code’s Intellisense to dig into the types inside CryptoJS. CryptoJS’s <code>AES.decrypt</code> returns a custom type called a <code>WordArray</code> that has a <code>toString()</code> method. Ah ha! Foolish me to think <code>toString()</code> was the native <code>Array.toString()</code> function! I tried mocking the <code>toString()</code> and <code>CryptoJS.enc.Utf8</code> in a variety of ways but couldn’t get Jest to mock a nested function of a nested object function…</p>
<h2>Fuck it, just use Copilot</h2>
<p>It was now 10pm. There was dinner. There was watching TV with the kids. There was bed time. But I was noodling on my laptop like a deadbeat dad because I needed Jest to allow this one line of code in my commit. In desperation, I deleted all my attempts and sent a prayer to the AI gods (who were unhelpful up to this point) and kept hitting tab… Copilot summoned this:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="js"><span class="c1">// Final Fix </span>
<span class="nx">jest</span><span class="p">.</span><span class="nx">mock</span><span class="p">(</span><span class="dl">'</span><span class="s1">crypto-js</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
	<span class="k">return</span> <span class="p">{</span>
		<span class="na">AES</span><span class="p">:</span> <span class="p">{</span>
			<span class="na">decrypt</span><span class="p">:</span> <span class="nx">jest</span><span class="p">.</span><span class="nx">fn</span><span class="p">().</span><span class="nx">mockReturnValue</span><span class="p">({</span>
				<span class="na">toString</span><span class="p">:</span> <span class="nx">jest</span><span class="p">.</span><span class="nx">fn</span><span class="p">().</span><span class="nx">mockReturnValue</span><span class="p">(</span><span class="dl">'</span><span class="s1">decryptedThing</span><span class="dl">'</span><span class="p">)</span>
			<span class="p">})</span>
		<span class="p">}</span>
		<span class="p">...</span>
	<span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>
<p>And it worked! Looking at the final answer, I can see how this appeases Jest, but I ashamedly admit that I would have never got there on my own. A double <code>fn().mockReturnValue()</code>. This situation is an embodiment of my biggest frustration with Jest. The ESM imports thing is big and makes me want to switch to <a href="https://vitest.dev/">Vitest</a> today, but the biggest frustration is that you spend so much time and energy mocking away Jest’s deficiencies.</p>
<p>Anyways, thanks Copilot for solving the problem my powerless human brain could never answer. In <a href="https://daverupert.com/2022/08/github-copilot/">my review of Copilot</a> last year I mentioned using it for writing tests and it’s still proving valuable there. It’s solving the “fear of the blank page” problem. With a little coaxing, I’m able to get 100% coverage on my components for props and conditionals. Copilot seems okay at that. Little less great at mocks, but it can do it. Sometimes the robot is wrong though, so you have pay attention to what the machine is spitting out.</p>
<p>If you’re on the fence about Copilot, I’d recommend it for the sole purpose of having someone else around who will throw spaghetti when you’re tired of throwing spaghetti at the wall. It makes the job a little easier and can unblock you.</p>
]]></description>
      <pubDate>Fri, 27 Jan 2023 15:48:00 +0000</pubDate>
      <link>https://daverupert.com/2023/01/using-ai-to-appease-jest/</link>
      <dc:creator>daverupert.com</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/3972390877</guid>
    </item>
    <item>
      <title><![CDATA[Out of Sight]]></title>
      <description><![CDATA[
        <p><iframe width="560" height="315" src="https://www.youtube.com/embed/4qCbiCxBd2M" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe></p>

<p>After her guide dog runs off after a thief who takes her bag, a girl navigates a world guided by her powerful senses of touch, hearing, and smell. Super charming and inventive. From the YouTube comments:</p>

<blockquote><p>For those who don’t know, blind people will clap their hands and listen for the echo to get a sense of how big a space they are in, if it’s wide open or a tightly enclosed space.</p></blockquote>

<p>(via <a href="https://www.peterme.com/2022/12/29/youtube-2022-reflections-part-4-stuff-i-watched-with-my-11yo-daughter/">peterme</a>)</p>

 <strong>Tags:</strong> <a href="https://kottke.org/tag/blind">blind</a>&nbsp;&nbsp; <a href="https://kottke.org/tag/video">video</a>
    ]]></description>
      <pubDate>Wed, 04 Jan 2023 23:46:47 +0000</pubDate>
      <link>https://kottke.org/23/01/out-of-sight</link>
      <dc:creator>Starred Articles</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/3972287287</guid>
    </item>
    <item>
      <title><![CDATA[Ryan Florence @ryanflorence]]></title>
      <description><![CDATA[  <div class="feedbin--wrap">
    <p class="feedbin--content-text">Hype warning, but I think HMR in Remix is going to be best implementation I've ever used.

We're able to do typical HMR when only rendering code changes, but if server code changes too we revalidate the data before applying HMR, so rendering + server code update the UI together</p>
  </div>
]]></description>
      <pubDate>Sat, 28 Jan 2023 15:08:57 +0000</pubDate>
      <link>https://twitter.com/ryanflorence/status/1619351833444782083</link>
      <dc:creator>@ryanflorence</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/3973648119</guid>
    </item>
    <item>
      <title><![CDATA[Working with Refresh Tokens in Remix]]></title>
      <description><![CDATA[<article><p>When using an external API, you may need to keep an access token to send a request as a user. And a refresh token to get a new access token once the access token expires.</p><p>In a SPA, you can create a wrapper for your Fetch. Suppose the request is rejected because of an expired access token. In that case, you can refresh it, update your access and refresh token, and try the request again with the new one. From that moment, all future requests will use the new access token.</p><p>But what about Remix? What happens if your access token is used inside a loader? You probably stored both tokens in the session, so you need to commit the session to update it, and if more than one loader is running, they may all find the expired token.</p><p>To solve this in loaders, we can do a simple trick, refresh the token and redirect to the same URL that will trigger the same loaders. It will update the tokens in the session, so the new request will come with the updated tokens after the redirect.</p><p>For actions, it's trickier because you can't redirect and generate a new POST. At the same time, you know that only one action function will be called per request, so you could refresh the token, get the tokens back and set a cookie with the new one.</p><p>Let's say how we could create an <code>authenticate</code> function to do that.</p><pre>// our authenticate function receives the Request, the Session and a Headers
// we make the headers optional so loaders don't need to pass one
async function authenticate(
  request: Request,
  session: Session,
  headers = new Headers()
) {
  try {
    // get the auth data from the session
    let accessToken = session.get("accessToken");

    // if not found, redirect to login, this means the user is not even logged-in
    if (!accessToken) throw redirect("/login");

    // if expired throw an error (we can extends Error to create this)
    if (new Date(session.get("expirationDate")) &lt; new Date()) {
      throw new AuthorizationError("Expired");
    }

    // if not expired, return the access token
    return accessToken;
  } catch (error) {
    // here, check if the error is an AuthorizationError (the one we throw above)
    if (error instanceof AuthorizationError) {
      // refresh the token somehow, this depends on the API you are using
      let { accessToken, refreshToken, expirationDate } = await refreshToken(
        session.get("refreshToken")
      );

      // update the session with the new values
      session.set("accessToken", accessToken);
      session.set("refreshToken", refreshToken);
      session.set("expirationDate", expirationDate);

      // commit the session and append the Set-Cookie header
      headers.append("Set-Cookie", await commitSession(session));

      // redirect to the same URL if the request was a GET (loader)
      if (request.method === "GET") throw redirect(request.url, { headers });

      // return the access token so you can use it in your action
      return accessToken;
    }

    // throw again any unexpected error that could've happened
    throw error;
  }
}
</pre><p>Now, we can define a loader function like this:</p><pre>export let loader: LoaderFunction = async ({ request }) =&gt; {
  // read the session
  let session = await getSession(request);
  // authenticate the request and get the accessToken back
  let accessToken = await authenticate(request, session);
  // do something with the token
  let data = await getSomeData(accessToken);
  // and return the response
  return json(data);
};
</pre><p>And our action functions will be similar:</p><pre>export let action: ActionFunction = async ({ request }) =&gt; {
  // also read the session
  let session = await getSession(request);
  // but create a headers object
  let headers = new Headers();
  // authenticate the request and get the accessToken back, this will be the
  // already saved token or the refreshed one, in that case the headers above
  // will have the Set-Cookie header appended
  let accessToken = await authenticate(request, session, headers);
  // do something with the token
  let data = await getSomeData(accessToken);
  // and return the response passing the headers so we update the cookie
  return json(data, { headers });
};
</pre><p>And that's all. Our loader/action functions will be able to refresh the token and use the new one. In the loader case, a redirect will happen hidden entirely from our code. We don't need to think about it at all.</p><p>Note this may change once Remix supports pre/post request hooks, so we could do the auth check in a pre-request hook and do it once.</p><p>Another option if you use Express is to move this to the Express request handler in your server code. That will run before Remix, which can happen before all your loaders and actions run in a single request.</p></article>]]></description>
      <pubDate>Fri, 27 Jan 2023 09:30:07 +0000</pubDate>
      <link>https://sergiodxa.com/articles/working-with-refresh-tokens-in-remix</link>
      <dc:creator>Pages</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/3971935860</guid>
    </item>
    <item>
      <title><![CDATA[The Best Time to Own a Domain Was 20 Years Ago; The Second Best Time Is Today]]></title>
      <description><![CDATA[<p>Mastodon has a verification system built on the humble <code>rel=me</code> which gained coverage on <a href="https://themarkup.org/levelup/2022/12/22/how-we-verified-ourselves-on-mastodon-and-how-you-can-too">The Markup</a> (found via <a href="https://adactio.com/links/19783">adactio links</a>). It allows you to verify that your Mastodon profile controls a particular domain (e.g. <a href="https://mastodon.social/@jimniels">I have verified</a> <code>jim-nielsen.com</code>, something nobody else in the world can claim to do without my consent). From the article:</p>
<blockquote>
<p>The checkmark on a Mastodon account is only as credible as the website it references and the Mastodon server that displays the account.</p>
</blockquote>
<p>I think <code>rel=me</code> is such a neat <a href="https://indieweb.org/rel-me">idea</a>. It’s a very “webby” way of doing verification on the internet because it leverages bedrock internet technology (e.g. links) and institutions (e.g. domains and ICANN) to address the problem of identity verification (as opposed to, say, <a href="https://help.twitter.com/en/managing-your-account/legacy-verification-policy">the shifting, fleeting policies of a for-profit corporation</a>).</p>
<p><code>rel=me</code> powerfully reinforces the importance and value of owning a domain in the digital age.</p>
<p>For example: imagine for a moment the value of <code>google.com</code>. What would that domain cost? People’s entire personal and professional lives — email, photos, calendar, documents, etc. — built up over years and years, solely accessible at that domain.</p>
<p>Now imagine someone who invested years of their life creating value for others and amassing a following on Twitter. They now face a dilemma: all that value lives at a URL they can’t control <code>twitter.com/{username}</code>. Even the word-of-mouth username is something they have zero control over.</p>
<ul>
<li>Person 1: “Hey, you should checkout @insightful608 —&nbsp;they’re great!”</li>
<li>Person 2: “Ah man, didn’t you hear? Twitter is down. Who is @insightful608?”</li>
<li>Person 3: “Oh, I don’t know. Let me see if I can find them elsewhere on the internet…”</li>
</ul>
<p>However, if own your domain, create value there, and drive people to it, you’re paying ~$10 a year to build unbounded value over the years — value <em>you control</em>.</p>
<p>That is why owning a domain (and publishing your content there) is like planting a tree: it’s value that starts small and grows. The best time to own a domain and publish your content there was 20 years ago. The second best time is today.</p>
<p>This is the what I love about <code>rel=me</code>: it reinforces the value of owning a domain that people respect and <em>trust</em> because, through the years, it becomes a source of quality information. In an internet connected world, domains are a currency of value. Do you want to own your value, or let someone else own it?</p>
<blockquote>
<p>[verification with <code>rel=me</code>] is based on a loose specification created by bloggers back in 2003 called Xhtml Friends Network. Just as with Mastodon, its intention is not to tell us “this account belongs to television host Stephen Fry” but rather “this Stephen Fry account was vouched for by stephenfry.com, the website.”</p>
</blockquote>
<p>I love a technology like <code>rel=me</code> which pushes the idea of domain ownership into broader and broader spheres of society — “How do I get that nice little green checkmark on my profile?” It reinforces the value of owning your own domain (which you can verify elsewhere) and encourages citizens of the internet people to build value in their own little corners of the world wide web.</p>
<p>After all, <a href="https://blog.jim-nielsen.com/2023/subscribe-wherever-you-get-your-content/">domains are the OG handles of the internet</a>: find me <a href="https://jim-nielsen.com">@jim-nielsen.com</a>.</p>
<p>What domain do you want vouching for you?</p>

    <hr>
    <ul>
      <li>
        Reply via:
        
        <a href="mailto:jimniels%2Bblog@gmail.com?subject=Re:%20blog.jim-nielsen.com/2023/best-time-to-own-a-domain/">Email</a>, <a href="https://twitter.com/jimniels">Twitter</a>,
        <a href="https://mastodon.social/@jimniels">Mastodon</a>
      </li>
      
      
    </ul>
    <br>
  ]]></description>
      <pubDate>Tue, 24 Jan 2023 15:50:43 +0000</pubDate>
      <link>https://blog.jim-nielsen.com/2023/best-time-to-own-a-domain/</link>
      <dc:creator>Jim Nielsen’s Blog</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/3967809873</guid>
    </item>
    <item>
      <title><![CDATA[A Short, Totally Unofficial, History of Port Numbers in Web Development]]></title>
      <description><![CDATA[<p>As I go through my daily work in web dev, I constantly have questions bouncing around in my head that are too low of a priority to really research. But, when they bounce around in my head long enough, my curiosity won’t let me sit idly by.</p>
<p>One such bouncing question deals with port numbers. It seems like every time I run  <code>npm start</code> on a web project, it’s a roll of the dice as to which port of <code>localhost</code> I’ll get a local dev server on.</p>
<p>I understand perfect uniformity across all projects is unlikely (not everyone will be on port <code>8000</code>), but it’s also not completely random across the entire spectrum of thousands of ports (I never get a local dev server on <code>4971</code> or <code>9571</code> or <code>6373</code>).</p>
<p>Why does the local dev server port always seem to be a random selection between about four to five ports?</p>
<p><a href="https://twitter.com/jimniels/status/1598436595421663237">I asked on Twitter</a>:</p>
<blockquote>
<p>Does somebody have an article like "A short history of port numbers" that describes why I never get a consistent default port experience across tools? Some commons ones I consistently see:</p>
<p>localhost<br>localhost:3000<br>localhost:8000<br>localhost:8080<br>localhost:8888</p>
</blockquote>
<p>And further:</p>
<blockquote>
<p>why/where did those numbers pop into our collective culture? why not 2000 or 4000? or why not 6666? or any other random number for that matter?</p>
</blockquote>
<p>To be clear, this post is not meant to be the definitive answer to that question. But I read enough random links on the internet to arrive at what feels like a satisfactory answer (though I can’t vouch for the accuracy of it).</p>
<p>So I will try re-stating what I found across various resources on the web and see if I can weave them all into a coherent narrative.</p>
<h2 id="ports-80">Ports 80**</h2>
<p>Ports 0-1023 are considered “system ports” (a.k.a “well-known ports” or “privileged ports”). They are reserved by the system for specific applications.</p>
<p>For example, the following ports are reserved by the system for well-known services:</p>
<ul>
<li><code>21</code> FTP</li>
<li><code>25</code> SMTP</li>
<li><code>80</code> HTTP</li>
<li><code>443</code> HTTPS</li>
</ul>
<p>But why <code>80</code> and <code>443</code> for HTTP and HTTPS? Why not <code>51</code> and <code>666</code>?</p>
<p><a href="https://www.howtogeek.com/233383/why-was-80-chosen-as-the-default-http-port-and-443-as-the-default-https-port/">It appears</a> that IANA (a department of ICANN) published RFC1060 in 1990 listing all well-known ports at that point in time and port 80 was unclaimed. When Tim Burners-Lee issued the first version of HTTP in 1991, he used port 80 for HTTP. Later, in 1992, RFC1060 was reissued and port 80 was claimed by HTTP. As for <code>443</code>, its history is less clear but it officially showed up as claimed by HTTPS in an RFC in 1994.</p>
<p>(<strong>Update</strong>: <a href="https://adactio.com/journal/16531">turns out</a>, 2784 was the port number for the web before 80 was — thank you Jeremy!)</p>
<p>Ok so, an HTTP server runs on port <code>80</code>. But what if you’re doing local development? The standard HTTP port <code>80</code> is privileged and requires <code>root</code> access (which would be a hassle to provide every time you start the server). Plus, you don’t want your development server interfering with other running processes on your machine. So what you need is an alternative port number for your local dev server that is above 1024.</p>
<p>(<strong>Update:</strong> turns out <a href="https://developer.apple.com/forums/thread/674179">macOS no longer enforces privileged ports</a> (<a href="https://news.ycombinator.com/item?id=18302380">Hacker News thread</a>) — thank you <a href="https://mastodon.social/@carlmjohnson/109740040734781379">Carl Johnson</a>.)</p>
<p>What number do you choose? Well, I’m going to guess that people just wanted a number that was easy to remember so they went with variants of the reserved HTTP port (<code>80</code>) which is how we got conventions for local HTTP servers to run on ports like <code>8000</code>, <code>8080</code>, <code>8888</code> and <code>8008</code><sup id="fnref:1"><a href="https://blog.jim-nielsen.com/2023/short-history-of-port-numbers/#fn:1">[1]</a></sup>. </p>
<p>It appears that lots of services (and malware) were built on top of these well-known HTTP alternative ports. For example:</p>
<ul>
<li><a href="https://www.speedguide.net/port.php?port=8000">Port <code>8000</code></a>: was used by VmWare, VMotion, AWS Local DynamoDB, Canon Management Console, Django Dev Server, and Winamp Audio Streaming — but also by malware and spybots.</li>
<li><a href="https://www.speedguide.net/port.php?port=8080">Port <code>8080</code></a> was used by Unreal Tournament and Microsoft Lync — but also by trojans and backdoor worm exploits.</li>
<li><a href="https://www.speedguide.net/port.php?port=8888">Port <code>8888</code></a> was used by MAMP — but also, you get it, more malware.</li>
</ul>
<h2 id="ports-30">Ports 30**</h2>
<p>So what about port <code>3000</code>? That doesn’t even have an eight in it anywhere. Where did that number come from?</p>
<p>I’m personally aware of the prevelance of port <code>3000</code> because of its ubiquity in the Node ecosystem. But from what I gather, Ruby (which predates Node) also uses <code>3000</code> prevalently (and, as you probably guessed, <a href="https://www.speedguide.net/port.php?port=3000">malware uses it too</a>). Ultimately, I cannot find a story behind that port.<sup id="fnref:2"><a href="https://blog.jim-nielsen.com/2023/short-history-of-port-numbers/#fn:2">[2]</a></sup></p>
<p>That said, once a project like Node does something you can imagine how it spreads: for whatever reason, somebody starts using port <code>3000</code> in code or docs or examples, and that spreads to become the standard port for starting an HTTP server in Node. From there the number spread like wildfire into the community and tooling (which would explain why the first place I remember seeing port <code>3000</code> used in a dev server was when I first used <code>create-react-app</code>).</p>
<h2 id="conclusion">Conclusion</h2>
<p>Phew. I think I have sufficiently answered my own question to a degree of satisfaction where my curiosity will let me rest.</p>
<p>Some additional links from my research:</p>
<ul>
<li><a href="https://www.quora.com/Whats-the-story-behind-port-8080">“What's the story behind port 8080?” on Quora</a></li>
<li><a href="https://www.quora.com/Why-is-port-3000-used-when-running-a-Node-js-application">“Why is port 3000 used when running a Node.js application?” on Quora</a></li>
<li><a href="https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers">“List of TCP and UDP port numbers” on Wikipedia</a></li>
<li><a href="https://en.wikipedia.org/wiki/Registered_port">“Registered port” on Wikipedia</a></li>
<li><a href="https://www.w3.org/Daemon/User/Installation/PrivilegedPorts.html">“Priviliged ports” on w3.org</a></li>
<li><a href="https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml?page=109">“Service Name and Transport Protocol Port Number Registry” on iana.org</a></li>
<li><a href="https://www.grc.com/port_8080.htm">“Port 8080” in the Port Authority Database (from the Gibson Research Corporation)</a></li>
<li><a href="https://blog.cloudflare.com/the-history-of-the-url/">“The History of the URL” on The Cloudflare Blog</a></li>
</ul>
<hr><ol class="footnotes"><li id="fn:1">IANA is responsible for resources related to internet protocols and it appears you can register (i.e. reserve) commonly used port numbers for well-known services. For example, <code>8008</code>, <code>8080</code>, and <code>591</code> are all considered HTTP alternatives, a.k.a. <a href="https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml?search=http-alt"><code>http-alt</code></a>. <code>591</code> seems like a random outlier, but it appears to be because <a href="https://www.speedguide.net/port.php?port=591">FileMaker chose to use it</a> as an alternative port (and <a href="https://community.claris.com/en/s/article/Specifying-a-port-number-for-Instant-Web-Publishing-1503692906237">registered it</a> as one). <a href="https://blog.jim-nielsen.com/2023/short-history-of-port-numbers/#fnref:1" title="Jump back to footnote 1 in the text.">↩</a></li><li id="fn:2"><a href="https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml?search=3000">Port 3000 is registered with IANA</a> for “RemoteWare Client” and “HBCI”. If you Google those names with the people who registered them, the furthest convo back I can find is an obscure mailing list text that sheds no light on the subject. So who knows where <code>3000</code> came from. <a href="https://blog.jim-nielsen.com/2023/short-history-of-port-numbers/#fnref:2" title="Jump back to footnote 2 in the text.">↩</a></li></ol>
    <hr>
    <ul>
      <li>
        Reply via:
        
        <a href="mailto:jimniels%2Bblog@gmail.com?subject=Re:%20blog.jim-nielsen.com/2023/short-history-of-port-numbers/">Email</a>, <a href="https://twitter.com/jimniels">Twitter</a>,
        <a href="https://mastodon.social/@jimniels">Mastodon</a>
      </li>
      
      
    </ul>
    <br>
  ]]></description>
      <pubDate>Mon, 23 Jan 2023 15:20:44 +0000</pubDate>
      <link>https://blog.jim-nielsen.com/2023/short-history-of-port-numbers/</link>
      <dc:creator>Jim Nielsen’s Blog</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/3966260818</guid>
    </item>
    <item>
      <title><![CDATA[The gotcha of unhandled promise rejections]]></title>
      <description><![CDATA[<p>Let's say you wanted to display a bunch of chapters on the page, and for whatever reason, the API only gives you a chapter at a time. You could do this:</p>
<div class="code-example"><pre class="language-js"><code><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">showChapters</span><span class="token punctuation">(</span><span class="token parameter">chapterURLs</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> url <span class="token keyword">of</span> chapterURLs<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> response <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fetch</span><span class="token punctuation">(</span>url<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> chapterData <span class="token operator">=</span> <span class="token keyword">await</span> response<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token function">appendChapter</span><span class="token punctuation">(</span>chapterData<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span></code></pre></div><p>This gives the correct result – all the chapters appear in the right order. But, it's kinda slow, because it waits for each chapter to finish fetching before it tries to fetch the next one.</p>
<p>Alternatively, you could do the fetches in parallel:</p>
<div class="code-example"><pre class="language-js"><code><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">showChapters</span><span class="token punctuation">(</span><span class="token parameter">chapterURLs</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> chapterPromises <span class="token operator">=</span> chapterURLs<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">url</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> response <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fetch</span><span class="token punctuation">(</span>url<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> response<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">const</span> chapters <span class="token operator">=</span> <span class="token keyword">await</span> Promise<span class="token punctuation">.</span><span class="token function">all</span><span class="token punctuation">(</span>chapterPromises<span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> chapterData <span class="token keyword">of</span> chapters<span class="token punctuation">)</span> <span class="token function">appendChapter</span><span class="token punctuation">(</span>chapterData<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre></div><p>Great! Except, you're now waiting on the last chapter before showing the first.</p>
<p>For the best performance, do the fetches in parallel, but handle them in sequence:</p>
<div class="code-example"><pre class="language-js"><code><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">showChapters</span><span class="token punctuation">(</span><span class="token parameter">chapterURLs</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> chapterPromises <span class="token operator">=</span> chapterURLs<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">url</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> response <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fetch</span><span class="token punctuation">(</span>url<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> response<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">for</span> <span class="token keyword">await</span> <span class="token punctuation">(</span><span class="token keyword">const</span> chapterData <span class="token keyword">of</span> chapterPromises<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token function">appendChapter</span><span class="token punctuation">(</span>chapterData<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span></code></pre></div><p>However, this has introduced a tricky bug involving unhandled promise rejections.</p>
<h2 id="unhandled-promise-rejections"><a href="https://jakearchibald.com/2023/unhandled-rejections/#unhandled-promise-rejections">Unhandled promise rejections</a></h2>
<p>Unhandled promise rejections happen when a promise… is rejected… but isn't handled.</p>
<p>Ok ok, they're like the promise equivalent of an uncaught error. Like this:</p>
<div class="code-example"><pre class="language-js"><code><span class="token keyword">const</span> promise <span class="token operator">=</span> Promise<span class="token punctuation">.</span><span class="token function">reject</span><span class="token punctuation">(</span><span class="token function">Error</span><span class="token punctuation">(</span><span class="token string">'BAD'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div><p>The rejected state of this promise is 'unhandled' because nothing is dealing with the rejection.</p>
<p>Here are a few ways it could be handled:</p>
<div class="code-example"><pre class="language-js"><code><span class="token comment">// Like this:</span>
<span class="token keyword">try</span> <span class="token punctuation">{</span>
  <span class="token keyword">await</span> promise<span class="token punctuation">;</span>
<span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token comment">// …</span>
<span class="token punctuation">}</span>

<span class="token comment">// Or this:</span>
promise<span class="token punctuation">.</span><span class="token function">catch</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token comment">// …</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">// Or even just this:</span>
<span class="token keyword">await</span> promise<span class="token punctuation">;</span>
<span class="token comment">// In this case the promise is handled,</span>
<span class="token comment">// but a rejection will be turned into a throw.</span>

<span class="token comment">// Including this:</span>
promise<span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token comment">// …</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// …although, since this doesn't handle the rejected case,</span>
<span class="token comment">// it returns a new promise that's also rejected,</span>
<span class="token comment">// and that new promise will be unhandled (all new promises are unhandled).</span></code></pre></div><p>A promise is handled when something is done in reaction to that promise, even if it's creating another rejected promise, or turning a rejected promise into a throw.</p>
<p>Once a promise is rejected, you have until just-after the next processing of microtasks to handle that rejection, else it may count as an unhandled rejection ('may', because there's a little bit of wiggle room with task queuing).</p>
<div class="code-example"><pre class="language-js"><code><span class="token keyword">const</span> promise <span class="token operator">=</span> Promise<span class="token punctuation">.</span><span class="token function">reject</span><span class="token punctuation">(</span><span class="token function">Error</span><span class="token punctuation">(</span><span class="token string">'BAD'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">// You can handle the promise here</span>
<span class="token comment">// without it being an 'unhandled rejection'.</span>

<span class="token function">queueMicrotask</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token comment">// Or here.</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token comment">// But here might be too late.</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div><h3 id="unhandled-rejections-are-problematic"><a href="https://jakearchibald.com/2023/unhandled-rejections/#unhandled-rejections-are-problematic">Unhandled rejections are problematic</a></h3>
<p>Unhandled rejections are a bit like uncaught errors, in that they cause the entire program to exit with an error code in Node and Deno.</p>
<p>In browsers, you get errors appearing in the console, again similar to uncaught errors:</p>
<figure class="full-figure max-figure">
<picture>
  <source type="image/avif" srcset="https://jakearchibald.com/c/console-72b15aa1.avif">
  <img src="https://jakearchibald.com/c/console-88bec2e0.png" width="1598" height="288" alt="In the console: Uncaught (in promise) TypeError: Failed to fetch" style="height: auto">
</picture>
</figure>

<p>They might also appear in error logging systems, if the system listens for unhandled rejections:</p>
<div class="code-example"><pre class="language-js"><code><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'unhandledrejection'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token comment">// …Log the error to the server…</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div><p>The point is, you want to avoid unhandled rejections.</p>
<h2 id="but-where-are-the-unhandled-rejections-in-the-example"><a href="https://jakearchibald.com/2023/unhandled-rejections/#but-where-are-the-unhandled-rejections-in-the-example">But where are the unhandled rejections in the example?</a></h2>
<p>It's not immediately obvious:</p>
<div class="code-example"><pre class="language-js"><code><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">showChapters</span><span class="token punctuation">(</span><span class="token parameter">chapterURLs</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> chapterPromises <span class="token operator">=</span> chapterURLs<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">url</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> response <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fetch</span><span class="token punctuation">(</span>url<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> response<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">for</span> <span class="token keyword">await</span> <span class="token punctuation">(</span><span class="token keyword">const</span> chapterData <span class="token keyword">of</span> chapterPromises<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token function">appendChapter</span><span class="token punctuation">(</span>chapterData<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span></code></pre></div><p>The promises in <code>chapterPromises</code> are handled by the <code>for await</code> in each iteration of the loop. When the loop encounters a rejected promise, it becomes a throw, which abandons the function and rejects the promise <code>showChapters</code> returned.</p>
<p>The bug happens if a promise rejects before the <code>for await</code> handles that promise, or if that promise is never reached.</p>
<p>For example: If <code>chapterPromises[0]</code> takes a long time to resolve, and meanwhile <code>chapterPromises[1]</code> rejects, then <code>chapterPromises[1]</code> is an unhandled rejection, because the loop hasn't reached it yet.</p>
<p>Or: If <code>chapterPromises[0]</code> <em>and</em> <code>chapterPromises[1]</code> reject, then <code>chapterPromises[1]</code> is an unhandled rejection, because the loop is abandoned before it gets to <code>chapterPromises[1]</code>.</p>
<p>Ugh. The "unhandled promise rejection" feature is there so you don't 'miss' rejected promises, but in this case it's a false positive, because the promise returned by <code>showChapters</code> already sufficiently captures the success/failure of the operation.</p>
<p>This issue doesn't always involve fetches, it could be any bit of work you start early, then pick up the result later. Like a worker task.</p>
<p>This doesn't always involve <code>for await</code> either. It impacts any situation where you start the work early, then handle the result later, asynchronously.</p>
<p>It <em>wasn't</em> an issue for the <code>Promise.all</code> example:</p>
<div class="code-example"><pre class="language-js"><code><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">showChapters</span><span class="token punctuation">(</span><span class="token parameter">chapterURLs</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> chapterPromises <span class="token operator">=</span> chapterURLs<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">url</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> response <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fetch</span><span class="token punctuation">(</span>url<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> response<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">const</span> chapters <span class="token operator">=</span> <span class="token keyword">await</span> Promise<span class="token punctuation">.</span><span class="token function">all</span><span class="token punctuation">(</span>chapterPromises<span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> chapterData <span class="token keyword">of</span> chapters<span class="token punctuation">)</span> <span class="token function">appendChapter</span><span class="token punctuation">(</span>chapterData<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre></div><p>In this case all the promises in <code>chapterPromises</code> are handled immediately, by <code>Promise.all</code>, which returns a single promise that's immediately handled by the <code>await</code>. But this solution has worse performance than our sequential solution.</p>
<h2 id="whats-the-real-solution"><a href="https://jakearchibald.com/2023/unhandled-rejections/#whats-the-real-solution">What's the real solution?</a></h2>
<p>Unfortunately it's a bit of a hack. The solution is to immediately mark the promises as handled, before they have a chance to become unhandled rejections.</p>
<div class="code-example"><pre class="language-js"><code><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">showChapters</span><span class="token punctuation">(</span><span class="token parameter">chapterURLs</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> chapterPromises <span class="token operator">=</span> chapterURLs<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">url</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> response <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fetch</span><span class="token punctuation">(</span>url<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> response<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token comment">// TODO: 'handle' every promise in chapterPromises here</span>

  <span class="token keyword">for</span> <span class="token keyword">await</span> <span class="token punctuation">(</span><span class="token keyword">const</span> chapterData <span class="token keyword">of</span> chapterPromises<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token function">appendChapter</span><span class="token punctuation">(</span>chapterData<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span></code></pre></div><p>One way to do this is to add a dummy <code>catch</code> handler to each promise:</p>
<div class="code-example"><pre class="language-js"><code><span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> promise <span class="token keyword">of</span> chapterPromises<span class="token punctuation">)</span> promise<span class="token punctuation">.</span><span class="token function">catch</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div><p>This doesn't change the promises other than marking them as 'handled'. They're still rejected promises. It doesn't cause errors to be missed/swallowed elsewhere.</p>
<p>A shorter way to achieve the same thing is <a href="https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled"><code>Promise.allSettled</code></a>:</p>
<div class="code-example"><pre class="language-js"><code>Promise<span class="token punctuation">.</span><span class="token function">allSettled</span><span class="token punctuation">(</span>chapterPromises<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div><p>This works because <code>allSettled</code> handles all the promises you give it, similar to <code>Promise.all</code>, but unlike <code>Promise.all</code> it never returns a rejected promise itself (unless something is fundamentally wrong with the input iterator).</p>
<p>Both of these look pretty hacky, and likely to confuse others that read the code later. Because of this, I'd probably create a helper function like <code>preventUnhandledRejections</code>:</p>
<div class="code-example"><pre class="language-js"><code><span class="token comment">// In promise-utils.js:</span>
<span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">preventUnhandledRejections</span><span class="token punctuation">(</span><span class="token parameter"><span class="token operator">...</span>promises</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> promise <span class="token keyword">of</span> promises<span class="token punctuation">)</span> promise<span class="token punctuation">.</span><span class="token function">catch</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre></div><p>And comment its usage:</p>
<div class="code-example"><pre class="language-js"><code><span class="token keyword">import</span> <span class="token punctuation">{</span> preventUnhandledRejections <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./promise-utils.js'</span><span class="token punctuation">;</span>

<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">showChapters</span><span class="token punctuation">(</span><span class="token parameter">chapterURLs</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> chapterPromises <span class="token operator">=</span> chapterURLs<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">url</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> response <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fetch</span><span class="token punctuation">(</span>url<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> response<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token comment">// Avoid unhandled rejections leaking out of this function.</span>
  <span class="token comment">// The subsequent `for await` handles all the relevant promises.</span>
  <span class="token function">preventUnhandledRejections</span><span class="token punctuation">(</span><span class="token operator">...</span>chapterPromises<span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">for</span> <span class="token keyword">await</span> <span class="token punctuation">(</span><span class="token keyword">const</span> chapterData <span class="token keyword">of</span> chapterPromises<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token function">appendChapter</span><span class="token punctuation">(</span>chapterData<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span></code></pre></div><p>I wish there was a less 'blunt' way of handling this in JavaScript, but I'm not sure what that would look like. The design of the "unhandled rejections" feature directly clashes with starting work early and handling the result later, or not handling the result if a prerequisite fails.</p>
<p>In the meantime, <code>preventUnhandledRejections</code> does the trick!</p>
<p><small>For completeness, here's an <a href="https://gist.github.com/jakearchibald/8bc2360a6d3f6240d1bad2de375fa92a">abortable implementation of <code>showChapters</code></a>, that also handles bad responses.</small></p>
<p><small>Thanks to <a href="https://twitter.com/DasSurma">Surma</a> and <a href="https://twitter.com/tomayac">Thomas Steiner</a> for proof-reading.</small></p>
]]></description>
      <pubDate>Wed, 11 Jan 2023 01:00:00 +0000</pubDate>
      <link>https://jakearchibald.com/2023/unhandled-rejections/</link>
      <dc:creator>Jake Archibald&#39;s blog</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/3950178604</guid>
    </item>
    <item>
      <title><![CDATA[Faking Min Width on a Table Column]]></title>
      <description><![CDATA[
<p>The good ol’ <code>&lt;table&gt;</code> tag is the most semantic HTML for showing tabular data. But I find it very hard to control how the table is presented, particularly column widths in a dynamic environment where you might not know how much content is going into each table cell. In some cases, one column is super wide while others are scrunched up. Other times, we get equal widths, but at the expense of a column that contains more content and needs more space.</p>



<p>But I found a CSS tricks-y workaround that helps make things a little easier. That’s what I want to show you in this post.</p>



<span id="more-376307"></span>


<h3 id="the-problem">The problem</h3>


<p>First we need to understand how layout is handled by the browser. We have the <a href="https://css-tricks.com/almanac/properties/t/table-layout/"><code>table-layout</code></a> property in CSS to define how a table should distribute the width for each table column. It takes one of two values:</p>



<ul>
<li>
<code>auto</code> (default)</li>



<li><code>fixed</code></li>
</ul>



<p>Let us start with a table without defining any widths on its columns. In other words, we will let the browser decide how much width to give each column by applying <code>table-layout: auto</code> on it in CSS. As you will notice, the browser does its best with the algorithm it has to divide the full available width between each column.</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_KKqZgZE" src="//codepen.io/anon/embed/KKqZgZE?height=350&amp;theme-id=1&amp;slug-hash=KKqZgZE&amp;default-tab=result" height="350" scrolling="no" frameborder="0" allowfullscreen="" allowpaymentrequest="" name="CodePen Embed KKqZgZE" title="CodePen Embed KKqZgZE" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<p>If we swap out an auto table layout with <code>table-layout: fixed</code>, then the browser will merely divide the full available space by the total number of columns, then apply that value as the width for each column:</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_xxrpEaE" src="//codepen.io/anon/embed/xxrpEaE?height=450&amp;theme-id=1&amp;slug-hash=xxrpEaE&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen="" allowpaymentrequest="" name="CodePen Embed xxrpEaE" title="CodePen Embed xxrpEaE" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<p>But what if we want to control the widths of our columns? We have the <code>&lt;colgroup&gt;</code> element to help! It consists of individual <code>&lt;col&gt;</code> elements we can use to specify the exact width we need for each column. Let’s see how that works in with <code>table-layout: auto</code>:</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_GREyNye" src="//codepen.io/anon/embed/GREyNye?height=600&amp;theme-id=1&amp;slug-hash=GREyNye&amp;default-tab=result" height="600" scrolling="no" frameborder="0" allowfullscreen="" allowpaymentrequest="" name="CodePen Embed GREyNye" title="CodePen Embed GREyNye" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<p class="is-style-explanation">I have inlined the styles for the sake of illustration.</p>



<p>The browser is not respecting the inline widths since they exceed the amount of available table space when added up. As a result, the table steals space from the columns so that all of the columns are visible. This is perfectly fine default behavior.</p>



<p>How does <code>&lt;colgroup&gt;</code> work with <code>table-layout: fixed</code>. Let’s find out:</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_MWorbLm" src="//codepen.io/anon/embed/MWorbLm?height=450&amp;theme-id=1&amp;slug-hash=MWorbLm&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen="" allowpaymentrequest="" name="CodePen Embed MWorbLm" title="CodePen Embed MWorbLm" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<p>This doesn’t look good at all. We need the column with a bunch of content in it to flex a little while maintaining a fixed width for the rest of the columns. A fixed <code>table-layout</code> value respects the width — but so much so that it eats up the space of the column that needs the most space… which is a no-go for us.</p>



<p>This could easily be solved if only we could set a <code>min-width</code> on the column instead of a <code>width</code>. That way, the column would say, “I can give all of you some of my width until we reach this minimum value.“ Then the table would simply overflow its container and give the user a horizontal scroll to display the rest of the table. But unfortunately, <code>min-width</code> on table columns are not respected by the <code>&lt;col&gt;</code> element.</p>


<h3 id="the-solution">The solution</h3>


<p>The solution is to fake a <code>min-width</code> and we need to be a bit creative to do it.</p>



<p>We can add an empty <code>&lt;col&gt;</code> as the second column for our <code>&lt;colgroup&gt;</code> in the HTML and apply a <code>colspan</code> attribute on the first column so that the first column takes up the space for both columns:</p>



<pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line="4,12"><code markup="tt">
&lt;table&gt;
  &lt;colgroup&gt;
    &lt;col class="col-200" /&gt;
    &lt;col /&gt;
    &lt;col class="col-input" /&gt;
    &lt;col class="col-date" /&gt;
    &lt;col class="col-edit" /&gt;
  &lt;/colgroup&gt;
  
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th colspan="2"&gt;Project name&lt;/th&gt;
      &lt;th&gt;Amount&lt;/th&gt;
      &lt;th&gt;Date&lt;/th&gt;
      &lt;th&gt;Edit&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  
  &lt;!-- etc. --&gt;
&lt;/table&gt;</code></pre>



<p class="is-style-explanation">Note that I have added classes in place of the inline styles from the previous example. The same idea still applies: we’re applying widths to each column.</p>



<p>The trick is that relationship between the first <code>&lt;col&gt;</code> and the empty second <code>&lt;col&gt;</code>. If we apply a width to the first <code>&lt;col&gt;</code> (it’s <code>200px</code> in the snippet above), then the second column will be eaten up when the fixed table layout divides up the available space to distribute to the columns. But the width of the first column (<code>200px</code>) is respected and remains in place.</p>



<p><em>Voilà!</em> We have a faux <code>min-width</code> set on a table cell. The first cell flexes as the available space changes and the table overflows for horizontal scrolling just as we hoped it would.</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_BaZJyLr" src="//codepen.io/anon/embed/BaZJyLr?height=450&amp;theme-id=1&amp;slug-hash=BaZJyLr&amp;default-tab=result" height="450" scrolling="no" frameborder="0" allowfullscreen="" allowpaymentrequest="" name="CodePen Embed BaZJyLr" title="CodePen Embed BaZJyLr" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>



<p>(I added a little <a href="https://css-tricks.com/a-table-with-both-a-sticky-header-and-a-sticky-first-column/">sticky positioning</a> to the first column there.)</p>


<h2 id="accessibility">Accessibility</h2>


<p>Let’s not totally forget about accessibility here. I ran the table through NVDA on Windows and VoiceOver on macOS and found that all five columns are announced, even if we’re only using four of them. And when the first column is in focus, it announces, “Column one through two”. Not perfectly elegant but also not going to cause someone to get lost. I imagine we could throw an <code>aria-hidden</code> attribute on the unused column, but also know ARIA isn’t a substitute for poor HTML.</p>



<hr class="wp-block-separator has-alpha-channel-opacity">



<p>I’ll admit, this feels a little, um, hacky. But it does work! Let me know if you have a different approach in the comments… or know of any confusions this “hack” might bring to our users.</p>
<hr>
<p><small><a rel="nofollow" href="https://css-tricks.com/faking-min-width-on-a-table-column/">Faking Min Width on a Table Column</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</small></p>
]]></description>
      <pubDate>Tue, 10 Jan 2023 15:11:20 +0000</pubDate>
      <link>https://css-tricks.com/faking-min-width-on-a-table-column/</link>
      <dc:creator>Starred Articles</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/3952336926</guid>
    </item>
    <item>
      <title><![CDATA[The Anti-Capitalist Web]]></title>
      <description><![CDATA[<p>I’m not sure if any idea has stuck in my head over the last year more than <a href="https://twitter.com/terriblemia/status/1198706002419310592">this tweet by Miriam Suzanne</a>:</p>
<blockquote>
<p>Large companies find HTML &amp; CSS frustrating “at scale” because the web is a fundamentally anti-capitalist mashup art experiment, designed to give consumers all the power.</p>
</blockquote>
<p>Ooooff. Ponder on that for a while.</p>
<p>I love the web — and what I love about the web, at its core, is something that is individually freeing and runs against the grain of corporate value extraction. How do you make a living ($$$) working on something that you love whose reason for being resists the impulse for capitalization?</p>
<p>The web is full of puzzling but beautiful dualities. It reminds me of <a href="https://www.roughtype.com/">Nicholas Carr’s writing</a> where he points out the irony of the web being heralded by early advocates as a tool of liberation but in practice becoming widely (ab)used as an instrument of control. </p>
<p>Could the web, and everything I love about it, even be built as a platform today? <a href="https://mastodon.social/@craigmod/109561306266587786">Craig Mod wonders this aloud</a>:</p>
<blockquote>
<p>I still find it fully miraculous that webpage source code is freely viewable, and browsers allow for trivial editing of the source live and inline </p>
<p>Can just imagine the pushback against if someone tried to launch a platform like this today</p>
</blockquote>
<p>It’s quite incredible that the money and power birthed through the advent of the web haven’t yet been able to completely overtake this “mashup art experiment” medium which “gives consumers all the power”.</p>

    <hr>
    <ul>
      <li>
        Reply via:
        
        <a href="mailto:jimniels%2Bblog@gmail.com?subject=Re:%20blog.jim-nielsen.com/2023/the-anti-capitalist-web/">Email</a>, <a href="https://twitter.com/jimniels">Twitter</a>,
        <a href="https://mastodon.social/@jimniels">Mastodon</a>
      </li>
      
      
    </ul>
    <br>
  ]]></description>
      <pubDate>Wed, 18 Jan 2023 16:38:18 +0000</pubDate>
      <link>https://blog.jim-nielsen.com/2023/the-anti-capitalist-web/</link>
      <dc:creator>Jim Nielsen’s Blog</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/3959828227</guid>
    </item>
    <item>
      <title><![CDATA[Subscribe Wherever You Get Your Content]]></title>
      <description><![CDATA[<p>I’ve been thinking about that turn of phrase you hear in podcasting everywhere these days:</p>
<ul>
<li>“New episodes every week. Subscribe wherever you get your podcasts.”</li>
<li>“Follow us in your podcatcher of choice.”</li>
<li>“Search for ____ wherever you listen and follow.”</li>
</ul>
<p>It’s an expression that has entered the larger public consciousness and suggests (albeit an implicit) understanding of an open, distributed web. You don’t go to a singular, centralized, corporate gatekeeper to get your podcasts. Instead, you grab an app — any podcasting app — and all they seem to have access to the same stuff (unless they’re exclusive and you have to pay — <em>cough</em> Spotify <em>cough</em>).</p>
<p>What’s amazing about these phrases — like “Follow us wherever you get your podcasts” — is that they are are <em>paid advertisements</em> that point you to nowhere in particular! It’s not “Stream it now on _Netlix_” or “Follow us on _Facebook_” or even “Visit _ourwebsite.com_”. These are paid advertisements with no “call to action” to any one commercial, corporate platform.</p>
<p>It’s kind of amazing: it’s not just hippie nerds talking about an open, accessible, distributed content model. People and corporations are putting real money into supporting a model that gives users all the power to browse, follow, and access whatever they want, wherever and however they best see fit.</p>
<p>It’s a wonderful phenomenon, articulated well in <a href="https://aus.social/@attacus/109499740512576514">this Mastodon post from @attacus@aus.social</a>:</p>
<blockquote>
<p>“You can find us anywhere you get your podcasts.”</p>
<p>I <em>adore</em> this phrase, because…Podcasts are just out there, like air. You don’t go to one place to get them; you get them from everywhere and anywhere. You can choose how you want to engage with them and manage them</p>
</blockquote>
<p>Wouldn’t it be amazing if a similar phrase could enter the larger public consciousness for blogs or even <em>people</em> who you follow online?</p>
<p>Instead of:</p>
<ul>
<li>“Follow me on Twitter.”</li>
<li>“I’m @realChuckieCheese on Instagram.” </li>
<li>“Subscribe to my videos on YouTube.”</li>
</ul>
<p>You’d hear some popular influencer saying:</p>
<ul>
<li>“Follow me wherever you follow people online.”</li>
<li>“Find me and subscribe wherever you get your content.”</li>
</ul>
<p>Imagine a de-centralized model of syndication and supporting third-party apps such that non-techies would know what it means when somebody says, “Follow me online”. It wouldn’t entail being funneled through the digital tools of any one, centralized entity; rather, you’d just grab your favorite app for following people and subscribe!</p>
<p>In a world like this, perhaps <a href="https://twitter.com/jimniels/status/1597654715873267713">apex domains could become the currency of online handles</a>: follow me <code>@jim-nielsen.com</code>. Then, through <a href="https://blog.jim-nielsen.com/2021/automatically-discoverable-rss-feeds/">the magic of auto-discoverable feed URLs</a>, all meta info necessary for any  reader to subscribe to any feed of content would become automagically available. Feeds everywhere and <a href="https://chriscoyier.net/2023/01/04/feeds-can-be-whatever/">feeds can be whatever</a>.</p>
<p>Perhaps, one day, this will be our future.</p>

    <hr>
    <ul>
      <li>
        Reply via:
        
        <a href="mailto:jimniels%2Bblog@gmail.com?subject=Re:%20blog.jim-nielsen.com/2023/subscribe-wherever-you-get-your-content/">Email</a>, <a href="https://twitter.com/jimniels">Twitter</a>,
        <a href="https://mastodon.social/@jimniels">Mastodon</a>
      </li>
      
      <li>
            Related posts linking here:
            <a href="https://blog.jim-nielsen.com/2023/best-time-to-own-a-domain/">The Best Time to Own a Domain Was 20 Years Ago; The Second Best Time Is Today</a>
          </li>
    </ul>
    <br>
  ]]></description>
      <pubDate>Tue, 10 Jan 2023 17:27:30 +0000</pubDate>
      <link>https://blog.jim-nielsen.com/2023/subscribe-wherever-you-get-your-content/</link>
      <dc:creator>Jim Nielsen’s Blog</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/3949044742</guid>
    </item>
    <item>
      <title><![CDATA[OK LCH, I’m Convinced]]></title>
      <description><![CDATA[<p>Here’s Josh delivering a fascinating tidbit about LCH color in his recent post <a href="https://www.joshwcomeau.com/css/color-formats/">“Color Formats in CSS”</a>:</p>
<blockquote>
<p>LCH isn't linked to a particular color space, and so we don't know where the upper saturation limit is. It's not static: as display technology continues to improve, we can expect monitors to reach wider and wider gamuts. LCH will automatically be able to reference these expanded colors by cranking up the chroma. Talk about future-proofing!</p>
</blockquote>
<p>Wait, what?!? LCH syntax has an eye towards future-proofing?? I did not know this!!</p>
<p>I’ve heard LCH proponents talk about why they prefer it — usually it has to do with how it accounts for human perception in color changes. Here’s <a href="https://lea.verou.me/2020/04/lch-colors-in-css-what-why-and-how/">Lea Verou</a>:</p>
<blockquote>
<p>In LCH, the same numerical change in coordinates produces the same perceptual color difference. This property of a color space is called “perceptual uniformity”. RGB or HSL are not perceptually uniform.</p>
</blockquote>
<p>That’s neat and solves a real problem I’ve encountered in color transformations.</p>
<p>But this “future-proofing” business? I love that! Granted, technology will always be changing, but a new API that aims for longevity is a hype train I can jump on — and hopefully ride for a very long time. Switching from hex to rgb to hsl to hwb to who knows what next over the years can be a bit exhausting.</p>
<p>Lea explains how LCH is designed to represent the entire spectrum of what humans can see. As such, chroma for example, is theoretically unbounded in the syntax allowing for increased saturation values as hardware improves:</p>
<blockquote>
<p>In HSL, saturation is a neat 0-100 percentage, since it’s a simple transformation of RGB into polar coordinates. In LCH however, <strong>Chroma is theoretically unbounded</strong>. LCH (like Lab) is designed to be able to represent the entire spectrum of human vision, and not all of these colors can be displayed by a screen, even a P3 screen. Not only is the maximum chroma different depending on screen gamut, it’s actually different per color.</p>
</blockquote>
<p>Josh illustrates how this concept works in this intriguing code example:</p>
<pre><code class="language language-css"><span class="hljs-selector-class">.red-box</span> {
  <span class="hljs-comment">/* This is a very-red box in sRGB: */</span>
  <span class="hljs-attribute">background</span>: <span class="hljs-built_in">lch</span>(<span class="hljs-number">50%</span> <span class="hljs-number">120</span> <span class="hljs-number">20</span>);
  
  <span class="hljs-comment">/*
    This is an identical color in sRGB, but will appear
    MUCH REDDER in the wider-gamut displays of the future:
  */</span>
  <span class="hljs-attribute">background</span>: <span class="hljs-built_in">lch</span>(<span class="hljs-number">50%</span> <span class="hljs-number">500</span> <span class="hljs-number">20</span>);
}
</code></pre>
<p>Note how the chroma value is jacked up super high? It’s representing a color visible to the human eye but maybe only visible on certain hardware. </p>
<p>And that’s the beautiful part: this all builds on the idea of progressive enhancement. If a color you specify in LCH is outside the gamut of a given monitor (e.g. you specify a YUGE chroma value) browsers will scale back the value to the closest approximation of that color given the supported hardware. Here’s Lea again:</p>
<blockquote>
<p>While the lack of boundaries can be somewhat unsettling (in people and in color spaces), don’t worry: if you specify a color that is not displayable in a given monitor, it will be scaled down so that it becomes visible while preserving its essence. After all, that’s not new: before monitors got gamuts wider than sRGB, this is what was happening with regular CSS colors when they were displayed in monitors with gamuts smaller than sRGB.</p>
</blockquote>
<p>Perceptual uniformity? An eye towards longevity? Future-proofing syntax? Graceful fallbacks? Aligned with human anatomy? OK LCH, I’ll jump on your bandwagon.</p>
<p>One color syntax to rule them all!</p>
<p>(BTW: <a href="https://evilmartians.com/chronicles/oklch-in-css-why-quit-rgb-hsl">this post</a> on LCH, and color in general on the web, was super helpful for me too!)</p>
<h2 id="update-2023-01-09">Update 2023-01-09</h2>
<p>Dan Peterson, who works on 1Password, <a href="https://mastodon.social/@danvpeterson/109660316334309838">ping me on Mastodon</a> noting his love for OKLCH after using it on a color token project. He also passed along a bunch of links he found useful during his research. I haven’t gone down the rabbit hole of all these, but they all look super useful so I’m appending them here. Thanks Dan!</p>
<ul>
<li><a href="https://bottosson.github.io/posts/oklab/">“A perceptual color space for image processing”</a></li>
<li><a href="https://bottosson.github.io/posts/colorpicker/">Okhsv and Okhsl</a></li>
<li><a href="https://blog.datawrapper.de/color-contrast-check-data-vis-wcag-apca/">“It’s time for a more sophisticated color contrast check for data visualizations”</a></li>
<li><a href="https://raphlinus.github.io/color/2021/01/18/oklab-critique.html">“An interactive review of Oklab”</a></li>
<li><a href="https://uxplanet.org/designing-color-systems-transparent-vs-solid-shades-9eb841571fdd">“Designing Color Systems — Transparent vs. Solid Shades”</a></li>
<li><a href="https://git.apcacontrast.com/documentation/WhyAPCA">“Why APCA as a New Contrast Method?”</a></li>
<li><a href="https://www.myndex.com/APCA/">APCA Contrast Calculator</a></li>
<li><a href="https://oklch.com/">OKLCH Color Picker &amp; Converter</a></li>
<li><a href="https://bottosson.github.io/misc/colorpicker/#8c2d2d">Interactive color picker comparison</a></li>
<li><a href="https://colorjs.io/">Color.js</a></li>
</ul>
<h2 id="update-2021-01-11">Update 2021-01-11</h2>
<p>In case you’re wondering, the consensus seems to be: you should be using <code>oklch</code> and not <code>lch</code> because <code>oklch</code> fixes a bug in <code>lch</code> (and adds a few new features).</p>
<p>But that <code>ok</code> prefix, where did that come from? <a href="https://twitter.com/jimniels/status/1612487807871623169">I asked on Twitter</a>:</p>
<blockquote>
<p>dumb question: is it called "oklch" because it fixes a bug in lch and so it's the "ok" version of lch to use? i can't discern where the "ok" comes from...</p>
</blockquote>
<p>And it sounds like that actually is the case, ha! <a href="https://twitter.com/bjornornorn/status/1613211390600450049">Here’s Björn Ottosson</a>, creator of Oklab:</p>
<blockquote>
<p>In many cases simpler models are more practical to use, and should give OK results. Not a lot of effort has gone into finding such simple models though. Oklab/Oklch is my attempt at addressing that, to get OK results without being too complicated.</p>
</blockquote>

    <hr>
    <ul>
      <li>
        Reply via:
        
        <a href="mailto:jimniels%2Bblog@gmail.com?subject=Re:%20blog.jim-nielsen.com/2023/ok-lch-im-convinced/">Email</a>, <a href="https://twitter.com/jimniels">Twitter</a>,
        <a href="https://mastodon.social/@jimniels">Mastodon</a>
      </li>
      
      
    </ul>
    <br>
  ]]></description>
      <pubDate>Sun, 08 Jan 2023 19:00:00 +0000</pubDate>
      <link>https://blog.jim-nielsen.com/2023/ok-lch-im-convinced/</link>
      <dc:creator>Jim Nielsen’s Blog</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/3946865772</guid>
    </item>
    <item>
      <title><![CDATA[Thinking Systematically]]></title>
      <description><![CDATA[<p>Jeremy writes about <a href="https://adactio.com/journal/18640">logical properties</a>:</p>
<blockquote>
<p>It’s like when you’re learning a new language. At some point your brain goes from <em>translating</em> from your mother tongue into the other language, and instead starts <em>thinking</em> in that other language. Likewise with CSS, at some point you want to stop translating “left” and “right” into “inline-start” and “inline-end” and instead start <em>thinking</em> in terms of inline and block dimensions.</p>
</blockquote>
<p>This got me ruminating on design systems work, but also system abstractions in general.</p>
<p>Thinking systematically, you begin to see abstractions in the one-offs which helps you translate the execution of a specific design to traits of a generalized one.</p>
<p>What am I talking about? Take color, for example.</p>
<p>You’re prototyping and you want to use purple, so you reach for a CSS keyword and call it a day with <code>color: purple</code>.</p>
<p>Later you decide there’s a particular hue of “purple” that’s considered “brand purple” so you copy/paste that value and ship it, resulting in CSS like <code>color: #8f00ff</code>.</p>
<p>But, wait: you need that purple in lots of other places, so you name it for reuse and leverage CSS custom properties: <code>var(--color-brand)</code>.</p>
<p>But wait again: that tells you what the thing <em>is</em>, not how you should <em>use</em> it. So you <a href="https://blog.jim-nielsen.com/2022/good-design-pt-ii/">devise a purpose</a> for the color within an ordered system by providing it a semantic name which circumscribes its use, role, and function apart from other colors, e.g. <code>var(--color-primary-accent)</code>.</p>
<p>You can see how there are <a href="https://blog.jim-nielsen.com/2022/the-word-value-in-css/">different tiers of values</a>, each having its own purpose and trade-offs:</p>
<ul>
<li>Approximate value: <code>purple</code></li>
<li>Literal value <code>#8f00ff</code></li>
<li>Named value <code>--color-brand</code></li>
<li>Semantic value <code>--color-primary-accent</code></li>
</ul>
<p>The idea of semantic values as vocabulary within a system is useful though not always immediately intuitive. It requires context at a broader level than any single, bespoke design. However, as you work in this model, eventually its language becomes second nature and you stop thinking of color as one-off choices, e.g. <code>#8f00ff</code>, and instead begin thinking of color as relational values disambiguated in purpose by semantic naming, e.g. <code>color-primary-accent</code>. </p>
<p>From this new perspective, there’s no longer “purple” and “green” for use as you see fit, but <code>color-primary-accent</code> and <code>color-secondary-accent</code> for purposeful use within a rationale, ordered system.</p>

    <hr>
    <ul>
      <li>
        Reply via:
        
        <a href="mailto:jimniels%2Bblog@gmail.com?subject=Re:%20blog.jim-nielsen.com/2023/thinking-systematically/">Email</a>, <a href="https://twitter.com/jimniels">Twitter</a>,
        <a href="https://mastodon.social/@jimniels">Mastodon</a>
      </li>
      
      
    </ul>
    <br>
  ]]></description>
      <pubDate>Wed, 04 Jan 2023 19:00:00 +0000</pubDate>
      <link>https://blog.jim-nielsen.com/2023/thinking-systematically/</link>
      <dc:creator>Jim Nielsen’s Blog</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/3941633371</guid>
    </item>
    <item>
      <title><![CDATA[Unchain My Inaccessibly-Labelled Heart]]></title>
      <description><![CDATA[
<p><a href="https://twitter.com/cfdbchick" rel="noopener">Suzy Naschansky</a> from the <a href="https://www.htmhell.dev/adventcalendar/2022/7/" rel="noopener">HTMHell Advent Calendar</a>:</p>



<pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;h2 id="article1-heading"&gt;All About Dragons&lt;/h2&gt;  
&lt;p&gt;I like dragons. Blah blah blah blah blah.&lt;/p&gt;  
&lt;p&gt;
  &lt;a id="article1-read-more" aria-labelledby="article1-read-more article1-heading"&gt;Read more&lt;/a&gt;  
&lt;/p&gt;  </code></pre>



<p>See that <code>aria-labelledby</code> attribute? It chains two IDs from the markup, one for the heading (<code>#article1-heading</code>) and one for the link (<code>#article1-read-more</code>). What happens there is a screenreader will replace the existing semantic label between the link tags and use the content from both elements and announce them together as a single string of text:</p>



<pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">Read more All About Dragons</code></pre>



<span id="more-375952"></span>



<p>I’m always sheepish when realizing there’s something I think I should know but don’t. This is definitely one of those cases and I’m thankful as all heck that Suzy shared it.</p>



<p>I was actually in a situation just recently where I <s>could’ve</s> should’ve done this. I always try to avoid a bunch of “Read more” links on the same page but coming up with different flavors of the same thing is tough when you’re working with something like a loop of 15 posts (even though there are <a href="https://ctaexamples.com" rel="noopener">resources to help</a>). And if we need to keep labels short for aesthetic reasons — design requirements and whatnot — it’s even more challenging. The <code>aria-labelledby</code> attribute gives me exactly what I want: consistent visual labels and more contextual announcements for assistive tech.</p>



<p>And this is only a thing when the text you want to use for the accessible label already exists on the page. Otherwise, you’d want to go with <code>aria-label</code> and with the caveat that it’s purely for interactive elements that are unable to label things accessibly with semantic HTML.</p>



<p>If you are working in a CMS like WordPress (which I am), you might need to do a little extra work. Like when I drop a Button block on the page, these are the options I have to work with:</p>



<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="2174" height="1962" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2022/12/Screenshot-2022-12-07-at-9.47.45-AM.png?resize=2174%2C1962&amp;ssl=1" alt="" class="wp-image-375953" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2022/12/Screenshot-2022-12-07-at-9.47.45-AM.png?w=2174&amp;ssl=1 2174w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2022/12/Screenshot-2022-12-07-at-9.47.45-AM.png?resize=300%2C271&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2022/12/Screenshot-2022-12-07-at-9.47.45-AM.png?resize=1024%2C924&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2022/12/Screenshot-2022-12-07-at-9.47.45-AM.png?resize=768%2C693&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2022/12/Screenshot-2022-12-07-at-9.47.45-AM.png?resize=1536%2C1386&amp;ssl=1 1536w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2022/12/Screenshot-2022-12-07-at-9.47.45-AM.png?resize=2048%2C1848&amp;ssl=1 2048w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1"></figure>



<p>Some nice options in there, but nothing to do with accessible labelling. If you’re wondering what’s buried in that Advanced panel:</p>



<figure class="wp-block-image size-full is-resized"><img decoding="async" loading="lazy" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2022/12/Screenshot-2022-12-07-at-9.48.56-AM.png?resize=300%2C403&amp;ssl=1" alt="" class="wp-image-375955" width="300" height="403" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2022/12/Screenshot-2022-12-07-at-9.48.56-AM.png?w=600&amp;ssl=1 600w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2022/12/Screenshot-2022-12-07-at-9.48.56-AM.png?resize=223%2C300&amp;ssl=1 223w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1"><figcaption class="wp-element-caption">Close, but no cigar.</figcaption></figure>



<p>Instead, you’ll need to edit the button in HTML mode:</p>



<figure class="wp-block-image size-full is-resized"><img decoding="async" loading="lazy" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2022/12/Screenshot-2022-12-07-at-9.57.42-AM.png?resize=590%2C362&amp;ssl=1" alt="" class="wp-image-375957" width="590" height="362" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2022/12/Screenshot-2022-12-07-at-9.57.42-AM.png?w=1180&amp;ssl=1 1180w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2022/12/Screenshot-2022-12-07-at-9.57.42-AM.png?resize=300%2C184&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2022/12/Screenshot-2022-12-07-at-9.57.42-AM.png?resize=1024%2C628&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2022/12/Screenshot-2022-12-07-at-9.57.42-AM.png?resize=768%2C471&amp;ssl=1 768w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1"></figure>



<p>But before doing that, you gotta add an ID to the heading you want to use. The Heading block has the same Advanced panel setting for adding an anchor, which’ll inject an ID on the element:</p>



<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="1702" height="292" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2022/12/Screenshot-2022-12-07-at-10.13.30-AM.png?resize=1702%2C292&amp;ssl=1" alt="" class="wp-image-375965" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2022/12/Screenshot-2022-12-07-at-10.13.30-AM.png?w=1702&amp;ssl=1 1702w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2022/12/Screenshot-2022-12-07-at-10.13.30-AM.png?resize=300%2C51&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2022/12/Screenshot-2022-12-07-at-10.13.30-AM.png?resize=1024%2C176&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2022/12/Screenshot-2022-12-07-at-10.13.30-AM.png?resize=768%2C132&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2022/12/Screenshot-2022-12-07-at-10.13.30-AM.png?resize=1536%2C264&amp;ssl=1 1536w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1"></figure>



<p>Then you can go edit the Button block in HTML mode and add the <code>accessible-labels</code> ID as well as an ID for the button itself. This is an example of the edited markup:</p>



<pre rel="HTML" class="wp-block-csstricks-code-block language-markup" data-line=""><code markup="tt">&lt;div class="wp-block-buttons"&gt;
  &lt;!-- wp:button --&gt;
  &lt;div class="wp-block-button"&gt;
    &lt;a id="read-more-button" aria-labelledby="read-more-button heading-accessible-labels" class="wp-block-button__link wp-element-button" href="https://webaim.org/projects/million/"&gt;
      Read Report
    &lt;/a&gt;
  &lt;/div&gt;
  &lt;!-- /wp:button --&gt;
&lt;/div&gt;</code></pre>



<p>Great! But WordPress just ain’t cool with that:</p>



<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="1410" height="206" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2022/12/Screenshot-2022-12-07-at-10.16.33-AM.png?resize=1410%2C206&amp;ssl=1" alt="" class="wp-image-375967" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2022/12/Screenshot-2022-12-07-at-10.16.33-AM.png?w=1410&amp;ssl=1 1410w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2022/12/Screenshot-2022-12-07-at-10.16.33-AM.png?resize=300%2C44&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2022/12/Screenshot-2022-12-07-at-10.16.33-AM.png?resize=1024%2C150&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2022/12/Screenshot-2022-12-07-at-10.16.33-AM.png?resize=768%2C112&amp;ssl=1 768w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1"></figure>



<p>You can try to resolve the issue:</p>



<figure class="wp-block-image size-large"><img decoding="async" loading="lazy" width="1024" height="298" src="https://i0.wp.com/css-tricks.com/wp-content/uploads/2022/12/Screenshot-2022-12-07-at-10.15.52-AM.png?resize=1024%2C298&amp;ssl=1" alt="" class="wp-image-375966" srcset="https://i0.wp.com/css-tricks.com/wp-content/uploads/2022/12/Screenshot-2022-12-07-at-10.15.52-AM.png?resize=1024%2C298&amp;ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2022/12/Screenshot-2022-12-07-at-10.15.52-AM.png?resize=300%2C87&amp;ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2022/12/Screenshot-2022-12-07-at-10.15.52-AM.png?resize=768%2C224&amp;ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2022/12/Screenshot-2022-12-07-at-10.15.52-AM.png?resize=1536%2C448&amp;ssl=1 1536w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2022/12/Screenshot-2022-12-07-at-10.15.52-AM.png?resize=2048%2C597&amp;ssl=1 2048w" sizes="(min-width: 735px) 864px, 96vw" data-recalc-dims="1"></figure>



<p><em>Le sigh.</em> The Button block has to be converted to a Custom HTML block. Kinda defeats the whole visual editing thing that WordPress is so good at. I did a super quick search for a plugin that might add ARIA labelling options to certain blocks, but came up short. Seems like a ripe opportunity to make one or <a href="https://github.com/WordPress/wordpress-develop/tree/trunk/src/wp-includes/blocks" rel="noopener">submit PRs for the blocks that could use those options</a>.</p>
<hr>
<p><small><a rel="nofollow" href="https://css-tricks.com/unchain-my-inaccessibly-labelled-heart/">Unchain My Inaccessibly-Labelled Heart</a> originally published on <a rel="nofollow" href="https://css-tricks.com">CSS-Tricks</a>, which is part of the <a href="https://try.digitalocean.com/css-tricks/?utm_medium=rss&amp;utm_source=css-tricks.com&amp;utm_campaign=family_&amp;utm_content=">DigitalOcean</a> family. You should <a href="https://css-tricks.com/newsletters/">get the newsletter</a>.</small></p><small>
</small>]]></description>
      <pubDate>Wed, 14 Dec 2022 14:08:20 +0000</pubDate>
      <link>https://css-tricks.com/unchain-my-inaccessibly-labelled-heart/</link>
      <dc:creator>CSS-Tricks</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/3916702470</guid>
    </item>
    <item>
      <title><![CDATA[Accessible Front-End Patterns For Responsive Tables (Part 1)]]></title>
      <description><![CDATA[<p>Tables allow us to organize data into <strong>grid-like format of rows and columns</strong>. Scanning the table in one direction allows users to search and compare the data while scanning in the other direction lets users get all details for a single item by <strong>matching the data to their respective table header</strong> elements. </p>
<p><img src="https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/ee609bea-f7ca-4fc7-9f32-388109253cc9/1-accessible-front-end-patterns-responsive-tables-part1.png"></p>
<p>Tables often rely on having <strong>enough screen space</strong> to communicate these data relations effectively. This makes designing and developing more complex responsive tables somewhat of a challenge. There is <strong>no universal, silver-bullet solution for making the tables responsive</strong> as we often see with other elements like accordions, dropdowns, modals, and so on. It all depends on the main purpose of the table and how it’s being used.</p>
<p>If we fail to consider these factors and use the wrong approach, we can potentially make usability worse for some users.</p>
<p><img src="https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/8962abce-13f4-4020-8a3f-a38de20e01d6/2-accessible-front-end-patterns-responsive-tables-part1.png"></p>
<p>In this article, we’re going to be strictly focused on various ways we can make tables on the web responsive, depending on the data type and table use-case, so we’re not going to cover table search, filtering, and other similar functionalities. </p>
<p>If you are interested in improving user experience (UX) for tables and other UI elements beyond just responsiveness, make sure to check out Smashing Magazine’s incredibly useful <a href="https://smart-interface-design-patterns.com/">Smart Interface Design Patterns workshop</a>, which covers best practices and guidelines for various UI components, tables included.</p>
Short Primer On Accessible Tables
<p>Before diving into specific responsive table patterns, let’s quickly go over some best practices regarding design and accessibility. We’ll cover some general points in this section and other, more specific ones in later examples.</p>
<h3>Design And Visual Features</h3>
<p>First, we need to ensure that users can easily scan the table and intuitively match the data to their respective table header elements. From the design perspective, we can ensure the following:</p>
<ul>
<li>Use <strong>proper vertical and horizontal alignment</strong> (“A List Apart” covers this in their <a href="https://alistapart.com/article/web-typography-tables/#section5">article</a>).</li>
<li>Design a table with <strong>clear divisions and optimal spacing</strong> between rows and cells.</li>
<li><strong>Table header elements should stand out</strong> and be styled differently from data cells.</li>
<li>Consider using <strong>alternate background color for rows or columns</strong> (“zebra stripes”) for easier scanning.</li>
</ul>
<h3>ARIA Roles</h3>
<p>We want to include proper ARIA attributes to our table element and its descendants. Applying some CSS styles like <code>display: block</code> or <code>display: flex</code> (to create responsive stacked columns) may <a href="https://adrianroselli.com/2018/02/tables-css-display-properties-and-aria.html#TheTable">cause issues in some browsers</a>. In those cases, screen readers interpret the <code>table</code> element differently, and we lose the useful table semantics. By adding ARIA labels, we can fix the issue and retain the table semantics.</p>
<p>Including these roles in HTML manually could become tedious and prone to error. If you are <strong>comfortable about using JavaScript</strong> for adding additional markup, and you aren’t using a framework that generates static HTML files, you can use this handy little <a href="https://adrianroselli.com/2018/05/functions-to-add-aria-to-tables-and-lists.html#Table">JavaScript function</a> made by Adrian Roselli to automatically add ARIA roles to table elements:</p>
<pre><code>function AddTableARIA() {
  try {
    var allTables = document.querySelectorAll("table");
    for (var i = 0; i &lt; allTables.length; i++) {
      allTables[i].setAttribute("role", "table");
    }
    var allRowGroups = document.querySelectorAll("thead, tbody, tfoot");
    for (var i = 0; i &lt; allRowGroups.length; i++) {
      allRowGroups[i].setAttribute("role", "rowgroup");
    }
    var allRows = document.querySelectorAll("tr");
    for (var i = 0; i &lt; allRows.length; i++) {
      allRows[i].setAttribute("role", "row");
    }
    var allCells = document.querySelectorAll("td");
    for (var i = 0; i &lt; allCells.length; i++) {
      allCells[i].setAttribute("role", "cell");
    }
    var allHeaders = document.querySelectorAll("th");
    for (var i = 0; i &lt; allHeaders.length; i++) {
      allHeaders[i].setAttribute("role", "columnheader");
    }
    // This accounts for scoped row headers
    var allRowHeaders = document.querySelectorAll("th[scope=row]");
    for (var i = 0; i &lt; allRowHeaders.length; i++) {
      allRowHeaders[i].setAttribute("role", "rowheader");
    }
    // Caption role not needed as it is not a real role, and
    // browsers do not dump their own role with the display block.
  } catch (e) {
    console.log("AddTableARIA(): " + e);
  }
}
</code></pre>

<p>However, keep in mind the following <strong>potential drawbacks</strong> of using JavaScript here:</p>
<ul>
<li>Users might choose to browse the website with JavaScript turned off.</li>
<li>The JavaScript file may not be downloaded or may be downloaded much later if the user is browsing the website on an unreliable or slow network.</li>
<li>If this is bundled alongside other JavaScript code in the same file, an error in other parts of the file might prevent this function from running in some cases.</li>
</ul>
<h3>Adding An a11y-Friendy Title</h3>
<p>Adding a title next to the table helps both sighted users and users with assistive devices get a complete understanding of the content.</p>
<p>Ideally, we would include a <code>caption</code> element inside the <code>table</code> element as a first child. Notice how we can nest any HTML heading element as a child to maintain the title hierarchy.</p>
<pre><code>&lt;table&gt;
  &lt;caption&gt;
    &lt;h2&gt;Top 10 best-selling albums of all time&lt;/h2&gt;
  &lt;/caption&gt;

   &lt;!-- Table markup --&gt;
&lt;/table&gt;
</code></pre>

<p>If we are using a wrapper element to make the table scrollable or adding some other functionality that makes the <code>caption</code> element not ideal, we can include the <code>table</code> inside a <code>figure</code> element and use a <code>figcaption</code> to add a title. Make sure to include a proper ARIA label on either the table element or a wrapper element and link it to a <code>figcaption</code> element:</p>
<div>
<pre><code>&lt;figure&gt;
  &lt;figcaption id="caption"&gt;Top 10 best-selling albums of all time&lt;/figcaption&gt;
  &lt;table aria-labelledby="caption"&gt;&lt;!-- Table markup --&gt;&lt;/table&gt;
&lt;/figure&gt;
</code></pre>
</div>

<div>
<pre><code>&lt;figure&gt;
  &lt;figcaption id="caption"&gt;
    &lt;h2&gt;Top 10 best-selling albums of all time&lt;/h2&gt;
  &lt;/figcaption&gt;
  &lt;div class="table-wrapper" role="group" aria-labelledby="caption" tabindex="0"&gt;
    &lt;table&gt;&lt;!-- Table markup --&gt;&lt;/table&gt;
  &lt;/div&gt;
&lt;/figure&gt;
</code></pre>
</div>

<p>There are <strong>other accessibility aspects</strong> to consider when designing and developing tables, like keyboard navigation, print styles, high contrast mode, and others. We’ll cover some of those in the following sections. For a <strong>more comprehensive guide</strong> on creating accessible table elements, make sure to check out <a href="https://inclusive-components.design/data-tables/">Heydon Pickering’s guide</a> and <a href="https://adrianroselli.com/2017/11/a-responsive-accessible-table.html">Adrian Roselli’s article</a> which is being kept up to date with the latest features and best practices.</p>
Bare-bones Responsive Approach
<p>Sometimes we don’t have to make any major changes to our table to make it responsive. We just need to ensure the table width responds to the viewport width. That can be easily achieved with <code>width: 100%</code>, but we should also consider setting a dynamic <code>max-width</code> value, so our table doesn’t grow too wide on larger containers and becomes difficult to scan, like in the following example:</p>
<p><img src="https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/b1b4d7d4-89f6-4735-b41f-ea99353e685f/3-accessible-front-end-patterns-responsive-tables-part1.png"></p>
<pre><code>table {
  width: fit-content;
}
</code></pre>

<p>With the <code>fit-content</code> value, we ensure that the table doesn’t grow beyond the minimum width required to optimally display the table contents and that it remains responsive. </p>
<p>The table responds to viewport size, and it looks good on small screens, but on wider screens, it becomes difficult to scan due to the unnecessary space between the columns.</p>
<p><img src="https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/1eca8103-7278-4896-985b-5dc826f255f6/4-accessible-front-end-patterns-responsive-tables-part1.png"></p>
<p>We can also ensure that the table <code>max-width</code> value always adapts to its content. We don’t have to rely on assigning a magic number for each table or wrap the table in a container that constrains the width to a fixed value.</p>
<p><img src="https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/cec22d05-8c25-4850-82b7-a51d4086cb94/5-accessible-front-end-patterns-responsive-tables-part1.png"></p>
<p>This works well for simple tables that don’t require too much screen space to be effectively parsed and aren’t affected by <code>word-break</code>. We can even use <a href="https://www.smashingmagazine.com/2022/01/modern-fluid-typography-css-clamp/">fluid typography and fluid spacing</a> to make sure these simple tables remain readable on smaller screens.</p>
<pre><code>/* Values generated with Utopia https://utopia.fyi/type/calculator/ */

tbody {
  font-size: clamp(1.13rem, calc(0.35rem + 2.19vw), 1.75rem);
}

tbody td {
  padding-top: clamp(1.13rem, calc(0.35rem + 2.19vw), 1.75rem);
  padding-bottom:  clamp(2rem, calc(0.62rem + 3.9vw), 3.11rem);
}
</code></pre>

<p><img src="https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/539553b1-ed1d-41ec-9ed6-22fc3052fead/6-accessible-front-end-patterns-responsive-tables-part1.png"></p>
<p>This is important to know because on some devices, like smartphones and tablets, scrollbars aren’t visible right away, and users might get the impression that the table is not scrollable.</p>
<p><img src="https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/1c7d9e0e-9c8c-4b36-8afe-5ac1957f0fe7/7-accessible-front-end-patterns-responsive-tables-part1.png"></p>
<p>Lea Verou and Roman Komarov have suggested using <a href="https://lea.verou.me/2012/04/background-attachment-local/">“scrolling shadows”</a> to subtly indicate the scrolling direction using gradient background and <code>background-attachment</code> property. Using this property, we can set background gradient behavior when scrolling. We also use linear gradients as edge covers for shadows, so we gradually hide the shadow when the user has reached an edge and cannot scroll in that direction anymore.</p>
<div>
<pre><code>.table-wrapper {
  overflow: auto;
  background: 
    linear-gradient(90deg, var(--color-background) 20%, rgba(255, 255, 255, 0)),
    linear-gradient(90deg, rgba(255, 255, 255, 0), var(--color-background) 80%) 
                    100% 0,
    radial-gradient(farthest-side at 0 0%, var(--color-shadow), rgba(0, 0, 0, 0)),
    radial-gradient(farthest-side at 100% 0%, var(--color-shadow), rgba(0, 0, 0, 0))
                    100% 0;
  background-repeat: no-repeat;
  background-size: 20% 200%, 20% 200%, 8% 400%, 8% 400%;
  background-attachment: local, local, scroll, scroll;
}
</code></pre>
</div>

<p><img src="https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/729f2187-5dea-4b98-a400-9acc64410ebc/8-accessible-front-end-patterns-responsive-tables-part1.png"></p>
<p><img src="https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/c21cbde7-218e-42f7-bea7-c43c4f538ea7/9-accessible-front-end-patterns-responsive-tables-part1.png"></p>
<p>Keep in mind that <code>background-attachment</code> property is <a href="https://caniuse.com/background-attachment">not supported on iOS Safari</a> and a few other browsers, so make sure to either <strong>provide a fallback or remove the background on unsupported browsers</strong>. We can also provide helpful text next to the table to make sure users understand that the table can be scrolled.</p>
<p><img src="https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/06b8c18c-76d5-4388-a31e-614037d744ed/10-accessible-front-end-patterns-responsive-tables-part1.png"></p>
<h3>Forcing Table Cropping</h3>
<p>We can also dynamically set the table column width to enforce table cropping mid-content, so the user gets a clear hint that the table is scrollable. I’ve created a simple function for this example. The last column will always get cropped to 85% of its size, and we’ll reduce the number of visible columns by one if we cannot show at least 5% of the column’s width.</p>
<pre><code>function cropTable(visibleCols) {
  const table = document.querySelector("figure");
  const { width: tableWidth } = table.getBoundingClientRect();
  const cols = table.querySelectorAll("th, td");
  const newWidth = tableWidth / visibleCols;

  // Resize columns to fit a table.
  cols.forEach(function(col) {
    // Always make sure that col is cropped by at least 15%.
    col.style.minWidth = newWidth + (newWidth * 0.15) + "px";
  });

  // Return if we are about to fall below min column count.
  if (visibleCols &lt;= MIN_COLS) {
    return;
  }

  // Measure a sample table column to check if resizing was successful.
  const { width: colWidth } = cols[0].getBoundingClientRect();

  // Check if we should crop to 1 column less (calculate new column width).
  if (colWidth * visibleCols &gt; tableWidth + newWidth * 0.95) {
    cropTable(visibleCols - 1);
  }
}
</code></pre>

<p>This function might need to be adjusted to a more complex use case. Check the example below and see how the table column width responds to window resizing:</p>
<p><img src="https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/6cd98ee7-7acf-4146-9616-3e03a3102ea2/11-accessible-front-end-patterns-responsive-tables-part1.png"></p>
Stacking Approach (Rows To Blocks)
<p>The stacking approach has been a <strong>very popular pattern</strong> for years. It involves converting each table row into a block of vertically stacked columns. This is a very useful approach for tables where <strong>data is not comparable</strong> or when <strong>we don’t need to highlight the hierarchy</strong> and order between items. </p>
<p>For example, cart items in a webshop or a simple contacts table with details — these items are independent, and users primarily scan them individually and search for a specific item. </p>
<p>As mentioned before, converting the table rows to blocks usually involves applying <code>display: block</code> on small screens. However, as Adrian Roselli has noted, applying a <code>display</code> property <a href="https://adrianroselli.com/2018/02/tables-css-display-properties-and-aria.html">overrides native table semantics</a> and makes the element less accessible on screen readers. This discovery was jarring to me, as I’ve spent years crafting responsive tables using this pattern without realizing I was making them less accessible in the process.</p>
<p>It’s not all bad news, as Adrian Roselli notes the following <a href="https://adrianroselli.com/2018/02/tables-css-display-properties-and-aria.html#Update4">change for Chrome version 80</a>:</p>
<blockquote>Big progress. Chrome 80 no longer drops semantics for HTML tables when the <code>display</code> properties <code>flex</code>, <code>grid</code>, <code>inline-block</code>, or <code>contents</code> are used. The new Edge (ChromiEdge) follows suit. Firefox still dumps table semantics for only <code>display: contents</code>. Safari dumps table semantics for everything.<br><br>— Adrian Roselli</blockquote>

<p>For this example, we’ll use <code>display: flex</code> instead of using <code>display: block</code> for our stacking pattern. This is not an ideal solution as other browsers might still drop table semantics, so make sure to test the accessibility on various browsers and devices.</p>
<pre><code>/* Small screen width styles */
table, tbody, tbody tr, tbody td, caption {
  display: flex;
  flex-direction: column;
  width: 100%;
  word-break: break-all;
}
</code></pre>

<p>See the Pen <a href="https://codepen.io/smashingmag/pen/bGKBNNr">Table - stacked [forked]</a> by <a href="https://codepen.io/AdrianBece">Adrian Bece</a>.</p>
<h3>Accordion</h3>
<p>The stacking pattern might look nice initially and seems to be an elegant solution from a design perspective. However, depending on the table and data complexity, <strong>this pattern might significantly increase page height</strong>, and the user might have to scroll longer to reach the content below the table.</p>
<p>One improvement I found interesting was to <strong>show the primary data column</strong> (usually the first column) and <strong>hide the less important data (other columns) under an accordion</strong>. This makes sense for our example, as users would first look for a name by contact and then scan for their details in the row.</p>
<pre><code>&lt;tr&gt;
  &lt;td onclick="toggle()"&gt;
    &lt;button aria-label="Expand contact details"&gt;
      &lt;!-- Icon --&gt;
    &lt;/button&gt;
    &lt;!-- Main content--&gt;
  &lt;/td&gt;
  &lt;td&gt;&lt;!-- Secondary content--&gt;&lt;/td&gt;
  &lt;td&gt;&lt;!-- Secondary content--&gt;&lt;/td&gt;
  &lt;td&gt;&lt;!-- Secondary content--&gt;&lt;/td&gt;
&lt;/tr&gt;
</code></pre>

<p>We’ll assume that the first table column contains primary data, and we’ll hide other columns unless a <code>row-active</code> class is applied:</p>
<pre><code>/* Small screen width styles */

thead tr &gt; *:not(:first-child) {
  display: none;
}

tbody,
tbody tr,
tbody td {
  display: flex;
  flex-direction: column;
  word-break: break-all;
}

tbody td:first-child {
  flex-direction: row;
  align-items: center;
}

tbody tr:not(.row-active) &gt; *:not(:first-child) {
  max-width: 0;
  max-height: 0;
  overflow: hidden;
  padding: 0;
}
</code></pre>

<p>Now we have everything in place for showing and hiding table row details. We also need to keep in mind the screen reader support and toggle the <code>aria-hidden</code> property to hide secondary info from screen readers. We don’t need to toggle the ARIA property if we’re toggling the element visibility with the <code>display</code> property:</p>
<pre><code>function toggle() {
  const row = this.window.event.target.closest("tr");
  row.classList.toggle("row-active");

  const isActive = row.classList.contains("row-active");

  if (isActive) {
    const activeColumns = row.querySelectorAll("td:not(:first-child)");
    activeColumns.forEach(function (col) {
      col.setAttribute("aria-hidden", "false");
    });
  } else {
    const activeColumns = row.querySelectorAll(`td[aria-hidden="false"]`);
    activeColumns.forEach(function (col) {
      col.setAttribute("aria-hidden", "true");
    });
}
</code></pre>

<p>We’ll assign this function to the <code>onclick</code> attribute on our main table column elements to make the whole column clickable. We also need to assign proper ARIA labels when initializing and resizing the window. We don’t want incorrect ARIA labels applied when we resize the screen between two modes.</p>
<pre><code>function handleResize() {
  const isMobileMode = window.matchMedia("screen and (max-width: 880px)");
  const inactiveColumns = document.querySelectorAll(
    "tbody &gt; tr &gt; td:not(:first-child)"
  );

  inactiveColumns.forEach(function (col) {
    col.setAttribute("aria-hidden", isMobileMode.matches.toString());
  });
}

//On window resize
window.addEventListener("resize", handleResize);

// On document load
handleResize();
</code></pre>

<p>See the Pen <a href="https://codepen.io/smashingmag/pen/dyKOYVr">Table - accordion [forked]</a> by <a href="https://codepen.io/AdrianBece">Adrian Bece</a>.</p>
<p>This approach significantly reduces table height on smaller screens compared to the previous example. The content below the table would now easily be reachable by quickly scrolling past the table.</p>
Toggleable Columns Approach
<p>Going back to our scrollable table example, in some cases, we can <strong>give users an option to customize the table</strong> <strong>view</strong> by allowing them to show and hide individual columns, temporarily reducing table complexity in the process. This is useful for users that want to <strong>scan or compare data only by specific columns</strong>.</p>
<p>We’ll use a checkbox form and have them run a JavaScript function. We’ll only have to pass an index of the column that we want to toggle. We’ll have to hide both the columns in data rows in a table body and a table header element:</p>
<pre><code>function toggleRow(index) {
  // Hide a data column for all rows in the table body.
  allBodyRows.forEach(function (row) {
    const cell = row.querySelector(`td:nth-child(${index + 1})`);
    cell.classList.toggle("hidden");
  });

  // Hide a table header element.
  allHeadCols[index].classList.toggle("hidden");
}
</code></pre>

<p>This is a neat solution if you want to avoid the stacking pattern and allow users to easily compare the data but give them options to reduce the table complexity by toggling individual columns. In this case, we’re using a <code>display</code> property to toggle the visibility, so we don’t have to handle toggling ARIA labels.</p>
<p>See the Pen <a href="https://codepen.io/smashingmag/pen/RwJoWQb">Responsive table - column toggle [forked]</a> by <a href="https://codepen.io/AdrianBece">Adrian Bece</a>.</p>
Conclusion
<p>Table complexity and design depend on the use case and the data they display. They generally rely on having enough screen space to display columns in a way user can easily scan them. There is no universal solution for making tables responsive and usable on smaller screens for all these possible use cases, so we have to rely on various patterns.</p>
<p>In this article, we’ve covered a handful of these patterns. We’ve focused primarily on simple design changes with a scrolling table pattern and a stacking pattern and began checking out more complex patterns that involve adding some JavaScript functionality. </p>
<p>In the next article, we’ll explore more specific and complex responsive table patterns and check out some responsive table libraries that add even more useful features (like <strong>filtering</strong> and <strong>pagination</strong>) to tables out of the box.</p>
<h3>References</h3>
<ul>
<li>“<a href="https://adrianroselli.com/2018/02/tables-css-display-properties-and-aria.html">Tables, CSS Display Properties, And ARIA</a>”, Adrian Roselli</li>
<li>“<a href="https://adrianroselli.com/2017/11/a-responsive-accessible-table.html">A Responsive Accessible Table</a>”, Adrian Roselli</li>
<li>“<a href="https://adrianroselli.com/2018/05/functions-to-add-aria-to-tables-and-lists.html">Functions To Add ARIA To Tables And Lists</a>”, Adrian Roselli</li>
<li><a href="https://inclusive-components.design/data-tables/">Data Tables</a>, Heydon Pickering</li>
<li>“<a href="https://www.smashingmagazine.com/2019/01/table-design-patterns-web/">Table Design Patterns On The Web</a>”, Chen Hui Jing</li>
<li>“<a href="https://lea.verou.me/2012/04/background-attachment-local/">Pure CSS Scrolling Shadows With background-attachment: local</a>”, Lea Verou</li>
<li><a href="https://www.dropbox.com/paper/ep/redirect/external-link?url=https%3A%2F%2Fwww.w3.org%2FWAI%2Ftutorials%2Ftables%2Fcaption-summary%2F&amp;hmac=UIbB%2BWBJu82LS4Aq3j%2Bc2mDo5wZzKF50%2F7P%2F%2BdH%2Bu%2BM%3D">Tables Caption &amp; Summary</a>, Web Accessibility Initiative</li>
<li><a href="https://www.w3.org/WAI/tutorials/tables/tips/">Tables Tips And Tricks</a>, Web Accessibility Initiative</li>
</ul>
]]></description>
      <pubDate>Tue, 06 Dec 2022 12:00:00 +0000</pubDate>
      <link>https://smashingmagazine.com/2022/12/accessible-front-end-patterns-responsive-tables-part1/</link>
      <dc:creator>Articles on Smashing Magazine — For Web Designers And Developers</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/3906011523</guid>
    </item>
    <item>
      <title><![CDATA[Browsers, JSON, and FormData]]></title>
      <description><![CDATA[<p><a href="https://adactio.com/journal/19573">Jeremy wrote</a> about what a boon the new view transitions API could be for the web:</p>
<blockquote>
<p>If we get a View Transitions API that works across page navigations, it could potentially turbo-charge the web. It will act as a disincentive to building single page apps—you’d be able to provide swish transitions without sacrificing performance or resilience at the alter of a heavy-handed JavaScript-only architecture.</p>
</blockquote>
<p>While getting browser-native support for transitions could act as an antidote to the JavaScript-heavy SPA experiences common today, I think there’s another equally significant change that could decrease the amount of JavaScript shipped to modern websites: we need either 1) more APIs that speak <a href="https://developer.mozilla.org/en-US/docs/Web/API/FormData">FormData</a>, or 2) browsers that speak JSON.</p>
<p>Allow me to try and explain.</p>
<h2 id="json-requires-javascript">JSON Requires JavaScript</h2>
<p>A big impediment to moving away from SPAs is that we’ve collectively spent years building up an entire infrastructure of backend services which power the internet by communicating with browsers in JSON —&nbsp;a format that is <em>not</em> the default method of communication for web browsers. Browsers cannot communicate user actions over the network as JSON <em>without</em> the use of JavaScript. It’s impossible.</p>
<p>What do I mean by this?</p>
<p>By default, browsers make requests via HTTP methods (GET, POST). If a server responds with HTML, the browser knows how to render a UI from that response. If a server responds with JSON, the browser can’t render any UI unless it has some corresponding JavaScript instructions telling it how. JSON <em>requires</em> JavaScript to render any kind of user interface. HTML does not. Browsers know how to turn HTML into human-readable, user-actionable interfaces with or without JavaScript.</p>
<p>In this way, browsers communicate and render GUIs at their most basic level in HTML via HTTP. </p>
<ul>
<li><code>&lt;a&gt;</code> element clicked? That’s an HTTP GET for another resource whose response, if HTML, will render something new in the browser.</li>
<li><code>&lt;form&gt;</code> element submitted? That’s an HTTP <a href="https://stackoverflow.com/questions/8054165/using-put-method-in-html-form">GET or POST</a> <em>with user-supplied data</em> for another resource whose response, if HTML, will render something new in the browser.</li>
</ul>
<p>The <code>&lt;form&gt;</code> element in HTML allows users to supply data to the browser and send it to a server without the use of JavaScript. Browsers do this on their own by sending a request, <em>not</em> as <code>application/json</code>, but as <code>application/x-www-form-urlencoded</code> (<a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#attr-enctype">the default MIME type of a form submission</a>). To send an HTTP request as JSON requires that the client have JavaScript enabled and working (bug free).</p>
<p>To reiterate: <strong>every request a browser makes with a JSON payload requires functioning JavaScript</strong> — every newsletter signup, every contact inquiry, every account creation request, every e-commerce purchase, every <em>you name it</em>. No exceptions.</p>
<p>That’s pretty wild when you think about it. Why are there <em>so many</em> APIs that communicate solely in JSON on the web when browsers can’t even POST in JSON without JavaScript? Granted, many APIs probably aren’t designed to communicate directly with browsers — but many are and yet they only speak JSON! In those cases, that is assuming a lot by not even communicating in the lingua franca of the browser.</p>
<p>I can’t help but think: only once we start building web APIs that speak in the default communication idiom of browsers will be able to break out of a complete reliance on JavaScript.</p>
<p>To put it bluntly: there’s no progressive enhancement if all your APIs only accept JSON payloads directly from the browser.</p>
<p>If you stopped shipping all JavaScript to the client tomorrow, would the backend services you depend on support receiving information via the browser’s default mechanism for sending user data?</p>
<h2 id="a-simple-example">A Simple Example</h2>
<p>Imagine I have a simple static site with a call to action to sign up for my newsletter. I drop a static HTML file on a web server with some <code>&lt;form&gt;</code> markup which the browser will use to render a GUI where the user can enter their information.</p>
<pre><code>&lt;form&gt;
  &lt;label for="email"&gt;
    Sign Up For My Newsletter
  &lt;/label&gt;
  &lt;input type="email" id="email" /&gt;
  &lt;button type="submit"&gt;
    Sign Up
  &lt;/button&gt;
&lt;/form&gt;
</code></pre>
<p>Now I need a server <em>somewhere</em> to collect this information and store it. </p>
<p>So many third-party services have APIs you can integrate with and guess what? They probably speak JSON exclusively. But browsers don’t send user-entered data as JSON by default, so what’s one to do? You can:</p>
<ol>
<li>Create a URL that accepts the structure of a default browser POST request (<code>&lt;form method="post" action="/my-url"&gt;</code>).</li>
<li>Throw progressive enhancement out the door, expect JavaScript to work for all your users, and drop a <code>&lt;script&gt;</code> tag on the page somewhere that handles the logic to submit a JSON payload to whatever service will store this information for you.</li>
</ol>
<p>While I love JAMStack and everything it has done to simplify and make more accessible many aspects of web development, this is a trade-off that cuts deep. Funnily enough, the trade-off is in the very name: JAM standing for JavaScript, APIs, and Markup. In other words, it assumes — even implicitly requires — JavaScript as a functioning dependency in the browser<sup id="fnref:1"><a href="https://blog.jim-nielsen.com/2022/browsers-json-formdata/#fn:1">[1]</a></sup>.</p>
<p>Once you need to collect one simple piece of data from a user on a “static” web page — like <code>&lt;input type="email" /&gt;</code> — you either 1) now need to control a server that accepts <code>application/x-www-form-urlencoded</code> payloads, or 2) need to require JavaScript for your page to work.</p>
<p>Quick plug: this is where a framework like Remix which supports progressive enhancement really shines. Remix makes it incredibly easy to support <code>&lt;form&gt;</code> submissions as the browser default <code>application/x-www-form-urlencoded</code> type <em>or</em> as a JavaScript-enhanced <code>application/json</code> type. <a href="https://remix.run/docs/en/v1/guides/data-writes#html-form-post">Read the docs for more info</a>.</p>
<h2 id="conclusion">Conclusion</h2>
<p>In a recent article, Remy Sharp comments that <a href="https://remysharp.com/2022/10/13/two-javascripts">a form should be postable without JavaScript</a> which, as noted above, means that  an associated endpoint that speaks more than just JSON is required.</p>
<blockquote>
<p>A form should be postable without JavaScript. For instance, I'm sending you my contact details - if JavaScript broke (bad connection) or is disabled (you're spamming my face with subscribe notices), then a form should post details to the server and then reload with page like "thanks for you deets". Not rocket science.</p>
<p>Try to implement that using Next.js…and I can't think…how. You can post to "api endpoint", but that'll return JSON…I'm sure there's a work around, but the point is that it has to be worked around, intentionally.</p>
</blockquote>
<p>I think until we start building our APIs to speak in more than just JSON — or we get browsers to speak JSON without requiring JavaScript<sup id="fnref:2"><a href="https://blog.jim-nielsen.com/2022/browsers-json-formdata/#fn:2">[2]</a></sup> — it’s going to be difficult for progressively-enhanced web pages to gain traction over JavaScript rendered apps.</p>
<hr><ol class="footnotes"><li id="fn:1">To their credit, the folks at Netlify support <a href="https://docs.netlify.com/forms/setup/"><code>&lt;form&gt;</code> submissions without JavaScript</a> though I would hazard a guess that feature isn’t used nearly as often as a JavaScript <code>e.preventDefault()</code> on the form submission and the sending of a JSON payload instead. Third-party JSON APIs are so often the recipients of user data entered directly in the browser. If anybody out there is using Netlify’s <code>&lt;form netlify&gt;</code> feature to support progressive enhancement, hit me up with the details on how you’re doing it! <a href="https://blog.jim-nielsen.com/2022/browsers-json-formdata/#fnref:1" title="Jump back to footnote 1 in the text.">↩</a></li><li id="fn:2">AFAIK, there’s no way (currently) to declaratively describe a form in HTML that makes a request with JSON (though <a href="https://www.w3.org/TR/html-json-forms/">there appears to have been an effort to do so at one point</a>). <a href="https://blog.jim-nielsen.com/2022/browsers-json-formdata/#fnref:2" title="Jump back to footnote 2 in the text.">↩</a></li></ol>
    <hr>
    <ul>
      <li>
        Reply via:
        
        <a href="mailto:jimniels%2Bblog@gmail.com?subject=Re:%20blog.jim-nielsen.com/2022/browsers-json-formdata/">Email</a>, <a href="https://twitter.com/jimniels">Twitter</a>
      </li>
      
      <li>
            Related posts linking here:
            <a href="https://blog.jim-nielsen.com/2022/remix-alternate-timeline-of-web-dev/">Remix and the Alternate Timeline of Web Development</a>
          </li>
    </ul>
    <br>
  ]]></description>
      <pubDate>Wed, 09 Nov 2022 15:13:12 +0000</pubDate>
      <link>https://blog.jim-nielsen.com/2022/browsers-json-formdata/</link>
      <dc:creator>Jim Nielsen’s Blog</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/3869691827</guid>
    </item>
    <item>
      <title><![CDATA[Website Fidelity]]></title>
      <description><![CDATA[<p>Kyle Simpson has a talk titled <a href="https://www.youtube.com/watch?v=SiYFh7wutTI">“FOUC, and the Death of Progressive Enhancement”</a> wherein he suggests the idea of building websites in layers of fidelity instead of layers of technology.</p>
<p class="image-container"><img src="https://cdn.jim-nielsen.com/blog/2022/fidelity-ks-slide-degrees-fidelity.png" width="1012" height="566" alt="Screenshot from the YGLF conference recording showing Kyle Simpson on stage with a slide noting we should build things in “Degress of fidelity”.">

</p><p>He argues that building websites is not about clinging to an ideal like the separation of concerns: markup separate from styles separate from scripts. Instead, it’s about the humans — about building technology solutions that can defer to user preferences. What we humans want and need varies from one moment to the next, from one human to the next, and we need technology that accommodates that.</p>
<p>As a simple example, many news sites offer separate, trimmed-down versions of their sites:</p>
<ul>
<li><a href="https://text.npr.org/">text.npr.org</a></li>
<li><a href="https://lite.cnn.com/">lite.cnn.com</a></li>
<li><a href="https://lite.pbs.org/">lite.pbs.org</a></li>
</ul>
<p>These are great because they recognize the one-size-fits-all approach to providing a website doesn’t cater to the vast needs and preferences of users.</p>
<p>Not only do demographics vary wildly, but within and across demographics needs are always changing and contextual to attributes of a user’s current state like environment, device, or capability. For example, <a href="https://twitter.com/NPR/status/1040231625571758086">NPR notes</a> the purpose (one of many, I’m sure) of their text-only website:</p>
<blockquote>
<p>We have a text-only version of our website for anyone who needs to stay up-to-date on Hurricane Florence news and keep their battery and data usage to a minimum</p>
</blockquote>
<p>Somebody visiting <code>text.npr.org</code> to preserve battery and decrease bandwidth because they’re in a Hurricane disaster zone probably needs a lite version for <em>every</em> website they visit, not just NPR.</p>
<p>What Kyle suggests is a world where you don’t need remember separate domains based on the experience you want. Rather, you visit the same domains you always visit, but with the ability to express a preference for the fidelity of experience you want/need in any given moment.</p>
<p>Kyle gives an example of how this could work: a browser-level control to indicate the “fidelity” of the experience you want, e.g. how much bandwidth, CPU, and battery are required to load and interact with the page (note the slider at the top where the URL bar is).</p>
<p class="image-container"><img src="https://cdn.jim-nielsen.com/blog/2022/fidelity-ks-slide-fidelity-slider.png" width="1013" height="566" alt="Screenshot from the YGLF conference recording showing Kyle Simpson on stage with a slide showing a proof of concept “fidelity slider” for New York Times’ website.">

</p><p>In other words, rather than going to <code>text.npr.org</code> when you want a lean experience, you always go to <code>npr.org</code> but you set your “fidelity preference” to “low”. In theory, this sends a header to NPR indicating you want a “low fidelity” version of the website, e.g. text-only.</p>
<p class="image-container"><img src="https://cdn.jim-nielsen.com/blog/2022/fidelity-ks-slide-headers.png" width="1013" height="567" alt="Screenshot from the YGLF conference recording showing Kyle Simpson on stage with a slide showing a theoretical “request-fidelity” header highlighted in the developer tools.">

</p><p>From there, it’s up to the site owners to figure out how that user preference maps to an available version of the site. Kyle illustrates this with an example routing configuration.</p>
<p class="image-container"><img src="https://cdn.jim-nielsen.com/blog/2022/fidelity-ks-slide-fidelity-json.png" width="1013" height="568" alt="Screenshot from the YGLF conference recording showing Kyle Simpson on stage with a slide showing a routing configuration for different mappings of a “fidelity slider” header value to different versions of a website.">

</p><p>Granted, there would have to be some kind of rough consensus about what this spectrum of fidelity means. For example, “0” means HTML only: no styles or scripts (inline or external) or even inline images. From there, you build up. </p>
<p>A site may intentionally provide a fidelity that, on the generalized scale may only be a “0.4” but is the highest fidelity offered. That’s a good thing, it means their site will be fast by default, even if the user’s preferred setting across sites is “0.8”.</p>
<p>The thing is, we don’t have to wait for browsers to adopt this. As Kyle suggests, we can start now.</p>
<p class="image-container"><img src="https://cdn.jim-nielsen.com/blog/2022/fidelity-ks-slide-wait.png" width="1012" height="566" alt="Screenshot from the YGLF conference recording showing Kyle Simpson on stage with a slide saying we shouldn’t wait for browsers to adopt a fidelity in website experiences.">

</p><p>Granted, a browser-level feature would be the ideal here, as it would allow a persistent experience of fidelity across sites. Imagine going from CNN to NPR to PBS and maintaining a (roughly) similar experience of fidelity across websites!</p>
<p class="image-container"><img src="https://cdn.jim-nielsen.com/blog/2022/fidelity-example-across-sites.png" width="948" height="1325" alt="Three mobile screenshots of NPC, CNN, and PBS as text-only websites, with a “fidelity slider” in the browser set to low. From there, they flow into three mobile screenshots of the same websites but with a high “fidelity slider” and the websites have styles, images, and text.">

</p><h2 id="a-quick-aside-on-css">A Quick Aside on CSS</h2>
<p>Experiencing various appearances of a website — i.e. keep the same markup but only change the stylesheet — is cool, like <a href="http://www.csszengarden.com/">CSS Zen Garden</a>.</p>
<p>But that’s not what I’m trying to get at here. I’m not talking about varying <em>styles</em> based on user preference. I’m talking about varying <em>experiences</em> based on user preference. The styles change, but so too does the HTML and the JavaScript.</p>
<p>While I love the idea of a media feature like <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-data">@prefers-reduced-data</a>, the fact is: if someone really wants a leaner experience of your website, you need to know that at request time. That’s a preference that, in my opinion, transcends CSS. A preference for reduced data means less resources, including HTML, CSS, and JavaScript. How do you control a preference for reduced data in CSS alone?</p>
<p>For example, one end of fidelity is <code>0</code>. To me, this is pure HTML with no embedded or inlined resource dependencies. No styles. No JavaScript. Not even an <code>&lt;img&gt;</code> tag. We’re talking the most lean experience you can provide. If you opened the console, you’d only see one request: the <code>.html</code> file.</p>
<p>How would you handle <code>&lt;img&gt;</code> then? Hyperlink it! Instead of:</p>
<p><code>&lt;img src="elephant.jpg" alt="An image of an elephant." /&gt;</code></p>
<p>You do:</p>
<p><code>&lt;a href="elephant.jpg"&gt;[An image of an elephant.]&lt;/a&gt;</code></p>
<p>You can’t do that with <code>@prefers-reduced-data</code>. That’s why the idea of fidelity as a preference in a request header seems crucial. Not just customizeable themes, but customizeable experiences.</p>
<h2 id="my-implementation">My Implementation</h2>
<p>Given all the above, I wanted to try this out as a site-level preference on my blog. I implemented it as three separate preferences: Default, Minimal, and Text-Only.</p>
<p class="image-container"><img src="https://cdn.jim-nielsen.com/blog/2022/fidelity-preference-control.png" width="708" height="257" alt="Screenshot of the fidelity preference pane on my blog, showing options for Default, Minimal, and Text-Only">

</p><p>The controls for this fidelity preference vary, based on which version of the site you’re looking at.</p>
<p class="image-container"><img src="https://cdn.jim-nielsen.com/blog/2022/fidelity-preference.png" width="1248" height="447" alt="Screenshot of the fidelity preference pane on my blog, set in varying degrees of fidelity: from a stylized version down to an HTML-only version.">

</p><p>“Default” is the experience you get if you have no preference set (how the web works today). “Minimal” is set around the core experience of reading the site: no JavaScript and just a few basic styles. Text-only is my theoretical “fidelity 0”: no styles, no JavaScript, no inline images, as small a site as can be.</p>
<p class="image-container"><img src="https://cdn.jim-nielsen.com/blog/2022/fidelity-images-example.png" width="1282" height="623" alt="Screenshot of my blog in Default, Minimal, and Text-Only fidelity (with the Text-Only fidelity showing how inline images were removed).">

</p><p>Because I wanted this feature to work without JavaScript — that is, after all, part of the essence of the feature — it works by setting a cookie which persists across requests. Choosing a preference sets a cookie and the server knows which version of the site to serve based on the presence of a given cookie (a browser-level request header in the future would be nice, something that’s present upon every client request).</p>
<h3 id="technical-details">Technical Details</h3>
<p>In my case, I am using Netlify and a static site generator to serve my blog, so I wanted to try accomplishing this feature with those constraints.</p>
<p>In essence, it works by having three versions of my site. The first version (the “Default” version) is in the root folder while the other two versions are nested in named folders. Example:</p>
<pre><code>.
├── index.html
├── _fidelity/
     ├── low/
     │    └── index.html
     └── med/
          └── index.html
</code></pre>
<p>Using <a href="https://docs.netlify.com/routing/redirects/redirect-options/#redirect-by-cookie-presence">Netlify’s redirects</a>, I route requests to the appropriate version of the site based on the presence of a cookie.</p>
<pre><code>[[redirects]]
  from = "/*"
  to = "/_fidelity/low/:splat"
  status = 200
  force = true
  conditions = { Cookie = ["fidelity-low"] }

[[redirects]]
  from = "/*"
  to = "/_fidelity/med/:splat"
  status = 200
  force = true
  conditions = { Cookie = ["fidelity-med"] }
</code></pre>
<p>This is a little convoluted, but again, working within the constraints I have I think it works. (Fortunately, my site is pretty basic.)</p>
<p>As for the different “fidelities” of the site, I accomplished this using a form of backwards progressive enhancement: I generate the “Default” version of my site and then strip features out in stages to lower the fidelity. (Thank goodness I built my blog in a progressively-enhanced way, or I could not have done this without breaking the site!)</p>
<p>For example, to generate the “Minimal” version of the site, I take the “Default” version, strip out tags like <code>&lt;script&gt;</code>, <code>&lt;style&gt;</code>, <code>&lt;link rel=stylesheet&gt;</code>, etc., and inject a separate <a href="https://github.com/jimniels/blog/blob/dc40caf048a4812ee8b80500154486ea35865376/src/server/styles/basic.css">“basic” stylesheet</a> (~25 lines of CSS, inspired by <a href="https://www.robinrendle.com/notes/the-smallest-css/">Robin Rendle</a> and <a href="https://web.archive.org/web/20200807101742/https://frankchimero.com/blog/2020/redesign-this-design/">Frank Chimero</a>).</p>
<p>To generate the “Text-Only” version, I take the “Minimal” version, strip out the remaining styles, and swap out inline images for inline links.</p>
<p>This leaves me with three experiences of my site, which I would summarize as:</p>
<ul>
<li><strong>Default</strong>: All the bells and whistles (yet still lean and mean). Custom theme selection, styled motifs, and any extras — including JavaScript.</li>
<li><strong>Minimal</strong>: Basic enhancements to the core reading experience, including inline images and a base stylesheet for improved layout, but otherwise nothing else (no JavaScript).</li>
<li><strong>Text-Only</strong>: Leanest experience possible. No styles, interactivity, or inline images. Saves bandwidth, CPU, and battery.</li>
</ul>
<p>I’m using Metalsmith as my static site generator, so you can <a href="https://github.com/jimniels/blog/blob/dc40caf048a4812ee8b80500154486ea35865376/metalsmith.js#L31-L102">peak at the code if you want</a>. The above approach results in a slower build that I would prefer as I accomplish generating each version of the site by creating each page as a JSDOM document and using <code>.querySelector</code> to strip stuff out.</p>
<p>In the future, I’ll probably generate the site by running my templating pass three distinct times with a configuration value like <code>fidelity=(low|med|default)</code> and, based on the presence of that value, render things appropriately. Example:</p>
<pre><code>&lt;head&gt;
  ${fidelity === 'default' &amp;&amp;
    `&lt;link
      rel=stylesheet
      href=/styles/default.css
    /&gt;`
  }
  ${fidelity === 'med' &amp;&amp;
    `&lt;link
      rel=stylesheet
      href=/styles/basic.css
    /&gt;`
  }
  &lt;!-- No styles if fidelity === 'low' --&gt;
&lt;/head&gt;
</code></pre>
<p>Admittedly, this would be simpler if I was using a server to generate pages at request time. Perhaps I’ll go that direction someday – in the meantime, <a href="https://answers.netlify.com/t/redirect-based-on-cookie-value/75478/4">I’m working with what I’ve got</a> in static file hosting.</p>
<p>There’s a lot more I could write about this, but maybe I’ll split those out into different posts. If you decide to try this out on your site, hit me up with a link!</p>

    <br>
    <br>
    /* Reply via
    <a href="mailto:jimniels+blog@gmail.com?subject=Re:%20blog.jim-nielsen.com/2022/website-fidelity/">email</a>
    or <a href="https://twitter.com/jimniels">twitter</a>.
    
    */
    <br>
    <br>
  ]]></description>
      <pubDate>Mon, 17 Oct 2022 14:47:19 +0000</pubDate>
      <link>https://blog.jim-nielsen.com/2022/website-fidelity/</link>
      <dc:creator>Jim Nielsen’s Blog</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/3839629927</guid>
    </item>
    <item>
      <title><![CDATA[Solving “The Dangler” Conundrum with Container Queries and :has()]]></title>
      <description><![CDATA[<p><img src="https://daverupert.com/images/posts/2022/dangler.png" alt="Two two-column grids of three items. The first grid shows a third item dropping down to a third row with unwanted whitespace to its right. This grid has a thumbs down overlayed on it. The second grid shows the first item going full width and two grid items below. This grid has a thumbs up overlayed on it."></p>
<p>Y’know that situation where you tell the client, “Here’s your website and you can edit those four (4) little homepage features in the CMS” and the client says “Okay okay okay” and you check the site a week later and it looks bad because the client —despite your incredible documentation— put an odd number of items in the feature grid. It’s a major minor problem that’s tough to explain to the client, but it all comes down to…</p>
<p>The dangler.</p>
<p>Odd number grids are a sore point in Responsive Design. Even numbered grids collapse easily from 4 columns → 2 columns → 1 column, but odd numbered grids leave you with one extra column to figure out. You have two choices: drop the odd column to its own row or take <em>the coward’s route</em> and convert 3 columns → 1 column prematurely at ~800px. If you take that route, two more choices appear: awkwardly center the content or awkwardly stretch all the items out. This is how painful-looking tablet views are born.</p>
<p>You can design your way out of the dangler conundrum. What if we could detect odd numbered columns and make the first item full-width? And then, what if we could give it a horizontal treatment when there’s extra space?</p>
<h2>Using <code>:has()</code> to find odd-numbered grids 🪄</h2>
<p><a href="https://shoptalkshow.com/520/">Jeffrey Zeldman, Eric Meyer, Chris Coyier, and I were talking on Shoptalk</a> about how the new CSS feature <code>:has()</code> potentially changes how we do CSS. “Contextual Web Design”, I called it. We drummed up some examples but I’ll confess I’m still wrapping my head around what <code>:has()</code> can do. <a href="https://blog.jim-nielsen.com/2022/unlocked-possibilities-of-has-selector/">Jim Nielsen came up with some ideas for <code>:has()</code></a> and that got the gears turning in my brain.</p>
<p>The algorithm I’m going after is pretty simple: <em>If</em> the grid of items has an odd number of items, <em>then</em> make the first item full-width. But CSS can’t do logic… right? Well… hold my proverbial beer.</p>
<div class="language-scss highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="scss"><span class="nc">.items</span> <span class="p">{</span>
	<span class="nl">display</span><span class="p">:</span> <span class="n">grid</span><span class="p">;</span>
	<span class="na">grid-template-columns</span><span class="p">:</span> <span class="nf">repeat</span><span class="p">(</span><span class="n">auto-fit</span><span class="o">,</span> <span class="nf">minmax</span><span class="p">(</span><span class="m">200px</span><span class="o">,</span> <span class="m">1fr</span><span class="p">));</span>
	<span class="na">gap</span><span class="p">:</span> <span class="m">5vw</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">@media</span> <span class="p">(</span><span class="n">max-width</span><span class="o">:</span> <span class="m">800px</span><span class="p">)</span> <span class="p">{</span>
	<span class="nc">.items</span> <span class="p">{</span>
    <span class="na">grid-template-columns</span><span class="p">:</span> <span class="m">1fr</span> <span class="m">1fr</span><span class="p">;</span>
  <span class="p">}</span>
	
	<span class="nc">.items</span><span class="nd">:has</span><span class="o">(</span><span class="nc">.item</span><span class="nd">:last-of-type:nth-of-type</span><span class="o">(</span><span class="nt">odd</span><span class="o">))</span> <span class="nc">.item</span><span class="nd">:first-of-type</span> <span class="p">{</span>
		<span class="nl">grid-column</span><span class="p">:</span> <span class="m">1</span> <span class="o">/</span> <span class="m">-1</span><span class="p">;</span>
	<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>There’s two bits of magic going on. I’m using <code>auto-fit</code> instead of <code>auto-fill</code> on the grid to make sure our grid doesn’t create any&nbsp;ghost columns 👻. And then the most essential bit of code is the <code>:has()</code> selector statement.</p>
<div class="language-scss highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="scss"><span class="nc">.items</span><span class="nd">:has</span><span class="o">(</span><span class="nc">.item</span><span class="nd">:last-of-type:nth-of-type</span><span class="o">(</span><span class="nt">odd</span><span class="o">))</span> <span class="nc">.item</span><span class="nd">:first-of-type</span> <span class="p">{</span>
	<span class="nl">grid-column</span><span class="p">:</span> <span class="m">1</span> <span class="o">/</span> <span class="m">-1</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The result almost reads as plain English. This will select item grids where the last item is odd (read: it’s an odd-numbered grid) and then set the first item to span the full-width from column <code>1</code> (the start) to column <code>-1</code> (the end). 🎉&nbsp;Tada! Magic.</p>
<h2>Using Container Queries to flex differently</h2>
<p>Now that we can make an item go full-width, we don’t want to create a “top dangler” problem, that would be even worse than a “bottom dangler”. Ideally, we can apply a different treatment to give this element a horizontal treatment when its container is wide.</p>
<p>This sounds like a job for Container Queries! <em>If</em> the item container is &gt;400px (the image size), <em>then</em> make the item’s inner contents display as a horizontal row.</p>
<div class="language-scss highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="scss"><span class="nc">.item</span> <span class="p">{</span>
  <span class="na">container-name</span><span class="p">:</span> <span class="n">item</span><span class="p">;</span>
  <span class="na">container-type</span><span class="p">:</span> <span class="n">inline-size</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">@container</span> <span class="nt">item</span> <span class="o">(</span><span class="nt">min-width</span><span class="nd">:</span> <span class="nt">400px</span><span class="o">)</span> <span class="p">{</span>
  <span class="nc">.item_inner</span> <span class="p">{</span>
    <span class="nl">display</span><span class="p">:</span> <span class="n">flex</span><span class="p">;</span>
    <span class="nl">flex-direction</span><span class="p">:</span> <span class="n">row</span><span class="p">;</span>
    <span class="nl">align-items</span><span class="p">:</span> <span class="nb">center</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>And there you go! A card-like item that changes its presentation based on its width, independent of the viewport and contextual to its parent container<sup class="footnote-ref"><a href="https://daverupert.com/2022/07/solving-the-dangler-conundrum-with-has-and-container-queries/#fn1" id="fnref1">1</a></sup>.</p>
<h2>Putting it all together</h2>
<p>On their own each of these tricks is great. But together, they create something special: a content machine that applies styles conditionally based on quantity and size of the content inside. I <a href="https://codepen.io/davatron5000/pen/VwXWbRM">made a demo of this in CodePen</a> so you can give it a squeeze.</p>
<p>⚠️&nbsp;As of July 2022, this only works in Safari Technical Preview and Edge/Chrome Canary.</p>
<iframe height="500" style="width: 100%;" scrolling="no" title="Solving &quot;The Dangler&quot; Conundrum with Container Queries and :has()" src="https://codepen.io/davatron5000/embed/VwXWbRM?default-tab=result" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen="true">
  See the Pen <a href="https://codepen.io/davatron5000/pen/VwXWbRM">
  Solving &quot;The Dangler&quot; Conundrum with Container Queries and :has()</a> by Dave Rupert (<a href="https://codepen.io/davatron5000">@davatron5000</a>)
  on <a href="https://codepen.io">CodePen</a>.
</iframe>
<p>Container Queries are a real gift here. In fact, Container Queries solved a problem I hadn’t even considered… what if the client entered two (2) items in the CMS and then someone visits on desktop. Thankfully our <code>auto-fit</code> grid + Container Queries handle that edge case without breaking a sweat, filling the space in the least awkward manner possible. Best of all, this technique progressively enhances, so older browsers get the old dangler experience.</p>
<p>Making the first item full-width is an old RWD trick; it’s worth noting you <em>can</em> do this with some JavaScript, or some class soup, or something like <code>&lt;div class="items" data-count="3"&gt;</code> on your item grid. That’s fine, but I think you’ll feel the pain of coupling and cramming a lot of logic in your item grid component. Even though I’m good at CSS, I tend to avoid styling the first child like this because of the layout complexity it introduces…</p>
<p>But now… now the complexity is almost gone thanks to <code>:has()</code> and Container Queries. What a gift. We truly are entering a new era for CSS, an era where your content informs your layout, not the other way around.</p>
<section class="footnotes">
<ol>
<li id="fn1">
<p><em>Note</em>: I had to use an extra <code>div.item_inner</code> to make this work how I wanted it, but you’re clever and may find a better way. <a href="https://daverupert.com/2022/07/solving-the-dangler-conundrum-with-has-and-container-queries/#fnref1" class="footnote-backref">↩</a></p>
</li>
</ol>
</section>
]]></description>
      <pubDate>Mon, 25 Jul 2022 14:05:00 +0000</pubDate>
      <link>https://daverupert.com/2022/07/solving-the-dangler-conundrum-with-has-and-container-queries/</link>
      <dc:creator>daverupert.com</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/3726223586</guid>
    </item>
    <item>
      <title><![CDATA[Building forms with custom elements]]></title>
      <description><![CDATA[<p>Custom elements can be efficient and powerful UI building blocks, especially for large scale applications, but when it comes to building forms they need help. In this post I review what we can do now with custom elements in forms to ensure they behave as expected, and what’s on the horizon to simplify this process.</p>
<p>(Before we continue, I’m going to assume you’re already sold on <a href="https://codeburst.io/6-reasons-you-should-use-native-web-components-b45e18e069c2">the value of using web components</a> and <a href="https://web.dev/shadowdom-v1/">shadow DOM encapsulation</a>, and have a working knowledge of web component standards/APIs and how to construct a basic custom element.)</p>
<h2 id="the-state-of-custom-form-elements-in-2022">The state of custom form elements in 2022 <a class="direct-link" href="http://filamentgroup.com/lab/forms-with-custom-elements/#the-state-of-custom-form-elements-in-2022" aria-hidden="true">#</a></h2>
<p>Web component standards came on the scene about 11 years ago and introduced the concept of custom elements: blank canvases that we can define, encapsulate, and reuse in ways that we can’t with plain HTML. They changed how we think about extensibility in complex, large scale sites and applications.</p>
<p>When it comes to form composition, though, they require that we reinvent the wheel to some extent. We need custom elements to function exactly like their HTML counterparts — with a focus state, keyboard events, and when set up with a proper <code>&lt;form&gt;</code> and <code>submit</code> button, data validation and serialization — and in ways that leverage HTML itself. That would be my ideal scenario: to seamlessly use HTML form controls in the context of web components.</p>
<h3 id="web-standards-and-browser-vendors-are-getting-there">Web standards and browser vendors are getting there <a class="direct-link" href="http://filamentgroup.com/lab/forms-with-custom-elements/#web-standards-and-browser-vendors-are-getting-there" aria-hidden="true">#</a></h3>
<p>The <a href="https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals">ElementInternals API</a> is meant to, essentially, apply HTML’s form capabilities to custom elements. Think: automatic form data serialization like the good old days, just drop in your form elements, specify how and where to submit, and the browser does the rest. <a href="https://caniuse.com/mdn-api_elementinternals">Browser support</a> remains incomplete (no Safari); in the meantime there’s a <a href="https://github.com/calebdwilliams/element-internals-polyfill#readme">polyfill</a>.</p>
<p>We also have the <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/is">global <code>is</code> attribute</a> for extending built-in HTML form elements, but are still waiting on full support in <a href="https://bugs.webkit.org/show_bug.cgi?id=182671">Safari (Mac or iOS)</a>.</p>
<p>Until these features are fully implemented in all major browsers (ahem, Safari), it’s up to front-end developers to ensure that custom form elements work.</p>
<h2 id="a-checklist-for-building-forms-with-custom-elements">A checklist for building forms with custom elements <a class="direct-link" href="http://filamentgroup.com/lab/forms-with-custom-elements/#a-checklist-for-building-forms-with-custom-elements" aria-hidden="true">#</a></h2>
<p>Whether you use a web component framework or library, or build your own from scratch, when using custom elements to compose forms we have to make sure they meet the standards set by HTML:</p>
<ol>
<li>Does the custom element sufficiently recreate HTML functionality?</li>
<li>Do elements maintain <code>id</code> and <code>name</code> associations?</li>
<li>Can you programmatically assign focus to each element?</li>
<li>Do the events you care about bubble from the shadow DOM?</li>
<li>Does your form submit with data?</li>
</ol>
<p>Let’s get into it!</p>
<p>(Examples below can be found at <a href="https://codepen.io/maggiewachs/pen/MWQMQeE">this CodePen</a>.)</p>
<h3 id="1.-does-the-custom-element-sufficiently-recreate-html-functionality%3F">1. Does the custom element sufficiently recreate HTML functionality? <a class="direct-link" href="http://filamentgroup.com/lab/forms-with-custom-elements/#1.-does-the-custom-element-sufficiently-recreate-html-functionality%3F" aria-hidden="true">#</a></h3>
<p>When users encounter a custom form element, they expect it to act like an HTML form element, which means it should:</p>
<ul>
<li>gain focus via user interaction</li>
<li>support mouse and key events</li>
<li>maintain states, like disabled or invalid</li>
<li>capture one or more values</li>
<li>validate the data (when enabled), and</li>
<li>submit data with the form.</li>
</ul>
<p>One of the simplest ways to to accomplish <em>most</em> of the above is use an HTML element at the core of your custom element (or use a library that does, like <a href="https://lion-web.netlify.app/components/input/overview/">Ing’s Lion</a> or <a href="https://shoelace.style/components/input">Shoelace</a>).</p>
<p>A simple example:</p>
<pre class="language-javascript"><code class="language-javascript"><span class="highlight-line"><span class="token keyword">class</span> <span class="token class-name">SimpleInput</span> <span class="token keyword">extends</span> <span class="token class-name">HTMLElement</span> <span class="token punctuation">{</span></span><br><span class="highlight-line">  <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span><br><span class="highlight-line">    <span class="token keyword">super</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line">    <span class="token keyword">const</span> shadow <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">attachShadow</span><span class="token punctuation">(</span><span class="token punctuation">{</span></span><br><span class="highlight-line">      mode<span class="token punctuation">:</span> <span class="token string">'open'</span></span><br><span class="highlight-line">    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line">    <span class="token keyword">const</span> input <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">createElement</span><span class="token punctuation">(</span><span class="token string">'input'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line">    <span class="token comment">// ...add some features here...  </span></span><br><span class="highlight-line">    shadow<span class="token punctuation">.</span><span class="token function">appendChild</span><span class="token punctuation">(</span>input<span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line">  <span class="token punctuation">}</span></span><br><span class="highlight-line"><span class="token punctuation">}</span></span></code></pre>
<p>That’s referenced in markup as:</p>
<pre class="language-html"><code class="language-html"><span class="highlight-line"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>simple-input</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>simple-input</span><span class="token punctuation">&gt;</span></span></span></code></pre>
<p>To a user, the HTML <code>input</code> rendered inside <code>&lt;simple-input&gt;</code> behaves as expected, with a default focus state and built-in keyboard interactions. Some edits are still needed, but this method means you don’t have to start at zero, and it lowers the risk of omitting accessibility and other expected features.</p>
<p>To round out the functionality, I defined a couple of attributes (de facto properties) on the custom element that let us pass <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes">global</a> and <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#individual_attributes">element-specific</a> attribute values to the HTML <code>input</code>:</p>
<pre class="language-javascript"><code class="language-javascript"><span class="highlight-line"><span class="token keyword">class</span> <span class="token class-name">SimpleInput</span> <span class="token keyword">extends</span> <span class="token class-name">HTMLElement</span> <span class="token punctuation">{</span></span><br><span class="highlight-line">  <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span><br><span class="highlight-line">    <span class="token keyword">super</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line">    <span class="token keyword">const</span> shadow <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">attachShadow</span><span class="token punctuation">(</span><span class="token punctuation">{</span></span><br><span class="highlight-line">      mode<span class="token punctuation">:</span> <span class="token string">'open'</span></span><br><span class="highlight-line">    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line">    <span class="token keyword">const</span> input <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">createElement</span><span class="token punctuation">(</span><span class="token string">'input'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line"></span><br><span class="highlight-line">    <span class="token comment">// set default value, if any</span></span><br><span class="highlight-line">    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">hasAttribute</span><span class="token punctuation">(</span><span class="token string">'value'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span><br><span class="highlight-line">      <span class="token keyword">let</span> val <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">getAttribute</span><span class="token punctuation">(</span><span class="token string">'value'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line">      <span class="token keyword">if</span> <span class="token punctuation">(</span>val<span class="token punctuation">)</span> input<span class="token punctuation">.</span>value <span class="token operator">=</span> val<span class="token punctuation">;</span></span><br><span class="highlight-line">    <span class="token punctuation">}</span></span><br><span class="highlight-line"></span><br><span class="highlight-line">    <span class="token comment">// check for required attr</span></span><br><span class="highlight-line">    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">hasAttribute</span><span class="token punctuation">(</span><span class="token string">'required'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span><br><span class="highlight-line">      <span class="token comment">// set corresponding input property</span></span><br><span class="highlight-line">      input<span class="token punctuation">.</span>required <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span></span><br><span class="highlight-line">      <span class="token comment">// by default, the field is invalid until filled in</span></span><br><span class="highlight-line">      <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">setAttribute</span><span class="token punctuation">(</span><span class="token string">'invalid'</span><span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line">    <span class="token punctuation">}</span></span><br><span class="highlight-line"> </span><br><span class="highlight-line">    shadow<span class="token punctuation">.</span><span class="token function">appendChild</span><span class="token punctuation">(</span>input<span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line">  <span class="token punctuation">}</span></span><br><span class="highlight-line"><span class="token punctuation">}</span></span></code></pre>
<pre class="language-html"><code class="language-html"><span class="highlight-line"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>simple-input</span></span><br><span class="highlight-line">  <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>123 Main Street<span class="token punctuation">"</span></span></span><br><span class="highlight-line"><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>simple-input</span><span class="token punctuation">&gt;</span></span></span><br><span class="highlight-line"></span><br><span class="highlight-line"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>simple-input</span></span><br><span class="highlight-line">  <span class="token attr-name">required</span></span><br><span class="highlight-line"><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>simple-input</span><span class="token punctuation">&gt;</span></span></span></code></pre>
<h3 id="2.-do-elements-maintain-id-and-name-associations%3F">2. Do elements maintain <code>id</code> and <code>name</code> associations? <a class="direct-link" href="http://filamentgroup.com/lab/forms-with-custom-elements/#2.-do-elements-maintain-id-and-name-associations%3F" aria-hidden="true">#</a></h3>
<p>When the shadow DOM is enabled, no. Elements within don’t communicate with elements outside that shadow DOM, including the enclosing <code>form</code>, <code>label</code> or <code>fieldset</code>, or other sibling form elements (like <code>radio</code> buttons).</p>
<p>A fix for this is on the horizon. The <a href="https://github.com/WICG/aom/blob/gh-pages/caniuse.md#can-i-use-accessibility-object-model-aom">Accessibility Object Model</a> has proposed that idrefs cross shadow root boundaries (see <a href="https://github.com/WICG/aom/blob/gh-pages/caniuse.md#phase-2-reflect-element-references-for-idref-attributes">Phase 2</a>). It’ll be fantastic when browsers standardize on this; until then, we have to work around it if we want encapsulated, reusable, <em>and accessible</em> custom form elements.</p>
<p><strong>Labelling elements</strong> (like <code>&lt;label&gt;</code> or <code>&lt;fieldset&gt;</code>) and ARIA attributes (<code>aria-label</code>, <code>aria-labelledby</code>, <code>aria-describedby</code>) retain their built-in connections when located in the same DOM as their associated element — connections like the ability to click a label to select a <code>checkbox</code>, or focus on an <code>input</code> to hear the label/description read aloud by a screen reader.</p>
<p>For the <code>&lt;simple-input&gt;</code>, I built in a <code>label</code> that pulls its text from an attribute:</p>
<pre class="language-javascript"><code class="language-javascript"><span class="highlight-line"><span class="token keyword">class</span> <span class="token class-name">SimpleInput</span> <span class="token keyword">extends</span> <span class="token class-name">HTMLElement</span> <span class="token punctuation">{</span></span><br><span class="highlight-line">  <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span><br><span class="highlight-line">    <span class="token keyword">super</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line">    <span class="token keyword">const</span> shadow <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">attachShadow</span><span class="token punctuation">(</span><span class="token punctuation">{</span></span><br><span class="highlight-line">      mode<span class="token punctuation">:</span> <span class="token string">'open'</span></span><br><span class="highlight-line">    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line"></span><br><span class="highlight-line">    <span class="token keyword">const</span> label <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">createElement</span><span class="token punctuation">(</span><span class="token string">'label'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line">    <span class="token keyword">const</span> labelText <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">createElement</span><span class="token punctuation">(</span><span class="token string">'span'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line">    labelText<span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">'label-text'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line">    <span class="token comment">// get text</span></span><br><span class="highlight-line">    labelText<span class="token punctuation">.</span>textContent <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">getAttribute</span><span class="token punctuation">(</span><span class="token string">'label'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line"></span><br><span class="highlight-line">    <span class="token keyword">const</span> input <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">createElement</span><span class="token punctuation">(</span><span class="token string">'input'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line"></span><br><span class="highlight-line">    <span class="token comment">// set default value, if any</span></span><br><span class="highlight-line">    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">hasAttribute</span><span class="token punctuation">(</span><span class="token string">'value'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span><br><span class="highlight-line">      <span class="token keyword">let</span> val <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">getAttribute</span><span class="token punctuation">(</span><span class="token string">'value'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line">      <span class="token keyword">if</span> <span class="token punctuation">(</span>val<span class="token punctuation">)</span> input<span class="token punctuation">.</span>value <span class="token operator">=</span> val<span class="token punctuation">;</span></span><br><span class="highlight-line">    <span class="token punctuation">}</span></span><br><span class="highlight-line"></span><br><span class="highlight-line">    <span class="token comment">// check for required attr</span></span><br><span class="highlight-line">    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">hasAttribute</span><span class="token punctuation">(</span><span class="token string">'required'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span><br><span class="highlight-line">      <span class="token comment">// set corresponding input property</span></span><br><span class="highlight-line">      input<span class="token punctuation">.</span>required <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span></span><br><span class="highlight-line">      <span class="token comment">// by default, the field is invalid until filled in</span></span><br><span class="highlight-line">      <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">setAttribute</span><span class="token punctuation">(</span><span class="token string">'invalid'</span><span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line">      <span class="token comment">// add a label class</span></span><br><span class="highlight-line">      labelText<span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">'required'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line">    <span class="token punctuation">}</span></span><br><span class="highlight-line"></span><br><span class="highlight-line">    <span class="token keyword">const</span> style <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">createElement</span><span class="token punctuation">(</span><span class="token string">'style'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line">    style<span class="token punctuation">.</span>textContent <span class="token operator">=</span> <span class="token template-string"><span class="token string">`</span><br><span class="highlight-line">      // ...styles go here</span><br><span class="highlight-line">    `</span></span><span class="token punctuation">;</span></span><br><span class="highlight-line"></span><br><span class="highlight-line">    <span class="token comment">// add a class to simplify styles, queries</span></span><br><span class="highlight-line">    <span class="token keyword">this</span><span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">'field'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line"></span><br><span class="highlight-line">    label<span class="token punctuation">.</span><span class="token function">appendChild</span><span class="token punctuation">(</span>labelText<span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line">    label<span class="token punctuation">.</span><span class="token function">appendChild</span><span class="token punctuation">(</span>input<span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line">    shadow<span class="token punctuation">.</span><span class="token function">appendChild</span><span class="token punctuation">(</span>style<span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line">    shadow<span class="token punctuation">.</span><span class="token function">appendChild</span><span class="token punctuation">(</span>label<span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line">  <span class="token punctuation">}</span></span><br><span class="highlight-line"><span class="token punctuation">}</span></span></code></pre>
<pre class="language-html"><code class="language-html"><span class="highlight-line"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>simple-input</span></span><br><span class="highlight-line">  <span class="token attr-name">label</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Street Address<span class="token punctuation">"</span></span></span><br><span class="highlight-line">  <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>123 Main Street<span class="token punctuation">"</span></span></span><br><span class="highlight-line"><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>simple-input</span><span class="token punctuation">&gt;</span></span></span></code></pre>
<p>If you’re using a framework like React or Stencil, you can create a reusable functional or stateless component to inject the correct labelling markup into each custom form element; see, for example, Stencil’s <a href="https://stenciljs.com/docs/functional-components">Working with Functional Components</a>.</p>
<p><strong>Radio buttons</strong> coded as separate web components (and with separate shadow DOMs) won’t act as a single set, even when the child <code>input</code> elements share a <code>name</code> value; checking one won’t impact the others.</p>
<p>In a recent project we worked around this by treating the radio button set as a single element (<code>&lt;simple-radio-set&gt;</code>), which worked well because there’s no use case for a stand-alone radio button. We configured the set’s <code>input</code>s by writing template logic that looped through a passed array:</p>
<pre class="language-javascript"><code class="language-javascript"><span class="highlight-line"><span class="token keyword">this</span><span class="token punctuation">.</span>radio<span class="token punctuation">.</span>options <span class="token operator">=</span> <span class="token punctuation">[</span></span><br><span class="highlight-line">  <span class="token punctuation">{</span> label<span class="token punctuation">:</span> <span class="token string">'Cats'</span><span class="token punctuation">,</span> value<span class="token punctuation">:</span> <span class="token string">'cats'</span> <span class="token punctuation">}</span><span class="token punctuation">,</span></span><br><span class="highlight-line">  <span class="token punctuation">{</span> label<span class="token punctuation">:</span> <span class="token string">'Dogs'</span><span class="token punctuation">,</span> value<span class="token punctuation">:</span> <span class="token string">'dogs'</span> <span class="token punctuation">}</span><span class="token punctuation">,</span></span><br><span class="highlight-line">  <span class="token punctuation">{</span> label<span class="token punctuation">:</span> <span class="token string">'Leopard Geckos'</span><span class="token punctuation">,</span> value<span class="token punctuation">:</span> <span class="token string">'geckos'</span><span class="token punctuation">,</span> <span class="token keyword">default</span><span class="token punctuation">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span></span><br><span class="highlight-line"><span class="token punctuation">]</span></span></code></pre>
<h3 id="3.-can-you-programmatically-assign-focus-to-each-element%3F">3. Can you programmatically assign focus to each element? <a class="direct-link" href="http://filamentgroup.com/lab/forms-with-custom-elements/#3.-can-you-programmatically-assign-focus-to-each-element%3F" aria-hidden="true">#</a></h3>
<p>Unlike their child HTML elements, custom form elements are not programmatically focusable, e.g., <code>myInput.focus()</code>, unless you make them so. This matters, for instance, when errors are found with client-side validation and we need to <a href="https://www.deque.com/blog/anatomy-of-accessible-forms-error-messages/">move focus to the first invalid field</a>.</p>
<p>Adding <code>tabindex</code> to the custom element may seem like a straightforward fix for this, but it creates a few problems. It would add a second focus state to the component: one Tab click to focus the custom element, and the next Tab would focus it’s child HTML element. Disabling <code>focus</code> on the child element would solve that problem, but would also disable any built-in focus-related behavior, like a screen reader announcing the associated label. You’d have to figure out how to add that back.</p>
<p>We can avoid all of that by enabling <a href="https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot/delegatesFocus"><code>delegatesFocus</code></a>, which lets you programmatically set focus on the custom element. As noted in <a href="https://web.dev/shadowdom-v1/#handling-focus">Shadow DOM v1 - Self-Contained Web Components, by Eric Bidelman</a>, <code>delegatesFocus: true</code>:</p>
<blockquote>
<p>...expands the focus behavior of elements within a shadow tree. If you click a node inside shadow DOM and the node is not a focusable area, the first focusable area becomes focused. When a node inside shadow DOM gains focus, <code>:focus</code> applies to the host in addition to the focused element.</p>
<address>Eric Bidelman</address>
<cite>Shadow DOM v1 - Self-Contained Web Components</cite>
</blockquote>
<p>Enable it within the <code>attachShadow</code> method:</p>
<pre class="language-javascript"><code class="language-javascript"><span class="highlight-line"><span class="token keyword">class</span> <span class="token class-name">SimpleInput</span> <span class="token keyword">extends</span> <span class="token class-name">HTMLElement</span> <span class="token punctuation">{</span></span><br><span class="highlight-line">  <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span><br><span class="highlight-line">    <span class="token keyword">super</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line">    <span class="token keyword">const</span> shadow <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">attachShadow</span><span class="token punctuation">(</span><span class="token punctuation">{</span></span><br><span class="highlight-line">      mode<span class="token punctuation">:</span> <span class="token string">'open'</span><span class="token punctuation">,</span></span><br><span class="highlight-line">      delegatesFocus<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span></span><br><span class="highlight-line">    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line">    <span class="token operator">...</span></span><br><span class="highlight-line">  <span class="token punctuation">}</span></span><br><span class="highlight-line"><span class="token punctuation">}</span></span></code></pre>
<h3 id="4.-do-the-events-you-care-about-bubble-from-the-shadow-dom%3F">4. Do the events you care about bubble from the shadow DOM? <a class="direct-link" href="http://filamentgroup.com/lab/forms-with-custom-elements/#4.-do-the-events-you-care-about-bubble-from-the-shadow-dom%3F" aria-hidden="true">#</a></h3>
<p>Using HTML in custom elements lets us take advantage of many built-in events and behaviors, like focus or the click/tap needed to open a <code>select</code> menu, but sometimes we need the UI to react to <code>input</code> or <code>change</code> events, for example, when applying data formatting or switching up which question is asked next based on the user’s answer.</p>
<p>When a child HTML element fires a <em>composed event</em> (and <a href="https://pm.dartus.fr/blog/a-complete-guide-on-shadow-dom-and-event-propagation/">most are composed by default</a>), it will bubble up through the shadow DOM to its custom element parent. So, for example, if you need to format a value on input, you can do that by listening for the input event on your custom element:</p>
<pre class="language-javascript"><code class="language-javascript"><span class="highlight-line"><span class="token keyword">class</span> <span class="token class-name">SimpleInput</span> <span class="token keyword">extends</span> <span class="token class-name">HTMLElement</span> <span class="token punctuation">{</span></span><br><span class="highlight-line">  <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span><br><span class="highlight-line">    <span class="token keyword">super</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line">    <span class="token keyword">const</span> shadow <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">attachShadow</span><span class="token punctuation">(</span><span class="token punctuation">{</span></span><br><span class="highlight-line">      mode<span class="token punctuation">:</span> <span class="token string">'open'</span><span class="token punctuation">,</span></span><br><span class="highlight-line">      delegatesFocus<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span></span><br><span class="highlight-line">    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line">    <span class="token operator">...</span></span><br><span class="highlight-line">    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'input'</span><span class="token punctuation">,</span> formatValueFn<span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line">    shadow<span class="token punctuation">.</span><span class="token function">appendChild</span><span class="token punctuation">(</span>input<span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line">  <span class="token punctuation">}</span></span><br><span class="highlight-line"><span class="token punctuation">}</span></span></code></pre>
<p>For <a href="https://github.com/whatwg/html/issues/5453#issuecomment-885222243">reasons outlined by the <abbr title="Web Hypertext Application Technology Working Group">WHATWG</abbr></a>, <code>change</code> is <em>not</em> composed:</p>
<blockquote>
<p>...events like <code>change</code>, <code>submit</code>, or <code>load</code> are not about a 'direct user action' that affects the page as a whole. They are communicating something that changed internally to the event target in question. If that event target is inside a shadow tree, then the fact that it fired such an event is an internal detail of the component, which that component can choose to re-expose or not.</p>
<address>Domenic Denicola, WHATWG member</address>
<cite>whatwg/html: input event is composed but change isn't #5453</cite>
</blockquote>
<p>To use <code>change</code> (or any other event that isn’t composed), you’ll have to manually dispatch a new event that bubbles to the custom element when the value changes:</p>
<pre class="language-javascript"><code class="language-javascript"><span class="highlight-line">input<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'change'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>e<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span></span><br><span class="highlight-line">  <span class="token keyword">let</span> changeEv <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Event</span><span class="token punctuation">(</span><span class="token string">'change'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> bubbles<span class="token punctuation">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line">  <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">dispatchEvent</span><span class="token punctuation">(</span>changeEv<span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line"><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span></code></pre>
<h3 id="5.-does-your-form-submit-with-data%3F">5. Does your form submit with data? <a class="direct-link" href="http://filamentgroup.com/lab/forms-with-custom-elements/#5.-does-your-form-submit-with-data%3F" aria-hidden="true">#</a></h3>
<p>Not unless you explicitly tell it to. Custom elements need JavaScript and possibly some extra markup to:</p>
<h4>Expose each custom element’s value for serialization</h4>
<p>And we can do that by listening to events fired by the child elements, like <code>input</code> and <code>change</code>, as the user edits the form.</p>
<p>Earlier I added a <code>value</code> attribute to <code>&lt;simple-input&gt;</code> that sets the child <code>input</code>'s default value. By attaching a <code>change</code> listener to the child <code>input</code>, we can keep the <code>input</code>'s property and custom element’s attribute in sync:</p>
<pre class="language-javascript"><code class="language-javascript"><span class="highlight-line">input<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'change'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>e<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span></span><br><span class="highlight-line">  <span class="token comment">// sync value with attr</span></span><br><span class="highlight-line">  <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">setAttribute</span><span class="token punctuation">(</span><span class="token string">'value'</span><span class="token punctuation">,</span> input<span class="token punctuation">.</span>value<span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line"></span><br><span class="highlight-line">  <span class="token comment">// dispatch change event that bubbles from the input</span></span><br><span class="highlight-line">  <span class="token keyword">let</span> changeEv <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Event</span><span class="token punctuation">(</span><span class="token string">'change'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> bubbles<span class="token punctuation">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line">  <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">dispatchEvent</span><span class="token punctuation">(</span>changeEv<span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line"><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span></code></pre>
<p>Later this will simplify serializing the form by exposing the <code>input</code>'s current value outside of the shadow DOM.</p>
<p>This is just one example of how to capture this value for serialization. If an attribute doesn’t work for whatever reason, the same basic logic can be used to assign the value to a property on its parent element, or append it to a data object or similar. The gist is that, when it’s time to submit the form, we can efficiently grab these values without having to traverse each custom element’s shadow DOM.</p>
<h4>Set up any component-specific client-side validation</h4>
<p>Using HTML form elements as the basis of our components lets us take advantage of the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Constraint_validation">Constraint validation API</a>. For example, earlier I marked the address field as <code>required</code> and included logic to render the same attribute on its child <code>input</code>:</p>
<pre class="language-html"><code class="language-html"><span class="highlight-line"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>simple-input</span></span><br><span class="highlight-line">  <span class="token attr-name">label</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">'</span>Street Address<span class="token punctuation">'</span></span></span><br><span class="highlight-line">  <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">'</span>123 Main Street<span class="token punctuation">'</span></span></span><br><span class="highlight-line">  <span class="token attr-name">required</span></span><br><span class="highlight-line"><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>simple-input</span><span class="token punctuation">&gt;</span></span></span></code></pre>
<p>We can then add methods to the component definition for altering the <code>input</code> when invalid (or restored to a valid state). In this case, I’m conditionally toggling an <code>invalid</code> attribute on the custom element along with error messaging and a class on the <code>input</code>:</p>
<pre class="language-javascript"><code class="language-javascript"><span class="highlight-line"><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function-variable function">isInvalid</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span></span><br><span class="highlight-line">  <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">setAttribute</span><span class="token punctuation">(</span><span class="token string">'invalid'</span><span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line">  errorMssg<span class="token punctuation">.</span>textContent <span class="token operator">=</span> <span class="token string">'Please enter a value.'</span><span class="token punctuation">;</span></span><br><span class="highlight-line">  input<span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">'invalid'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line"><span class="token punctuation">}</span><span class="token punctuation">;</span></span><br><span class="highlight-line"></span><br><span class="highlight-line"><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function-variable function">isValid</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span></span><br><span class="highlight-line">  <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">removeAttribute</span><span class="token punctuation">(</span><span class="token string">'invalid'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line">  errorMssg<span class="token punctuation">.</span>textContent <span class="token operator">=</span> <span class="token string">''</span><span class="token punctuation">;</span></span><br><span class="highlight-line">  input<span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token string">'invalid'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line"><span class="token punctuation">}</span><span class="token punctuation">;</span></span></code></pre>
<p>Then listen for the <code>blur</code> event to test the <code>input</code>'s <a href="https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation#the_constraint_validation_api"><code>validity</code> property</a> and call the appropriate method:</p>
<pre class="language-javascript"><code class="language-javascript"><span class="highlight-line">input<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'blur'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>e<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span></span><br><span class="highlight-line">  <span class="token comment">// client-side validation</span></span><br><span class="highlight-line">  <span class="token keyword">if</span> <span class="token punctuation">(</span>input<span class="token punctuation">.</span>validity<span class="token punctuation">.</span>valueMissing<span class="token punctuation">)</span> <span class="token punctuation">{</span></span><br><span class="highlight-line">    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">isInvalid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line">  <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span></span><br><span class="highlight-line">    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">isValid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line">  <span class="token punctuation">}</span></span><br><span class="highlight-line"><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span></code></pre>
<h4>Make sure your submit button submits</h4>
<p>Our goal is to ensure form submission is handled by JavaScript in a way that mimics a <code>&lt;button type='submit'&gt;</code>. By default, a standard HTML submit button fires two events in succession on the associated form:</p>
<ol>
<li><code>submit</code>, which kicks off form submission and triggers the browser to run any built-in validation (e.g., contains inputs marked <code>required</code>).</li>
<li><code>formdata</code>, is fired when validation passes and data entries are appended to the <code>FormData</code> object.</li>
</ol>
<p>One might assume we could just bind a <code>submit()</code> event to our custom button and be done with it — but no. Calling <code>simpleForm.submit()</code> <em>does not actually fire the <code>submit</code> event</em>. The <code>submit</code> event only fires when called by an HTML button or submit input. However, it does go ahead and fire the <code>formdata</code> event, possibly with no data attached. This is all perfectly fine.</p>
<p>The <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/requestSubmit"><code>requestSubmit</code></a> event was created to solve this problem, and it accurately mimics button behavior when <code>simpleForm.requestSubmit()</code> is called. Unfortunately, versions of Safari pre-16 don’t support it. (You can try it out now in <a href="https://developer.apple.com/documentation/safari-release-notes/safari-16-release-notes">Safari 16 beta</a>.)</p>
<h5>Use an HTML <code>submit</code> button</h5>
<p>Until Safari 16 is more ubiquitous, I prefer to sidestep the custom element/JavaScript event issues altogether and use HTML:</p>
<pre><code>&lt;form id='important-data'&gt;
  ...custom form elements...
  &lt;button type='submit'&gt;
&lt;/form&gt;
</code></pre>
<p>Then attach a <code>submit</code> listener to the form for processing client-side validation and creating a <code>FormData</code> object.</p>
<p>If your project must use a custom button element, you can include logic in its component definition that injects a hidden <code>&lt;button type='submit'&gt;</code> into the light DOM. You can then trigger a click on the HTML button and listen for the <code>submit</code> event.</p>
<h4>Put it all together</h4>
<p>To wrap up the <code>simple-input</code> example, the accompanying form logic might look like this (also on <a href="https://codepen.io/maggiewachs/pen/MWQMQeE">CodePen</a>):</p>
<pre class="language-javascript"><code class="language-javascript"><span class="highlight-line"><span class="token keyword">const</span> simpleForm <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span><span class="token string">'simple-form'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line"></span><br><span class="highlight-line">simpleForm<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'submit'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>e<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span></span><br><span class="highlight-line">  <span class="token comment">// pause to allow validation</span></span><br><span class="highlight-line">  e<span class="token punctuation">.</span><span class="token function">preventDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line"></span><br><span class="highlight-line">  <span class="token keyword">let</span> invalidFields <span class="token operator">=</span> simpleForm<span class="token punctuation">.</span><span class="token function">querySelectorAll</span><span class="token punctuation">(</span><span class="token string">'[invalid]'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line"></span><br><span class="highlight-line">  <span class="token keyword">if</span> <span class="token punctuation">(</span>invalidFields<span class="token punctuation">.</span>length <span class="token operator">&gt;</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span><br><span class="highlight-line">    invalidFields<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span>field<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span></span><br><span class="highlight-line">      <span class="token comment">// apply styles</span></span><br><span class="highlight-line">      field<span class="token punctuation">.</span><span class="token function">isInvalid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line">    <span class="token punctuation">}</span><span class="token punctuation">)</span></span><br><span class="highlight-line">    <span class="token comment">// focus the first invalid field</span></span><br><span class="highlight-line">    invalidFields<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">focus</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line">  <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span></span><br><span class="highlight-line">    <span class="token comment">// create a FormData obj</span></span><br><span class="highlight-line">    <span class="token keyword">this</span><span class="token punctuation">.</span>simpleData <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">FormData</span><span class="token punctuation">(</span>simpleForm<span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line"></span><br><span class="highlight-line">    <span class="token keyword">let</span> fields <span class="token operator">=</span> simpleForm<span class="token punctuation">.</span><span class="token function">querySelectorAll</span><span class="token punctuation">(</span><span class="token string">'.field'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line">    fields<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span>field<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span></span><br><span class="highlight-line">      <span class="token comment">// add each label/value pair to the FormData obj</span></span><br><span class="highlight-line">      <span class="token comment">// the `set` method adds new or replaces existing values</span></span><br><span class="highlight-line">      <span class="token comment">// https://developer.mozilla.org/en-US/docs/Web/API/FormData/set</span></span><br><span class="highlight-line">      <span class="token keyword">let</span> name <span class="token operator">=</span> field<span class="token punctuation">.</span><span class="token function">getAttribute</span><span class="token punctuation">(</span><span class="token string">'label'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line">      <span class="token keyword">let</span> val <span class="token operator">=</span> field<span class="token punctuation">.</span><span class="token function">getAttribute</span><span class="token punctuation">(</span><span class="token string">'value'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line">      <span class="token keyword">this</span><span class="token punctuation">.</span>simpleData<span class="token punctuation">.</span><span class="token keyword">set</span><span class="token punctuation">(</span>name<span class="token punctuation">,</span> val<span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line">    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line"></span><br><span class="highlight-line">    <span class="token comment">// at this point you can package and send your data in a few ways, more here:</span></span><br><span class="highlight-line">    <span class="token comment">// https://developer.mozilla.org/en-US/docs/Web/API/FormData/Using_FormData_Objects</span></span><br><span class="highlight-line">    <span class="token comment">// replace the following with submission logic :)</span></span><br><span class="highlight-line">    <span class="token keyword">let</span> feedbackArr <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span></span><br><span class="highlight-line">    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> <span class="token punctuation">[</span>name<span class="token punctuation">,</span> value<span class="token punctuation">]</span> <span class="token keyword">of</span> <span class="token keyword">this</span><span class="token punctuation">.</span>simpleData<span class="token punctuation">)</span> <span class="token punctuation">{</span></span><br><span class="highlight-line">      feedbackArr<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token template-string"><span class="token string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>name<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>value<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line">    <span class="token punctuation">}</span></span><br><span class="highlight-line">    feedback<span class="token punctuation">.</span>innerHTML <span class="token operator">=</span> <span class="token template-string"><span class="token string">`&lt;h3&gt;Form values submitted:&lt;/h3&gt;`</span></span> <span class="token operator">+</span> feedbackArr<span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">', '</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line">  <span class="token punctuation">}</span></span><br><span class="highlight-line"><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span></code></pre>
<p>To recap the form logic:</p>
<ul>
<li>I’m double-checking validation at the form level. All required fields were assigned an <code>invalid</code> attribute by default, so if they were skipped (never triggered <code>blur</code>) or failed the internal validity check, they’ll be flagged. I also assign focus to the first invalid field so the user can correct the error and try again.</li>
<li>When all fields pass, we can append their values to a <code>FormData</code> object, by either <a href="https://developer.mozilla.org/en-US/docs/Web/API/FormData/Using_FormData_Objects#creating_a_formdata_object_from_scratch">creating a new object</a> or <a href="https://developer.mozilla.org/en-US/docs/Web/API/FormData/Using_FormData_Objects#retrieving_a_formdata_object_from_an_html_form">using an existing <code>form</code>'s object</a>.</li>
</ul>
<h2 id="resources-linked-in-this-post">Resources linked in this post <a class="direct-link" href="http://filamentgroup.com/lab/forms-with-custom-elements/#resources-linked-in-this-post" aria-hidden="true">#</a></h2>
<ul>
<li><a href="https://codeburst.io/6-reasons-you-should-use-native-web-components-b45e18e069c2">6 Reasons You Should Use Native Web Components, by Kai Wedekind</a></li>
<li><a href="https://web.dev/shadowdom-v1/">Shadow DOM v1 - Self-Contained Web Components, by Eric Bidelman</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals">MDN: <code>ElementInternals</code></a></li>
<li><a href="https://github.com/calebdwilliams/element-internals-polyfill">calebdwilliams/element-internals-polyfill</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/is">MDN: <code>is</code> attribute</a></li>
<li><a href="https://lion-web.netlify.app/">Lion web component library, authored by ING</a></li>
<li><a href="https://shoelace.style/">Shoelace web component library, authored by Cory LaViska</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-within">MDN: <code>:focus-within</code></a></li>
<li><a href="https://github.com/WICG/aom/blob/gh-pages/caniuse.md">Can I Use Accessibility Object Model (AOM)</a></li>
<li><a href="https://pm.dartus.fr/blog/a-complete-guide-on-shadow-dom-and-event-propagation/">A complete guide on shadow DOM and event propagation, by Pierre-Marie Dartus</a></li>
<li><a href="https://github.com/whatwg/html/issues/5453#issuecomment-885222243"><code>input</code> event is composed but <code>change</code> isn’t #5453</a></li>
<li><a href="https://stenciljs.com/docs/events">Stencil documentation: Events</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Constraint_validation">MDN: Constraint validation API</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/ValidityState">MDN: ValidityState</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/FormData/Using_FormData_Objects">MDN: Using FormData Objects</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/requestSubmit">MDN: HTMLFormElement.requestSubmit()</a></li>
<li><a href="https://www.deque.com/blog/anatomy-of-accessible-forms-error-messages/">The Anatomy of Accessible Forms: Error Messages, by Raghavendra Satish Peri</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot/delegatesFocus">MDN: ShadowRoot.delegatesFocus</a></li>
</ul>
]]></description>
      <pubDate>Thu, 23 Jun 2022 00:00:00 +0000</pubDate>
      <link>http://filamentgroup.com/lab/forms-with-custom-elements/</link>
      <dc:creator>Filament Group, Inc: Lab</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/3684555150</guid>
    </item>
    <item>
      <title><![CDATA[Goodbye, IE]]></title>
      <description><![CDATA[<p>In 1995, I got my first PC with Windows 95 and my first web browser, unbeknownst to me at the time, was Internet Explorer 1.0. I dialed my computer into AOL checked my mail, checked my favorite chat rooms, but I soon learned the Internet I wanted existed outside the in-app silo of America Online. I wanted to surf and create content on the information super highway. Through Angelfire and AOL Pages, I stayed up late creating my own worlds on the World Wide Web.</p>
<p>In 1998, I went to college and was peer pressured to use Netscape by my roommates. Netscape had an exciting atmosphere about it, and its move to open source turned fellow Computer Science nerds into gleeful converts. That move to open source would be a critical change in the life of Netscape but also in the life of the Web and in the story of Internet Explorer.</p>
<p>In 2000, I used my student discount to get a copy of FrontPage 98. FrontPage had all these cool DHTML effects like “glow” using proprietary ActiveX controls. I liked the effects so much, I switched back to using Internet Explorer 5. The year 2001 would bring us IE6 on Windows XP with a brand new aesthetic that made Netscape feel ancient. During these years I built websites for college groups like my ultimate frisbee team and was blissfully ignorant of the fact that they didn’t work the same in non-IE browsers. This is some foreshadowing of the problems with IE’s monopoly.</p>
<p>In 2003, I saved enough money through my first fulltime job to buy my first Macbook Pro. OS X 10.1 was new at the time and Safari didn’t exist yet so I used Internet Explorer 5.5, which took the “Aqua” aesthetic to heart and had a cool feature that would ping a list of your favorite sites to check for updates; a weird precursor to RSS. Later in 2003, Apple released Safari and —Apple fanboy that I was— I switched to that.</p>
<p>In 2006, I started doing web development as a job. I read Zeldman’s Designing for Web Standards and became passionate about web standards. IE6, in a web of web standards, earned a bit of a bad rap among developers due to its bespoke box model and lack of updates (compared to Firefox, Safari, and soon Chrome). IE7 did come out, then IE8. Each one better than the one before but still with their own quirks that left them with a bad feeling. IE became the butt of every conference joke. “What about IE?” became the bane of every person talking about new web features. We put “Upgrade your browser” or “Try Firefox” banners on our sites. We collectively tried to eradicate it.</p>
<p>In 2011, Nishant Kothary invited Jason Santa Maria, Frank Chrimero, Naz Hamid, and Paravel to work on a project called “<a href="https://lostworldsfairs.com/">Lost World’s Fairs</a>” to celebrate the launch of IE9 and highlight its newfound support of WOFF web fonts. A celebration of the Web and what browsers can do. IE9 was a massive improvement and it seemed like the gears of change were turning. For sure one of the funnest projects I’ve ever worked on where I got to flex my skills.</p>
<p>In 2012, in part due to the work on the Lost World’s Fair site, Paravel was able to work on the <a href="http://Microsoft.com">Microsoft.com</a> homepage, right around the launch of Windows 8 (featuring the short-lived Internet Explorer 10). <a href="https://daringfireball.net/linked/2012/06/29/microsoft-home-page">Gruber liked it</a> and I heard Steve Ballmer himself gave it a thumbs up. Pretty cool.</p>
<p>In 2015, I switched back to Windows for <a href="https://daverupert.com/tag/davegoeswindows/">#davegoeswindows</a>. Part of the unpaid deal was to try and use Edge, the successor to IE, as my daily browser. It’s still impressive what the original Edge team did in an attempt to salvage a browser and its reputation. Pivoting from the old Trident rendering engine to EdgeHTML, rearchitecting its security principles, and building something modern and fast. There were leftover IE bits in Edge (e.g., F12 Developer Tools), but I got used to it and experienced using “not the most cutting edge” browser as my daily driver. The #davegoeswindows experiment was one of the most educational stunts I’ve ever done.</p>
<p>This week on June 15th, 2022, Internet Explorer reached its end of life and is no longer supported by Microsoft. I think this is something to celebrate. It’s the end of non-evergreen browsers. While <a href="https://css-tricks.com/evergreen-does-not-mean-immediately-available/">“Evergreen” does not mean immediately available</a> and saying “IE is dead” doesn’t mean someone out there isn’t using it, my hope is those people don’t become a gigantic strawman like “the boss who uses IE” did. It’s important to celebrate that outdated software licenses for governments and enterprise IT departments forcing legacy browser usage no longer control our industry or our collective conscious. That is life-changing.</p>
<hr>
<p>Internet Explorer has been a part of my entire online life; equal parts thrill, shame, joy, and pain… well, a smidge more pain. Over the years, IE crossed the boundaries from being a tech beacon (the first browsers with CSS), to becoming a villain we all raged against, to becoming a technical liability. The full circle. Hearing IE jokes from late night talk show hosts is surreal and noteworthy; IE sits in the cultural seat for a public roasting, the razzing of an old friend, the popularity of a celebrity death.</p>
<p>Though I may sound sentimental, I’m happy to say goodbye. I already feel the new allocations as sections of my brain used for remembering browser quirks get garbage collected. And in some ironic twist… the browser I’m reading this article on today is a browser from Microsoft. The spiritual successor to that old friend and foe. While IE is dead, its legacy lives on.</p>
]]></description>
      <pubDate>Fri, 17 Jun 2022 19:29:00 +0000</pubDate>
      <link>https://daverupert.com/2022/06/goodbye-ie/</link>
      <dc:creator>daverupert.com</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/3676673500</guid>
    </item>
    <item>
      <title><![CDATA[My Weekly Engineering Report]]></title>
      <description><![CDATA[<p>Providing visibility into your work is one of the most underrated developer soft skills. Coworkers are often too busy to keep track of what’s going on with engineering work, <em>even</em> if it’s their job to know what’s going on with engineering work. It’s generally not out of malice or lack of interest, but a lack of time and energy to chase down every issue or conversation thread. And to be fair, engineering is an opaque process where we trick the rocks inside a computer to act like a business.</p>
<p>Standups are a great tool for sharing visibility. Unfortunately, not everyone loves standups. I’ve personally experienced some extremely toxic scrum master situations. Impersonal bots can have the same effect. A daily status report can feel like micromangement leading to a loss of autonomy. Also, that visibility into your work evaporates after attendees leave the call.</p>
<p>For the past four or five years, I’ve been practicing an alternative to daily standups. Rather than waiting for people to confront you, I find it better to <strong>proactively share your progress in a brief bulleted list</strong><sup class="footnote-ref"><a href="https://daverupert.com/2022/04/my-weekly-engineering-report/#fn1" id="fnref1">1</a></sup> in a public channel (of appropriate level). That’s the secret: share without being asked. The template has evolved a bit over the years, but here’s what it looks like now:</p>
<div class="language-md highlighter-rouge"><div class="highlight"><pre class="highlight"><code data-lang="md">💪 <span class="gs">**Progress**</span>
<span class="p">-</span> Fixed something project#123
<span class="p">-</span> Added something project#234
<span class="p">-</span> Documented something

😖 <span class="gs">**Regressions**</span>
<span class="p">-</span> Found bug on something project#345

🧪 <span class="gs">**Prototypes**</span>
<span class="p">-</span> Experimented with new feature codepen.io/123

💬 <span class="gs">**To Discuss**</span>
<span class="p">-</span> Blocked on something project#456
 
📆 <span class="gs">**This Week**</span>
<span class="p">-</span> Work on new feature project#567
<span class="p">-</span> Out of office on Friday
</code></pre></div></div>
<p>That’s a … uh … standup?</p>
<p>Yeah. It’s basically a standup in newsletter form. It could be the years working in a Japanese office talking, but I don’t hate standups. They are great for mind-melding. But, as I’ve experienced, not everyone thrives in standups. But the sharing part is valuable, I try to keep doing the sharing part.</p>
<p>I’ve done this in solo contexts, team contexts, and external client-facing contexts and no one hates it. No one says, “God, Dave, I wish you’d quit summarizing what’s going on.” No one asks for it, I just post it. People —especially those that pay you— enjoy seeing progress made and you (or your team) can avoid a lot of workplace mistrust issues by broadcasting a short progress report.</p>
<p>Consistency helps. Any weekday works but I’ve found posting on Monday mornings provides a nice recap of last week’s progress and sets a baseline for expectations in the upcoming week. It’s also a good opportunity to publicly celebrate specific people or any team wins as well.</p>
<p>Thus sums up my entire software development philosophy: <em>Proactively communicate as succinctly as possible what you’re working on, what problems you ran into, and what you’re working on next.</em></p>
<section class="footnotes">
<ol>
<li id="fn1">
<p>The bulleted list part is maybe the most important part. <a href="https://daverupert.com/2022/04/my-weekly-engineering-report/#fnref1" class="footnote-backref">↩</a></p>
</li>
</ol>
</section>
]]></description>
      <pubDate>Thu, 21 Apr 2022 13:57:00 +0000</pubDate>
      <link>https://daverupert.com/2022/04/my-weekly-engineering-report/</link>
      <dc:creator>daverupert.com</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/3596537964</guid>
    </item>
    <item>
      <title><![CDATA[Aligning Content In Different Wrappers]]></title>
      <description><![CDATA[]]></description>
      <pubDate>Mon, 14 Mar 2022 03:47:11 +0000</pubDate>
      <link>http://ishadeed.com/article/aligning-content-different-wrappers/</link>
      <dc:creator>Ahmad Shadeed</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/3544988135</guid>
    </item>
    <item>
      <title><![CDATA[Hello, CSS Cascade Layers]]></title>
      <description><![CDATA[]]></description>
      <pubDate>Fri, 11 Feb 2022 03:47:11 +0000</pubDate>
      <link>http://ishadeed.com/article/cascade-layers/</link>
      <dc:creator>Ahmad Shadeed</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/3503191394</guid>
    </item>
    <item>
      <title><![CDATA[My hardware adventure with a Gameboy Pocket [blog]]]></title>
      <description><![CDATA[
<p>I bought this Gameboy Pocket off eBay in the hope that I could bring it back to life...</p>
<p><img src="https://remysharp.com/images/gbp-restore/before.jpg" alt=""></p>
<p>I'd seen this before and suspected that the game couldn't be loaded due to corruption during boot (on the GB side). I was wrong, but I definitely enjoyed the challenge!</p>
<!-- more -->
<p>Opening up the Gameboy up and took a peek, and yep, found game pak loading doberry had been messed with. Bit of a duff job on whoever tried to repair it before me - and it even looks like they spotted there was a short and tried to slice through it with a knife:</p>
<p><img src="https://remysharp.com/images/gbp-restore/pins-before.jpg" alt=""></p>
<p>Pins A2 and A1 were definitely connected! Some flux and solder later and it looks more like this:</p>
<p><img src="https://remysharp.com/images/gbp-restore/pins-after.jpg" alt=""></p>
<p>And now the Gameboy boots with a nice clean Nintendo logo. Except… it doesn't go past that point at all - and in fact the screen looks like it's doing some weird flash dance, going between dim and off (and I could see the amperage being pulled was fluctuating and not steady). On top of which, I'd also noticed I hadn't heard the speaker.</p>
<p>Fixed (replaced) the speaker first - it had no continuity between the two pins at all, so the only spare I had was this bigger one, but now I had sound again (but loading isn't working still):</p>
<p><img src="https://remysharp.com/images/gbp-restore/speaker.jpg" alt=""></p>
<p>Under the microscope I found there was corrosion (possibly it was dropped in liquid, my best guess), around the contrast wheel. After I tried to clean it, two of the five pads completely removed themselves from the PCB and I started noticing more corrosion across the board.</p>
<p><img src="https://remysharp.com/images/gbp-restore/bad-contrast.jpg" alt=""></p>
<p>The far left and middle trace pads were completely removed. To (sort of) fix the contrast wheel, according the to schematics, I could jump the far left pins together, and then the next pins together.</p>
<p>Still didn't work though. So search again under the microscope and came across this via with all it's copper gone (on the far right)</p>
<p><img src="https://remysharp.com/images/gbp-restore/bad-vias.jpg" alt=""></p>
<p>You can also see some residue green around some of the vias from corrosion.</p>
<p>I traced this line and it joined the S-RAM to the cartridge slot and then back to the CPU, and from the underside of the board, where the via was missing the copper - there was no continuity between the two chips - when there definitely needed to be.</p>
<p>So jumper to the rescue (I had to use a spare resistor lead as a jumper, turns out all my wires were too fat!):</p>
<p><img src="https://remysharp.com/images/gbp-restore/fixed-with-a-jumper.jpg" alt=""></p>
<p>And after that rather dodgy soldering job - the Gameboy Pocket is back to life!</p>
<p><img src="https://remysharp.com/images/gbp-restore/after.jpg" alt=""></p>
<p>Now I've just got to find it a new home (I've already got too many Gameboys!)</p>
<p><em>Originally published on <a href="https://remysharp.com/2022/02/05/my-hardware-adventure-with-a-gameboy-pocket">Remy Sharp's b:log</a></em></p>]]></description>
      <pubDate>Sat, 05 Feb 2022 00:00:00 +0000</pubDate>
      <link>https://remysharp.com/2022/02/05/my-hardware-adventure-with-a-gameboy-pocket</link>
      <dc:creator>remy sharp&#39;s b:log</dc:creator>
      <guid isPermaLink="false">https://feedbin.me/entries/3494055837</guid>
    </item>
  </channel>
</rss>
