<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Aaron Gustafson</title>
    <description>The latest articles on DEV Community by Aaron Gustafson (@aarongustafson).</description>
    <link>https://dev.to/aarongustafson</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F40594%2F9a451d7c-ccc5-43c1-a556-ece3d4d6265c.jpg</url>
      <title>DEV Community: Aaron Gustafson</title>
      <link>https://dev.to/aarongustafson</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aarongustafson"/>
    <language>en</language>
    <item>
      <title>A Production-Ready Web Component Starter Template</title>
      <dc:creator>Aaron Gustafson</dc:creator>
      <pubDate>Fri, 02 Jan 2026 00:06:37 +0000</pubDate>
      <link>https://dev.to/aarongustafson/a-production-ready-web-component-starter-template-533l</link>
      <guid>https://dev.to/aarongustafson/a-production-ready-web-component-starter-template-533l</guid>
      <description>&lt;p&gt;Creating a new web component from scratch involves a lot of boilerplate—testing setup, build configuration, linting, CI/CD, documentation structure, and more. After building — and refining/rebuilding — numerous web components, I’ve distilled all that work into a starter template that lets you focus on your component’s functionality rather than project setup.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/aarongustafson/web-component-starter" rel="noopener noreferrer"&gt;Web Component Starter Template&lt;/a&gt; is based on the architecture and patterns I’ve refined across my web component work, incorporating &lt;a href="https://web.dev/articles/custom-elements-best-practices" rel="noopener noreferrer"&gt;Google’s Custom Element Best Practices&lt;/a&gt; and advice from other web components practitioners including the always-brilliant &lt;a href="https://daverupert.com/" rel="noopener noreferrer"&gt;Dave Rupert&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  # What’s included
&lt;/h2&gt;

&lt;p&gt;The template provides everything you need to create a production-ready web component:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Interactive setup wizard&lt;/strong&gt; that scaffolds everything for your component.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple import patterns&lt;/strong&gt; supporting both auto-define and manual registration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Demo pages&lt;/strong&gt; for development, documentation, and CDN examples.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code quality tools&lt;/strong&gt; including ESLint and Prettier with sensible defaults.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modern testing setup&lt;/strong&gt; with Vitest, Happy DOM, and coverage reporting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI/CD workflows&lt;/strong&gt; for GitHub Actions with automated testing and npm publishing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Publishing ready&lt;/strong&gt; with proper npm package configuration and OIDC support.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  # Quick start with interactive setup
&lt;/h2&gt;

&lt;p&gt;Getting started is straightforward. If you’re a GitHub user, you can create a new repository directly from the template. Alternatively, clone it locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone https://github.com/aarongustafson/web-component-starter.git my-component
cd my-component
npm install
npm run setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The setup wizard asks for your component name and description, then automatically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Renames all files based on your component name,&lt;/li&gt;
&lt;li&gt;Updates all code and configuration templates with your details,&lt;/li&gt;
&lt;li&gt;Generates a proper README from the included template,&lt;/li&gt;
&lt;li&gt;Cleans up all template-specific files, and&lt;/li&gt;
&lt;li&gt;Initializes the git repository.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You’re left with a fully scaffolded repository, ready for you to develop your component.&lt;/p&gt;

&lt;h2&gt;
  
  
  # Flexible import patterns
&lt;/h2&gt;

&lt;p&gt;One of the key features is support for multiple registration patterns. Users of your component can choose what works best:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Manual registration for full control:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import{ ComponentNameElement } from '@yourscope/component-name';
customElements.define('my-custom-name', ComponentNameElement);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Auto-define for convenience:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import '@yourscope/component-name/define.js';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Or call the helper directly:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import{ defineComponentName } from '@yourscope/component-name/define.js';
defineComponentName();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The auto-define approach includes guards to ensure it only runs in browser environments and checks if &lt;code&gt;customElements&lt;/code&gt; is available, making it safe for server-side rendered (SSR) scenarios.&lt;/p&gt;

&lt;h2&gt;
  
  
  # Testing made easy
&lt;/h2&gt;

&lt;p&gt;The template includes a comprehensive testing setup using Vitest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import{ describe, it, expect } from 'vitest';
describe('MyComponent', ()=&amp;gt;{
  it('should render', ()=&amp;gt;{
    const el = document.createElement('my-component');
    expect(el).toBeInstanceOf(HTMLElement);
  });
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Happy DOM provides a lightweight browser environment, and the included scripts support:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Watch mode for development: &lt;code&gt;npm test&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Single run for CI: &lt;code&gt;npm run test:run&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Interactive UI: &lt;code&gt;npm run test:ui&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Coverage reports: &lt;code&gt;npm run test:coverage&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  # Automated publishing with OIDC
&lt;/h2&gt;

&lt;p&gt;The template is configured for secure automated publishing to npm using OpenID Connect (OIDC), which is more secure than long-lived tokens. After you manually publish the first version and configure OIDC on npm, create a GitHub release and the workflow handles publishing automatically.&lt;/p&gt;

&lt;p&gt;Manual publishing is still supported if you prefer that approach.&lt;/p&gt;

&lt;h2&gt;
  
  
  # Following best practices
&lt;/h2&gt;

&lt;p&gt;The template bakes in best practices from the start:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Shadow DOM with proper encapsulation&lt;/li&gt;
&lt;li&gt;Custom Elements v1 API&lt;/li&gt;
&lt;li&gt;Reflection of properties to attributes&lt;/li&gt;
&lt;li&gt;Lifecycle callbacks used appropriately&lt;/li&gt;
&lt;li&gt;Accessible patterns and ARIA support&lt;/li&gt;
&lt;li&gt;Progressive enhancement approach&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The included &lt;a href="https://github.com/aarongustafson/web-component-starter/blob/main/WEB-COMPONENTS-BEST-PRACTICES.md" rel="noopener noreferrer"&gt;&lt;code&gt;WEB-COMPONENTS-BEST-PRACTICES.md&lt;/code&gt; document&lt;/a&gt; explains the reasoning behind each pattern, making it a learning resource as well as a starter template.&lt;/p&gt;

&lt;h2&gt;
  
  
  # Why I built this
&lt;/h2&gt;

&lt;p&gt;After creating components like &lt;a href="https://github.com/aarongustafson/form-obfuscator" rel="noopener noreferrer"&gt;form-obfuscator&lt;/a&gt;, &lt;a href="https://github.com/aarongustafson/tabbed-interface" rel="noopener noreferrer"&gt;tabbed-interface&lt;/a&gt;, and several others, I found myself copying and adapting the same project structure each time. This template captures those patterns so I — and now you — can start building components faster.&lt;/p&gt;

&lt;p&gt;If you build something with it, I’d love to hear about it!&lt;/p&gt;

</description>
      <category>webcomponents</category>
      <category>javascript</category>
      <category>opensource</category>
      <category>developertools</category>
    </item>
    <item>
      <title>Fullscreen Video and Iframes Made Easy</title>
      <dc:creator>Aaron Gustafson</dc:creator>
      <pubDate>Mon, 29 Dec 2025 17:17:27 +0000</pubDate>
      <link>https://dev.to/aarongustafson/fullscreen-video-and-iframes-made-easy-238c</link>
      <guid>https://dev.to/aarongustafson/fullscreen-video-and-iframes-made-easy-238c</guid>
      <description>&lt;p&gt;Adding fullscreen capabilities to videos and embedded iframes shouldn’t require wrestling with prefixed APIs or managing focus states. The &lt;code&gt;fullscreen-control&lt;/code&gt; web component handles all of that for you — just wrap it around the element. The component handles the rest as a discrete progressive enhancement.&lt;/p&gt;

&lt;h2&gt;
  
  
  # Easy-peasy
&lt;/h2&gt;

&lt;p&gt;Here’s a simple example using a &lt;code&gt;video&lt;/code&gt; element:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;fullscreen-control&amp;gt;
  &amp;lt;videosrc="video.mp4"&amp;gt;&amp;lt;/video&amp;gt;
&amp;lt;/fullscreen-control&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that in place, the component&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Adds a styleable button for launching fullscreen control over the contained element,&lt;/li&gt;
&lt;li&gt;Handles browser prefixes as needed,&lt;/li&gt;
&lt;li&gt;Manages focus automatically,&lt;/li&gt;
&lt;li&gt;Rigs up the necessary keyboard events (e.g. Escape to exit), and&lt;/li&gt;
&lt;li&gt;Assigns the relevant ARIA attributes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The component uses light DOM, so your &lt;code&gt;video&lt;/code&gt; stays in the regular DOM tree and all your existing CSS continues to work.&lt;/p&gt;

&lt;h2&gt;
  
  
  # Fullscreen iframes
&lt;/h2&gt;

&lt;p&gt;Need to embed a YouTube video, slide deck, or code demo? The component works with &lt;code&gt;iframe&lt;/code&gt; elements too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;fullscreen-control&amp;gt;
  &amp;lt;iframe
    src="https://www.youtube.com/embed/dQw4w9WgXcQ" 
    width="560"
    height="315" 
    title="YouTube video player" 
    &amp;gt;&amp;lt;/iframe&amp;gt;
&amp;lt;/fullscreen-control&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The component automatically adds the necessary &lt;code&gt;allow="fullscreen"&lt;/code&gt; and &lt;code&gt;allowfullscreen&lt;/code&gt; attributes, including prefixed versions for broader compatibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  # Customizable &lt;code&gt;button&lt;/code&gt; text
&lt;/h2&gt;

&lt;p&gt;You can change the &lt;code&gt;button&lt;/code&gt; label to match your site’s language or writing style by setting the &lt;code&gt;button-text&lt;/code&gt; attribute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;fullscreen-control
  button-text="全画面表示"&amp;gt;
  &amp;lt;video src="video.mp4"&amp;gt;&amp;lt;/video&amp;gt;
&amp;lt;/fullscreen-control&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The default button label is “View fullscreen,” but you can use this attribute to customize it to anything you like. You can even dynamically inject the accessible name of the contained element, using the &lt;code&gt;{name}&lt;/code&gt; token. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;fullscreen-control
  button-text="View {name} fullscreen" 
  &amp;gt;
  &amp;lt;video src=“video.mp4"
         aria-label="Product demo"
    &amp;gt;&amp;lt;/video&amp;gt;
&amp;lt;/fullscreen-control&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a &lt;code&gt;button&lt;/code&gt; with the text “View Product demo fullscreen”. The component looks for &lt;code&gt;aria-label&lt;/code&gt;, &lt;code&gt;title&lt;/code&gt;, or other native naming on the wrapped element and uses that to make the &lt;code&gt;button&lt;/code&gt; contextual.&lt;/p&gt;

&lt;h2&gt;
  
  
  # Distinct screen reader labels
&lt;/h2&gt;

&lt;p&gt;If you want the visible label and accessible button name to differ, use the &lt;code&gt;button-label&lt;/code&gt; attribute. Like &lt;code&gt;button-text&lt;/code&gt;, it can also inject the accessible name of the controlled element using the &lt;code&gt;{name}&lt;/code&gt; token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;fullscreen-control
  button-text="Fullscreen" 
  button-label="View {name} in fullscreen mode"
  &amp;gt;
  &amp;lt;iframe
    src="https://example.com"
    title="Product teaser"
    &amp;gt;&amp;lt;/iframe&amp;gt;
&amp;lt;/fullscreen-control&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code will generate a &lt;code&gt;button&lt;/code&gt; that visually reads “Fullscreen”, but is announced as “View Product teaser in fullscreen mode” to screen readers. In mode cases, &lt;code&gt;button-text&lt;/code&gt; will suffice, but this option is available if you need to distinguish the buttons of multiple fullscreen controls from one another and don’t have visual space to display their accessible names.&lt;/p&gt;

&lt;h2&gt;
  
  
  # Focus management
&lt;/h2&gt;

&lt;p&gt;If users activate fullscreen using the button, focus will automatically return to the button upon exiting fullscreen. This ensures keyboard users don’t lose their place.&lt;/p&gt;

&lt;h2&gt;
  
  
  # Need more control?
&lt;/h2&gt;

&lt;p&gt;Want to manage the component yourself? The component exposes three methods:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const control = document.querySelector("fullscreen-control" );

// Enter fullscreen
await control.enterFullscreen();

// Exit fullscreen
await control.exitFullscreen();

// Toggle fullscreen state

control.toggleFullscreen();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These handle all the browser prefixes and error handling for you.&lt;/p&gt;

&lt;p&gt;There are also a set of events you can tap into when the fullscreen state changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const control = document.querySelector("fullscreen-control");
control.addEventListener("fullscreen-control:enter", ()=&amp;gt;{
  console.log("Entered fullscreen mode" );
});
control.addEventListener("fullscreen-control:exit", ()=&amp;gt;{
  console.log("Exited fullscreen mode");
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These events give you the ability to pause other media, track analytics, and the like.&lt;/p&gt;

&lt;h2&gt;
  
  
  # Style the button
&lt;/h2&gt;

&lt;p&gt;Since the component uses light DOM, you can style the button directly with CSS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fullscreen-control button {
  background: #ff6b6b;
  color: white;
  border: none;
  padding: 0.75rem 1.5rem;
  border-radius: 20px;
  font-weight: bold;
}
fullscreen-control button:hover {
  background: #ff5252;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The button is positioned absolutely by default (top-right corner), but you can adjust this with CSS custom properties:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fullscreen-control{
  –fullscreen-control-button-inset-block-start: 1rem;
  –fullscreen-control-button-inset-inline-end: 1rem;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This uses logical properties, so it adapts automatically to different writing modes.&lt;/p&gt;

&lt;h2&gt;
  
  
  # Installation
&lt;/h2&gt;

&lt;p&gt;Install via npm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install @aarongustafson/fullscreen-control
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then import it in your JavaScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import "@aarongustafson/fullscreen-control/define.js";
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or load it from a CDN for quick prototyping:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;script type="module"&amp;gt;
  import{ defineFullscreenControl }from "https://unpkg.com/@aarongustafson/fullscreen-control@latest/define.js?module";
  defineFullscreenControl();
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  # Browser support
&lt;/h2&gt;

&lt;p&gt;The component uses modern web standards (Custom Elements v1, ES Modules) and handles browser-prefixed fullscreen APIs internally. For older browsers, you may need polyfills, but the component gracefully handles missing APIs with console warnings rather than breaking your page.&lt;/p&gt;

&lt;h2&gt;
  
  
  # Demo and source code
&lt;/h2&gt;

&lt;p&gt;Check out the &lt;a href="https://aarongustafson.github.io/fullscreen-control/demo/" rel="noopener noreferrer"&gt;live demo&lt;/a&gt; to see all the features in action, or grab the code from &lt;a href="https://github.com/aarongustafson/fullscreen-control" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webcomponents</category>
      <category>progressiveenhancement</category>
      <category>html</category>
      <category>video</category>
    </item>
    <item>
      <title>Dynamic Datalist: Autocomplete from an API</title>
      <dc:creator>Aaron Gustafson</dc:creator>
      <pubDate>Tue, 16 Dec 2025 19:46:29 +0000</pubDate>
      <link>https://dev.to/aarongustafson/dynamic-datalist-autocomplete-from-an-api-17a</link>
      <guid>https://dev.to/aarongustafson/dynamic-datalist-autocomplete-from-an-api-17a</guid>
      <description>&lt;p&gt;HTML’s &lt;code&gt;datalist&lt;/code&gt; element provides native autocomplete functionality, but it’s entirely static—you have to know all the options up front. The &lt;code&gt;dynamic-datalist&lt;/code&gt; web component solves this by fetching suggestions from an API endpoint as users type, giving you the benefits of native autocomplete with the flexibility of dynamic data.&lt;/p&gt;

&lt;p&gt;This component is a modern replacement for &lt;a href="https://github.com/easy-designs/jquery.easy-predictive-typing.js" rel="noopener noreferrer"&gt;my old jQuery predictive typing plugin&lt;/a&gt;. I’ve reimagined it as a standards-based web component.&lt;/p&gt;

&lt;h2&gt;
  
  
  # Basic usage
&lt;/h2&gt;

&lt;p&gt;To use the component, wrap it around your &lt;code&gt;input&lt;/code&gt; field and specify an endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;dynamic-datalist
  endpoint="/api/search"&amp;gt;
  &amp;lt;label for="search"&amp;gt;Search
    &amp;lt;input type="text" 
           id="search"
           name="search"
           placeholder="Type to search…"&amp;gt;
  &amp;lt;/label&amp;gt;
&amp;lt;/dynamic-datalist&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As users type, the component makes GET requests to that endpoint, passing in the typed value as the “query” parameter (e.g., &lt;code&gt;/api/search?query=WHAT_THE_USER_TYPED&lt;/code&gt;). The response fromm the endpoint is used to populates a dynamic &lt;code&gt;datalist&lt;/code&gt; element with the results.&lt;/p&gt;

&lt;p&gt;The structure of the response should be JSON with an &lt;code&gt;options&lt;/code&gt; array of string values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "options": [
    "option 1",
    "option 2",
    "option 3"
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  # How it works
&lt;/h2&gt;

&lt;p&gt;Under the hood, the component:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Adopts (or creates) a &lt;code&gt;datalist&lt;/code&gt; element for your &lt;code&gt;input&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;Listens for “input” events,&lt;/li&gt;
&lt;li&gt;Debounces requests (waiting at least 250ms) to avoid overwhelming your API,&lt;/li&gt;
&lt;li&gt;Sends requests to your endpoint with the current value of the &lt;code&gt;input&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;Reads back the JSON response,&lt;/li&gt;
&lt;li&gt;Updates the &lt;code&gt;datalist&lt;/code&gt;&lt;code&gt;option&lt;/code&gt; elements, and&lt;/li&gt;
&lt;li&gt;Dispatches the update event.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All of this happens transparently—users just see autocomplete suggestions appearing as they type.&lt;/p&gt;

&lt;h2&gt;
  
  
  # Need POST?
&lt;/h2&gt;

&lt;p&gt;You can change the submission method via the &lt;code&gt;method&lt;/code&gt; attribute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;dynamic-datalist 
  endpoint="/api/lookup" 
  method="post"&amp;gt;
  &amp;lt;label for="lookup"&amp;gt;
    Lookup
    &amp;lt;input type="text"
           id="lookup"
           name="lookup"&amp;gt;
  &amp;lt;/label&amp;gt;
&amp;lt;/dynamic-datalist&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This sends a POST request with a JSON body: &lt;code&gt;{ "query": "…" }&lt;/code&gt;. Currently GET and POST are supported, but I could add more if folks want them.&lt;/p&gt;

&lt;h2&gt;
  
  
  # Custom variable names
&lt;/h2&gt;

&lt;p&gt;As I mentioned, the component uses “query” as the parameter name by default, but you can easily change it via the &lt;code&gt;key&lt;/code&gt; attribute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;dynamic-datalist
  endpoint="/api/terms" 
  key="term"&amp;gt;
  &amp;lt;label for="search"&amp;gt;
    Term search
    &amp;lt;input type=“text" 
           id="search" 
           name="term"&amp;gt;
  &amp;lt;/label&amp;gt;
&amp;lt;/dynamic-datalist&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This sends the GET request &lt;code&gt;/api/terms?term=…&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  # Working with existing datalists
&lt;/h2&gt;

&lt;p&gt;If your &lt;code&gt;input&lt;/code&gt; already has a &lt;code&gt;datalist&lt;/code&gt; defined, the component will inherit it and replace the existing options with the fetched results, which makes for a nice progressive enhancement:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;dynamic-datalist 
  endpoint="/api/cities"&amp;gt;
  &amp;lt;label for="city"&amp;gt;
    City
    &amp;lt;input type="text" 
           id="city"
           list="cities-list"
           placeholder="Type a city…"&amp;gt;
  &amp;lt;/label&amp;gt;
  &amp;lt;datalist id="cities-list"&amp;gt;
    &amp;lt;option&amp;gt;New York&amp;lt;/option&amp;gt;
    &amp;lt;option&amp;gt;Los Angeles&amp;lt;/option&amp;gt;
    &amp;lt;option&amp;gt;Chicago&amp;lt;/option&amp;gt;
  &amp;lt;/datalist&amp;gt;
&amp;lt;/dynamic-datalist&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Users see the pre-populated cities immediately, and as they type, API results supplement the list. If JavaScript fails or the web component doesn’t load, users still get the static options. Nothing breaks.&lt;/p&gt;

&lt;h2&gt;
  
  
  # Event handling
&lt;/h2&gt;

&lt;p&gt;If you want to tap into the component’s event system, it fires three custom events:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;dynamic-datalist:ready&lt;/code&gt; - Fired when the component initializes&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dynamic-datalist:update&lt;/code&gt; - Fired when the &lt;code&gt;datalist&lt;/code&gt; is updated with new options&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dynamic-datalist:error&lt;/code&gt; - Fired when an error occurs fetching data
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const element = document.querySelector( 'dynamic-datalist' );
element.addEventListener( 'dynamic-datalist:ready', (e)=&amp;gt;{
  console.log( 'Component ready:', e.detail );
});
element.addEventListener( 'dynamic-datalist:update', (e)=&amp;gt;{
  console.log( 'Options updated:', e.detail.options );
});
element.addEventListener('dynamic-datalist:error', (e)=&amp;gt;{
  console.error( 'Error:', e.detail.error );
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each event provides helpful &lt;code&gt;detail&lt;/code&gt; objects with references to the &lt;code&gt;input&lt;/code&gt;, &lt;code&gt;datalist&lt;/code&gt;, and other relevant data.&lt;/p&gt;

&lt;h2&gt;
  
  
  # Demo
&lt;/h2&gt;

&lt;p&gt;Check out &lt;a href="https://aarongustafson.github.io/dynamic-datalist/demo/" rel="noopener noreferrer"&gt;the demo&lt;/a&gt; for live examples (there are also &lt;a href="https://aarongustafson.github.io/dynamic-datalist/demo/unpkg.html" rel="noopener noreferrer"&gt;unpkg&lt;/a&gt; and &lt;a href="https://aarongustafson.github.io/dynamic-datalist/demo/esm.html" rel="noopener noreferrer"&gt;ESM&lt;/a&gt; builds if you want to test CDN delivery).&lt;/p&gt;

&lt;h2&gt;
  
  
  # Grab it
&lt;/h2&gt;

&lt;p&gt;The project is available on &lt;a href="https://github.com/aarongustafson/dynamic-datalist" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. You can also install via npm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install @aarongustafson/dynamic-datalist
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you go that route, there are a few ways to register the element depending on your build setup:&lt;/p&gt;

&lt;h3&gt;
  
  
  # Option 1: Define it yourself
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { DynamicDatalistElement } from '@aarongustafson/dynamic-datalist';
customElements.define('dynamic-datalist', DynamicDatalistElement);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  # Option 2: Let the helper guard registration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import '@aarongustafson/dynamic-datalist/define.js';

// or, when you need to wait:
import { defineDynamicDatalist } from '@aarongustafson/dynamic-datalist/define.js';
defineDynamicDatalist();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  # Option 3: Drop the helper in via a &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;script src="./node_modules/@aarongustafson/dynamic-datalist/define.js" type="module"&amp;gt;&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Regardless of how you register it, there are no framework dependencies—just clean autocomplete powered by your API. As I mentioned, it’s also available via CDNs, such as unpkg too, if you’d prefer to go that route.&lt;/p&gt;

</description>
      <category>forms</category>
      <category>html</category>
      <category>javascript</category>
      <category>progressiveenhancement</category>
    </item>
    <item>
      <title>Lazy Loading Images Based on Screen Size</title>
      <dc:creator>Aaron Gustafson</dc:creator>
      <pubDate>Wed, 10 Dec 2025 17:15:54 +0000</pubDate>
      <link>https://dev.to/aarongustafson/lazy-loading-images-based-on-screen-size-17gh</link>
      <guid>https://dev.to/aarongustafson/lazy-loading-images-based-on-screen-size-17gh</guid>
      <description>&lt;p&gt;Native lazy loading and &lt;code&gt;srcset&lt;/code&gt; are great, but they have a limitation: they always load &lt;em&gt;some&lt;/em&gt; variant of the image. The &lt;code&gt;lazy-img&lt;/code&gt; web component takes a different approach—it can completely skip loading images when they don’t meet your criteria, whether that’s screen size, container size, or visibility in the viewport.&lt;/p&gt;

&lt;p&gt;This is particularly valuable for mobile users on slow connections or limited data plans. If an image is only meaningful on larger screens, why waste their bandwidth loading it at all?&lt;/p&gt;

&lt;h2&gt;
  
  
  # The performance benefit
&lt;/h2&gt;

&lt;p&gt;Unlike &lt;code&gt;picture&lt;/code&gt; or &lt;code&gt;srcset&lt;/code&gt;, which always load some image variant, &lt;code&gt;lazy-img&lt;/code&gt; can &lt;strong&gt;completely skip loading images&lt;/strong&gt; on screens or containers below your specified threshold. Set &lt;code&gt;min-inline-size="768px"&lt;/code&gt; and mobile users will never download that image at all—saving data and speeding up page loads.&lt;/p&gt;

&lt;p&gt;Once an image is loaded, however, it remains loaded even if the viewport or container is resized below the threshold. This is intentional—the component prevents unnecessary downloads but doesn’t unload images already in memory. You can control visibility with CSS if needed using the &lt;code&gt;loaded&lt;/code&gt; and &lt;code&gt;qualifies&lt;/code&gt; attributes (which we’ll get to shortly).&lt;/p&gt;

&lt;h2&gt;
  
  
  # Basic usage
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;lazy-img&lt;/code&gt; works pretty much identically to a regular &lt;code&gt;img&lt;/code&gt; element, with all the attributes you know and love:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;lazy-imgsrc=“image.jpg”alt=“A beautiful image”&amp;gt;&amp;lt;/lazy-img&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But that’s not very interesting. The real power comes from conditional loading.&lt;/p&gt;

&lt;h2&gt;
  
  
  # Container queries (default)
&lt;/h2&gt;

&lt;p&gt;Load an image only when its container reaches a minimum width:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;lazy-imgsrc=“large-image.jpg”alt=“Large image”min-inline-size=“500px”&amp;gt;&amp;lt;/lazy-img&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The image loads when the &lt;code&gt;lazy-img&lt;/code&gt; element’s container reaches 500px wide. This is the default query mode—it uses &lt;code&gt;ResizeObserver&lt;/code&gt; to watch the container size.&lt;/p&gt;

&lt;h2&gt;
  
  
  # Media queries
&lt;/h2&gt;

&lt;p&gt;You can lazy load images based on viewport width instead by switching to media query mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;lazy-imgsrc=“desktop-image.jpg”alt=“Desktop image”min-inline-size=“768px”query=“media”&amp;gt;&amp;lt;/lazy-img&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this configuration, the image loads when the browser window is at least 768px wide.&lt;/p&gt;

&lt;h2&gt;
  
  
  # View mode (scroll-based loading)
&lt;/h2&gt;

&lt;p&gt;Load images when they scroll into view using &lt;code&gt;IntersectionObserver&lt;/code&gt; by switching to the “view” query type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;lazy-imgsrc=“image.jpg”alt=“Loads when scrolled into view”query=“view”&amp;gt;&amp;lt;/lazy-img&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The default behavior (&lt;code&gt;view-range-start="entry 0%"&lt;/code&gt;) loads as soon as any part of the image enters the viewport.&lt;/p&gt;

&lt;p&gt;Control when images load with the &lt;code&gt;view-range-start&lt;/code&gt; attribute:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Load when 50% visible:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;lazy-imgsrc=“image.jpg”alt=“Loads when half visible”query=“view”view-range-start=“entry 50%”&amp;gt;&amp;lt;/lazy-img&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Preload before entering viewport:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;lazy-imgsrc=“image.jpg”alt=“Preloads 200px before visible”query=“view”view-range-start=“entry -200px”&amp;gt;&amp;lt;/lazy-img&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a smooth user experience—images are already loaded by the time users scroll to them.&lt;/p&gt;

&lt;h2&gt;
  
  
  # Responsive images
&lt;/h2&gt;

&lt;p&gt;As with regular images, you can use &lt;code&gt;srcset&lt;/code&gt; and &lt;code&gt;sizes&lt;/code&gt; for responsive images:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;lazy-imgsrc=“image-800.jpg”srcset=“image-400.jpg 400w,image-800.jpg 800w,image-1200.jpg 1200w”sizes=“(max-width: 600px) 400px,(max-width: 1000px) 800px,1200px”alt=“Responsive image”min-inline-size=“400px”&amp;gt;&amp;lt;/lazy-img&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The component waits until the conditions are met before loading a real image and the browser takes over from there.&lt;/p&gt;

&lt;h2&gt;
  
  
  # Named breakpoints
&lt;/h2&gt;

&lt;p&gt;You can also define named breakpoints using CSS custom properties:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;:root{–lazy-img-mq: small;}@media(min-width: 768px){:root{–lazy-img-mq: medium;}}@media(min-width: 1024px){:root{–lazy-img-mq: large;}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then reference them in your markup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;lazy-imgsrc=“image.jpg”alt=“Image with named breakpoints”named-breakpoints=“medium, large”query=“media”&amp;gt;&amp;lt;/lazy-img&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The image loads when &lt;code&gt;–lazy-img-mq&lt;/code&gt; matches “medium” or “large”.&lt;/p&gt;

&lt;h2&gt;
  
  
  # Preventing layout shift
&lt;/h2&gt;

&lt;p&gt;As with regular images, don’t forget to use &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; attributes to prevent Cumulative Layout Shift (CLS):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;lazy-imgsrc=“image.jpg”alt=“A beautiful image”width=“800”height=“600”min-inline-size=“768px”&amp;gt;&amp;lt;/lazy-img&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The browser reserves the correct space while the image loads, preventing content from jumping around.&lt;/p&gt;

&lt;h2&gt;
  
  
  # State attributes for styling
&lt;/h2&gt;

&lt;p&gt;The component provides &lt;code&gt;loaded&lt;/code&gt; and &lt;code&gt;qualifies&lt;/code&gt; attributes you can use in CSS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/* Hide images that loaded but no longer meet conditions /lazy-img[loaded]:not([qualifies]){display: none;}/ Show a placeholder for images that qualify but haven’t loaded */lazy-img[qualifies]:not([loaded])::before{content:“Loading…”;display: block;padding: 2em;background: #f0f0f0;text-align: center;}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  # Events
&lt;/h2&gt;

&lt;p&gt;If you crave control, you can add your own functionality by listening for when images load:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const lazyImg = document.querySelector(‘lazy-img’);lazyImg.addEventListener(‘lazy-img:loaded’,(event)=&amp;gt;{console.log(‘Image loaded:’, event.detail.src);});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  # Performance
&lt;/h2&gt;

&lt;p&gt;The component is highly optimized:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Throttled resize&lt;/strong&gt; : Resize events are throttled to prevent excessive checks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shared &lt;code&gt;ResizeObserver&lt;/code&gt;&lt;/strong&gt; : Multiple images observing the same container share a single ResizeObserver&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shared window resize listener&lt;/strong&gt; : Media query mode shares a single window resize listener&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shared &lt;code&gt;IntersectionObserver&lt;/code&gt;&lt;/strong&gt; : View mode with the same &lt;code&gt;view-range-start&lt;/code&gt; shares an &lt;code&gt;IntersectionObserver&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clean disconnection&lt;/strong&gt; : Properly cleans up observers when elements are removed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even with hundreds of &lt;code&gt;lazy-img&lt;/code&gt; elements on a page, performance remains excellent.&lt;/p&gt;

&lt;h2&gt;
  
  
  # Progressive enhancement
&lt;/h2&gt;

&lt;p&gt;If JavaScript fails to load, images simply don’t appear (unless using immediate loading mode). This might sound problematic, but for non-critical images—decorative graphics, supplementary screenshots, marketing imagery—it’s often exactly what you want. Your content remains accessible; you just lose the enhancements.&lt;/p&gt;

&lt;p&gt;For critical images that are part of your content, use standard &lt;code&gt;img&lt;/code&gt; tags. Use &lt;code&gt;lazy-img&lt;/code&gt; for conditional enhancements.&lt;/p&gt;

&lt;h2&gt;
  
  
  # Demo
&lt;/h2&gt;

&lt;p&gt;Explore &lt;a href="https://aarongustafson.github.io/lazy-img/demo/" rel="noopener noreferrer"&gt;the demo&lt;/a&gt; to see container queries, media queries, scroll-based loading, and more in action.&lt;/p&gt;

&lt;h2&gt;
  
  
  # Grab it
&lt;/h2&gt;

&lt;p&gt;Check out the project on &lt;a href="https://github.com/aarongustafson/lazy-img" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. Install via npm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npminstall @aarongustafson/lazy-img
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Import and use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import‘@aarongustafson/lazy-img’;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Based on my original &lt;a href="https://github.com/easy-designs/easy-lazy-images.js" rel="noopener noreferrer"&gt;Easy Lazy Images&lt;/a&gt; concept, reimagined as a modern custom element.&lt;/p&gt;

</description>
      <category>webcomponents</category>
      <category>progressiveenhancement</category>
      <category>html</category>
      <category>performance</category>
    </item>
    <item>
      <title>A Web Component for Obfuscating Form Fields</title>
      <dc:creator>Aaron Gustafson</dc:creator>
      <pubDate>Sat, 06 Dec 2025 20:03:47 +0000</pubDate>
      <link>https://dev.to/aarongustafson/a-web-component-for-obfuscating-form-fields-2o06</link>
      <guid>https://dev.to/aarongustafson/a-web-component-for-obfuscating-form-fields-2o06</guid>
      <description>&lt;p&gt;We have the password reveal pattern for passwords, but what about other sensitive fields that need to be readable while editing and obfuscated while at rest? The &lt;code&gt;form-obfuscator&lt;/code&gt; web component does exactly that.&lt;/p&gt;

&lt;h2&gt;
  
  
  # Basic usage
&lt;/h2&gt;

&lt;p&gt;Wrap any text field in the component and it will automatically obfuscate the value when the field loses focus:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;form-obfuscator&amp;gt;&amp;lt;labelfor=“secret-key-1”&amp;gt;What was your first pet’s name?&amp;lt;/label&amp;gt;&amp;lt;inputtype=“text”id=“secret-key-1”name=“secret-key-1”&amp;gt;&amp;lt;/form-obfuscator&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When users click into the field, they see the actual value. When they click away, it’s replaced with asterisks (&lt;em&gt;). The real value is preserved in a hidden field for form submission.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  # Custom obfuscation characters
&lt;/h2&gt;

&lt;p&gt;If you don’t like asterisks, you can specify any character you like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;form-obfuscatorcharacter=“•”&amp;gt;&amp;lt;labelfor=“account”&amp;gt;Account Number&amp;lt;/label&amp;gt;&amp;lt;inputtype=“text”id=“account”name=“account”&amp;gt;&amp;lt;/form-obfuscator&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or get creative:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;form-obfuscatorcharacter=“🤐”&amp;gt;&amp;lt;labelfor=“ssn”&amp;gt;Social Security Number&amp;lt;/label&amp;gt;&amp;lt;inputtype=“text”id=“ssn”name=“ssn”&amp;gt;&amp;lt;/form-obfuscator&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  # Pattern-based obfuscation
&lt;/h2&gt;

&lt;p&gt;Sometimes you want to show part of the value while hiding the rest. The &lt;code&gt;pattern&lt;/code&gt; attribute lets you specify which characters to keep visible:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;form-obfuscatorpattern=“\d{4}$”&amp;gt;&amp;lt;labelfor=“ssn”&amp;gt;Social Security Number&amp;lt;/label&amp;gt;&amp;lt;inputtype=“text”id=“ssn”name=“ssn”&amp;gt;&amp;lt;/form-obfuscator&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps the last four digits visible while replacing everything else with your obfuscation character. Perfect for Social Security Numbers, credit cards, or phone numbers where showing the last few digits helps users confirm they’ve entered the right value.&lt;/p&gt;

&lt;h2&gt;
  
  
  # Limiting displayed characters
&lt;/h2&gt;

&lt;p&gt;Use the &lt;code&gt;maxlength&lt;/code&gt; attribute to cap how many characters appear when obfuscated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;form-obfuscatormaxlength=“4”&amp;gt;&amp;lt;labelfor=“password”&amp;gt;Password&amp;lt;/label&amp;gt;&amp;lt;inputtype=“text”id=“password”name=“password”&amp;gt;&amp;lt;/form-obfuscator&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even if the user enters a 20-character value, only four asterisks will be displayed when the field is obfuscated. This prevents giving away information about the length of the information entered.&lt;/p&gt;

&lt;h2&gt;
  
  
  # Custom replacement functions
&lt;/h2&gt;

&lt;p&gt;For complete control, you can provide a JavaScript function via the &lt;code&gt;replacer&lt;/code&gt; attribute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;script&amp;gt;window.emailReplacer=function(){var username = arguments[0][1];var domain = arguments[0][2];return username.replace(/./g,'‘)+ domain;}&amp;lt;/script&amp;gt;&amp;lt;form-obfuscatorpattern="^(.?)(@.+)$“replacer=”return emailReplacer(arguments)“&amp;gt;&amp;lt;labelfor=”email“&amp;gt;Email Address&amp;lt;/label&amp;gt;&amp;lt;inputtype=”text“id=”email“name=”email“value=”user@example.com"&amp;gt;&amp;lt;/form-obfuscator&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example uses a pattern to separate the username from the domain, then obfuscates only the username portion, leaving &lt;code&gt;@example.com&lt;/code&gt; visible.&lt;/p&gt;

&lt;p&gt;Here’s another practical example for credit cards:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;script&amp;gt;functioncardNumberReplacer(){var beginning = arguments[0][1];var final_digits = arguments[0][2];return beginning.replace(/\d/g,'’)+ final_digits;}&amp;lt;/script&amp;gt;&amp;lt;form-obfuscatorpattern=“^((?:[\d]+-)+)(\d+)$”replacer=“return cardNumberReplacer(arguments)”&amp;gt;&amp;lt;labelfor=“cc”&amp;gt;Credit Card&amp;lt;/label&amp;gt;&amp;lt;inputtype=“text”id=“cc”name=“cc”value=“1234-5678-9012-3456”&amp;gt;&amp;lt;/form-obfuscator&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This displays as &lt;code&gt;-- **** -3456&lt;/code&gt;, showing only the last group of digits.&lt;/p&gt;

&lt;h2&gt;
  
  
  # Combining attributes
&lt;/h2&gt;

&lt;p&gt;You can combine these attributes for sophisticated obfuscation patterns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;form-obfuscatorpattern=“\d{4}$”character=“•”maxlength=“16”&amp;gt;&amp;lt;labelfor=“card”&amp;gt;Credit Card&amp;lt;/label&amp;gt;&amp;lt;inputtype=“text”id=“card”name=“card”&amp;gt;&amp;lt;/form-obfuscator&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps the last 4 digits visible, uses bullets for obfuscation, and limits the display to 16 characters total.&lt;/p&gt;

&lt;h2&gt;
  
  
  # Event handling
&lt;/h2&gt;

&lt;p&gt;The component dispatches custom events when values are hidden or revealed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const obfuscator = document.querySelector(‘form-obfuscator’);obfuscator.addEventListener(‘form-obfuscator:hide’,(e)=&amp;gt;{console.log(‘Field obfuscated:’, e.detail.field.value);});obfuscator.addEventListener(‘form-obfuscator:reveal’,(e)=&amp;gt;{console.log(‘Field revealed:’, e.detail.field.value);});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can access both the visible field and the hidden field through &lt;code&gt;event.detail.field&lt;/code&gt; and &lt;code&gt;event.detail.hidden&lt;/code&gt; respectively.&lt;/p&gt;

&lt;h2&gt;
  
  
  # How it works
&lt;/h2&gt;

&lt;p&gt;The component creates a hidden &lt;code&gt;input&lt;/code&gt; field to store the actual value for form submission. When the visible field loses focus, it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Copies the current value to the hidden field&lt;/li&gt;
&lt;li&gt;Applies your obfuscation rules to create the display value&lt;/li&gt;
&lt;li&gt;Updates the visible field with the obfuscated value&lt;/li&gt;
&lt;li&gt;Dispatches the &lt;code&gt;form-obfuscator:hide&lt;/code&gt; event&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When the field gains focus, it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Restores the real value from the hidden field&lt;/li&gt;
&lt;li&gt;Updates the visible field&lt;/li&gt;
&lt;li&gt;Dispatches the &lt;code&gt;form-obfuscator:reveal&lt;/code&gt; event&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The source order ensures the hidden field is the one that gets submitted with the form.&lt;/p&gt;

&lt;h2&gt;
  
  
  # Progressive enhancement
&lt;/h2&gt;

&lt;p&gt;The component makes no assumptions about your markup—it works with any text-style &lt;code&gt;input&lt;/code&gt; element. If JavaScript fails to load, the field behaves like a normal &lt;code&gt;input&lt;/code&gt;, which is exactly what you want. Users can still enter and submit values; they just won’t get the obfuscation behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  # Demo
&lt;/h2&gt;

&lt;p&gt;I’ve created &lt;a href="https://aarongustafson.github.io/form-obfuscator/demo/" rel="noopener noreferrer"&gt;a comprehensive demo page showing the various configuration options&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  # Grab it
&lt;/h2&gt;

&lt;p&gt;Check out the full project over on &lt;a href="https://github.com/aarongustafson/form-obfuscator" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; or install via &lt;code&gt;npm&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install @aarongustafson/form-obfuscator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Import and use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import ‘@aarongustafson/form-obfuscator’;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No dependencies, just a straightforward way to add field obfuscation to your forms.&lt;/p&gt;

</description>
      <category>forms</category>
      <category>html</category>
      <category>javascript</category>
      <category>progressiveenhancement</category>
    </item>
    <item>
      <title>Optimizing Your Codebase for AI Coding Agents</title>
      <dc:creator>Aaron Gustafson</dc:creator>
      <pubDate>Thu, 23 Oct 2025 16:45:33 +0000</pubDate>
      <link>https://dev.to/aarongustafson/optimizing-your-codebase-for-ai-coding-agents-4ndm</link>
      <guid>https://dev.to/aarongustafson/optimizing-your-codebase-for-ai-coding-agents-4ndm</guid>
      <description>&lt;p&gt;I’ve been playing around a bit with GitHub Copilot as an autonomous agent to help with software development. The results have been mixed, but positive overall. I made an interesting discovery when I took the time to read through the agent’s reasoning over a particular task. I thought the task was straightforward, but I was wrong. Watching the agent work was like watching someone try to navigate an unfamiliar room, in complete darkness, with furniture and Lego bricks scattered everywhere.&lt;/p&gt;

&lt;p&gt;The good news? Most of the issues weren’t actually &lt;em&gt;code&lt;/em&gt; problems; they were organizational and documentation problems. The kinds of problems that make tasks hard for humans too.&lt;/p&gt;

&lt;p&gt;As I watched the agent struggle, I realized that optimizing for AI agents is really just about removing ambiguity and making implicit knowledge explicit. In other words: it’s just good engineering.&lt;/p&gt;

&lt;h2&gt;
  
  
  What did I learn?
&lt;/h2&gt;

&lt;p&gt;After reviewing the agent’s execution logs (which read like a stream-of-consciousness diary of confusion), several patterns emerged:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. &lt;strong&gt;Documentation sprawl is an efficiency killer&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The agent spent roughly 40% of its time just trying to figure out which documentation to trust. We had instructions in workflow comments, the README, task specific instructions, and more. In other words, we had no clear source of truth. Pieces of that truth were scattered across multiple files and the docs were inconsistent and — in some cases — contradictory.&lt;/p&gt;

&lt;p&gt;Sound familiar? It’s the equivalent of having five different “getting started” guides that all got written at different times by different people and nobody bothered to consolidate them. (If you’ve ever worked on a project that’s been around for more than a year with no one in charge of documentation, you know exactly what I’m talking about.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; Establish a single source of truth. Ruthlessly. We consolidated everything into one comprehensive guide and updated all references to point &lt;em&gt;only&lt;/em&gt; there. Deprecated docs were deleted and/or redirected, as appropriate. No more choose your own adventure. No more guessing.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. &lt;strong&gt;Agents won’t optimize themselves&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Here’s a fun one: the agent ran several full production builds—complete with image processing, template compilation… the whole shebang—just to validate a markdown file was in the right format. These builds took 30-60 seconds. &lt;em&gt;Each time.&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;This is like requiring someone to assemble an entire car just to check if the owner’s guide is displaying the right “check engine” symbol. Technically it works, but yikes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; Write fast, focused validation scripts. One tool for each job. Tell the agent what the utility is, where to find it, and how to use it. Give it explicit instructions to use utility scripts in lieu of full site builds whenever possible.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. &lt;strong&gt;Ambiguity breeds confusion (and wasted tokens)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The agent spent 15+ minutes having an internal philosophical debate about whether to process test data. Should it reject it? Accept it? Create a placeholder? The instructions didn’t say, so the agent did what any of us would do: it agonized — or at least feigned agonizing — over the decision and tried to infer intent from context clues.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; Be explicit about edge cases. We added a dedicated section for handling test form submissions. No more guessing.&lt;/p&gt;

&lt;h2&gt;
  
  
  There’s a pattern here
&lt;/h2&gt;

&lt;p&gt;If you squint, all of these issues share a common root cause: &lt;strong&gt;implicit assumptions&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We assumed humans would know to check one doc instead of five. We assumed the difference between validation and building was obvious. We assumed everyone would understand how to handle edge cases.&lt;/p&gt;

&lt;p&gt;AI agents don‘t — can’t? — make those assumptions. They need explicit instructions, clear boundaries, and unambiguous inputs. Honestly? So do humans. We’re just better at muddling through — or think we are.&lt;/p&gt;

&lt;h2&gt;
  
  
  Early results
&lt;/h2&gt;

&lt;p&gt;After implementing these changes, we expect (and early testing confirms):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;~40% reduction in processing time,&lt;/li&gt;
&lt;li&gt;~75% reduction in token usage, and&lt;/li&gt;
&lt;li&gt;&amp;gt;80% reduction in confusion and circular reasoning.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But here's the thing: these improvements don't just help the AI agent. They help &lt;em&gt;everyone&lt;/em&gt;. The consolidated documentation is easier to navigate. The fast validation scripts are useful for humans too. The explicit edge case handling prevents future questions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The key to reducing toil: excellent docs and tools
&lt;/h2&gt;

&lt;p&gt;Optimizing for AI agents isn't really about AI. It’s about removing ambiguity, eliminating redundancy, and making implicit knowledge explicit. It’s about writing code and documentation that doesn’t require a deep understanding of the project to comprehend.&lt;/p&gt;

&lt;p&gt;In other words: it’s just good engineering.&lt;/p&gt;

&lt;p&gt;So if you’re working with AI coding agents — or planning to — invest in your docs and tooling. Don’t think of it as wasted time “writing for robots.” Think of it as paying down documentation debt and building an efficient engineering process.&lt;/p&gt;

&lt;p&gt;Your future self, your teammates, and the bots will thank you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Afterword
&lt;/h2&gt;

&lt;p&gt;Interestingly, an AI agent was a particularly useful partner in finding and addressing the technical debt we’d been living with. Sometimes you need a pedantic robot to point out that your house is a mess.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This post originally appeard on my blog at: &lt;a href="https://www.aaron-gustafson.com/notebook/optimizing-your-codebase-for-ai-coding-agents/" rel="noopener noreferrer"&gt;https://www.aaron-gustafson.com/notebook/optimizing-your-codebase-for-ai-coding-agents/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>coding</category>
      <category>githubcopilot</category>
    </item>
    <item>
      <title>A Web Component for Conditionally Displaying Fields</title>
      <dc:creator>Aaron Gustafson</dc:creator>
      <pubDate>Mon, 20 Oct 2025 17:05:50 +0000</pubDate>
      <link>https://dev.to/aarongustafson/a-web-component-for-conditionally-displaying-fields-47jn</link>
      <guid>https://dev.to/aarongustafson/a-web-component-for-conditionally-displaying-fields-47jn</guid>
      <description>&lt;p&gt;Building on my recent work in the &lt;a href="https://www.aaron-gustafson.com/notebook/series/forms/" rel="noopener noreferrer"&gt;form utility space&lt;/a&gt;, I’ve created a new web component that allows you to conditionally display form fields based on the values of other fields: &lt;code&gt;form-show-if&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This component tackles a common UX pattern that HTML doesn’t natively support. You know the scenario—you have a form where certain fields should only appear when specific conditions are met. Maybe you want to show shipping address fields only when someone checks “Ship to different address,” or display a text input for “Other” when someone selects that option from a dropdown. This web component makes that setup effortless — and declarative.&lt;/p&gt;

&lt;p&gt;You set up &lt;code&gt;form-show-if&lt;/code&gt; like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;form-show-ifconditions=“contact_method=phone”&amp;gt;&amp;lt;labelfor=“phone”&amp;gt;Phone Number&amp;lt;inputtype=“tel”id=“phone”name=“phone”&amp;gt;&amp;lt;/label&amp;gt;&amp;lt;/form-show-if&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You wrap any field and its &lt;code&gt;label&lt;/code&gt; in the component and then declare the conditions under which it should be displayed in the &lt;code&gt;conditions&lt;/code&gt; attribute.&lt;/p&gt;

&lt;h2&gt;
  
  
  # Defining the display conditions
&lt;/h2&gt;

&lt;p&gt;Each condition is a key/value pair where the key aligns to the &lt;code&gt;name&lt;/code&gt; of the field you need to observe and the value is the value that triggers the display. If any value should trigger the display, use an asterisk (&lt;code&gt;*&lt;/code&gt;) as the value. In the example above, the field will become visible only if — in a theoretical contact method choice — a user chooses “phone” as the method they want used.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;conditions&lt;/code&gt; attribute can be populated with as many dependencies as you need. Multiple conditions are separated by double vertical pipes (&lt;code&gt;||&lt;/code&gt;), as in this example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;form-show-ifconditions=“contact_method=phone||contact_method=text_message”&amp;gt;&amp;lt;labelfor=“phone-number”&amp;gt;Phone Number&amp;lt;inputtype=“tel”id=“phone”name=“phone”&amp;gt;&amp;lt;/label&amp;gt;&amp;lt;/form-show-if&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here the field depends on one of the following conditions being true:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;the field matching &lt;code&gt;[name="contact_method"]&lt;/code&gt; has a value of “phone” &lt;em&gt;or&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;the field matching &lt;code&gt;[name="contact_method"]&lt;/code&gt; has a value of “text_message”&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the field you reference doesn’t exist, no errors will be thrown—it will just quietly exit.&lt;/p&gt;

&lt;h2&gt;
  
  
  # Customizing the show/hide behavior
&lt;/h2&gt;

&lt;p&gt;By default, the component uses the &lt;code&gt;hidden&lt;/code&gt; attribute to hide the wrapped content when it’s not needed. But you can customize this behavior using CSS classes instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;form-show-ifconditions=“shipping-method=express”disabled-class=“fade-out”enabled-class=“fade-in”&amp;gt;&amp;lt;labelfor=“delivery-date”&amp;gt;Express Delivery Date&amp;lt;inputtype=“date”id=“delivery-date”name=“delivery-date”&amp;gt;&amp;lt;/label&amp;gt;&amp;lt;/form-show-if&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When using custom classes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;disabled-class&lt;/code&gt;&lt;/strong&gt; is applied when the condition is not met (field should be hidden)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;enabled-class&lt;/code&gt;&lt;/strong&gt; is applied when the condition is met (field should be shown)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both are optional. Just remember that if you define a &lt;code&gt;disabled-class&lt;/code&gt;, the &lt;code&gt;hidden&lt;/code&gt; attribute will not be used — you will need to accessibly hide the content yourself.&lt;/p&gt;

&lt;p&gt;This gives you complete control over the visual presentation. You could use CSS transitions for smooth animations, apply different styling states, or integrate with your existing design system’s utility classes.&lt;/p&gt;

&lt;h2&gt;
  
  
  # Handling form state properly
&lt;/h2&gt;

&lt;p&gt;The component doesn’t just toggle visibility—it also manages the form state correctly. When fields are hidden, they’re automatically disabled using the &lt;code&gt;disabled&lt;/code&gt; attribute. If there are any sibling fields in the component, they will be disabled as well. This prevents these fields from being submitted with the form and ensures they don’t interfere with form validation.&lt;/p&gt;

&lt;p&gt;When conditions are met and fields become visible, they’re re-enabled automatically. This behavior works seamlessly with both native form validation and custom validation scripts.&lt;/p&gt;

&lt;h2&gt;
  
  
  # Real-world examples
&lt;/h2&gt;

&lt;p&gt;Here are some practical use cases where this component shines:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“Other” option handling:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;fieldset&amp;gt;&amp;lt;legend&amp;gt;How did you hear about us?&amp;lt;/legend&amp;gt;&amp;lt;label&amp;gt;&amp;lt;inputtype=“radio”name=“source”value=“google”&amp;gt; Google&amp;lt;/label&amp;gt;&amp;lt;label&amp;gt;&amp;lt;inputtype=“radio”name=“source”value=“friend”&amp;gt; Friend&amp;lt;/label&amp;gt;&amp;lt;label&amp;gt;&amp;lt;inputtype=“radio”name=“source”value=“other”&amp;gt; Other&amp;lt;/label&amp;gt;&amp;lt;form-show-ifconditions=“source=other”&amp;gt;&amp;lt;labelfor=“source-other”&amp;gt;Please specify&amp;lt;inputtype=“text”id=“source-other”name=“source-other”&amp;gt;&amp;lt;/label&amp;gt;&amp;lt;/form-show-if&amp;gt;&amp;lt;/fieldset&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Specific value matching:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;form-show-ifconditions=“email=test@example.com”&amp;gt;&amp;lt;labelfor=“debug-info”&amp;gt;Debug Information&amp;lt;textareaid=“debug-info”name=“debug-info”&amp;gt;&amp;lt;/textarea&amp;gt;&amp;lt;/label&amp;gt;&amp;lt;small&amp;gt;This field only shows for test accounts&amp;lt;/small&amp;gt;&amp;lt;/form-show-if&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  # Progressive enhancement in action
&lt;/h2&gt;

&lt;p&gt;Like all good web components, &lt;code&gt;form-show-if&lt;/code&gt; follows progressive enhancement principles. If JavaScript fails to load or the browser doesn’t support custom elements, your form still works—users just see all the fields all the time. Not ideal for the user experience, but nothing breaks either.&lt;/p&gt;

&lt;p&gt;The component is lightweight, has no dependencies, and works in all modern browsers.&lt;/p&gt;

&lt;h2&gt;
  
  
  # Demo
&lt;/h2&gt;

&lt;p&gt;I’ve put together &lt;a href="https://aarongustafson.github.io/form-show-if/demo.html" rel="noopener noreferrer"&gt;a comprehensive demo showing various use cases and configurations&lt;/a&gt; over on GitHub.&lt;/p&gt;

&lt;p&gt;The demo includes examples of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Basic show/hide functionality&lt;/li&gt;
&lt;li&gt;Multiple condition logic&lt;/li&gt;
&lt;li&gt;Custom CSS class integration&lt;/li&gt;
&lt;li&gt;Complex form scenarios with radio buttons and checkboxes&lt;/li&gt;
&lt;li&gt;Different field grouping approaches&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  # Grab it
&lt;/h2&gt;

&lt;p&gt;You can view the entire project (and suggest enhancements) over on &lt;a href="https://github.com/aarongustafson/form-show-if" rel="noopener noreferrer"&gt;the form-show-if component’s GitHub repo&lt;/a&gt;. The component is available as both a standard script and an ES module, so you can integrate it however works best for your project.&lt;/p&gt;

&lt;p&gt;Installation is straightforward—just include the script in your page and start using the &lt;code&gt;form-show-if&lt;/code&gt; element. No build step required, no framework dependencies, just clean, standards-based progressive enhancement.&lt;/p&gt;

</description>
      <category>webcomponents</category>
      <category>webforms</category>
      <category>progressiveenhanceme</category>
      <category>html</category>
    </item>
    <item>
      <title>Identifying Accessibility Data Gaps in CodeGen Models</title>
      <dc:creator>Aaron Gustafson</dc:creator>
      <pubDate>Thu, 16 Oct 2025 19:12:02 +0000</pubDate>
      <link>https://dev.to/aarongustafson/identifying-accessibility-data-gaps-in-codegen-models-3b7b</link>
      <guid>https://dev.to/aarongustafson/identifying-accessibility-data-gaps-in-codegen-models-3b7b</guid>
      <description>&lt;p&gt;Late last year, I probed an LLM’s responses to HTML code generation prompts to assess its adherence to accessibility best practices. The results were unsurprisingly disappointing — roughly what I’d expect from a developer aware of accessibility but unsure how to implement it. The study highlighted key areas where training data needs improvement.&lt;/p&gt;

&lt;h2&gt;
  
  
  # Why take on this challenge?
&lt;/h2&gt;

&lt;p&gt;I get it — you probably rolled your eyes at yet another “AI and accessibility” post. Maybe you think AI-assisted coding is overhyped, environmentally harmful, unreliable, or just plain dangerous for our craft. I share many of those concerns. But here’s the thing: whether we like it or not, codegen models aren’t going anywhere. GitHub Copilot has millions of users, and tools like Claude Code and Cursor are rapidly gaining popularity.&lt;/p&gt;

&lt;p&gt;So we have a choice: we can complain about the inevitable tide of AI-generated garbage code, or we can get in there and figure out how to make it better — especially when it comes to accessibility.&lt;/p&gt;

&lt;p&gt;We’re facing a looming wave of inaccessible code that will be extremely difficult to remediate later. The foundation models are already being trained on the collective output of the web’s development community — a community that doesn’t have a high bar high for accessibility already. Codegen models are a massive consultancy staffed with &lt;a href="https://christianheilmann.com/2015/07/17/the-full-stackoverflow-developer/" rel="noopener noreferrer"&gt;full StackOverflow developers&lt;/a&gt;. We need to figure out how to make them part of the solution, not part of the problem.&lt;/p&gt;

&lt;p&gt;It’s also worth noting that the better we make the output of these models, the fewer bugs will be generated. That, in turn, means fewer accessibility issues to fix later. If we don’t, there are plenty of AI-assisted scanners out there happy to burn the rainforest to find and remediate the bugs after the fact. We risk doubling the environmental impact—once to generate the bug, and again to fix it. That’s not the future I want. The reality here is that the only way to deal with this flood of AI-generated code is to make sure it’s good code in the first place.&lt;/p&gt;

&lt;h2&gt;
  
  
  # How did I conduct my research?
&lt;/h2&gt;

&lt;p&gt;Rather than relying on anecdotal evidence or cherry-picked examples, I built a systematic approach to evaluate how well LLMs — starting with GPT-4 — generate accessible HTML. The methodology is straightforward but comprehensive: I created a Python testing framework that sent carefully crafted prompts to Azure OpenAI’s GPT 4 model, collected the generated HTML responses, and then manually analyzed these responses for accessibility compliance.&lt;/p&gt;

&lt;p&gt;Here’s how it works:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prompt Engineering&lt;/strong&gt; : I designed prompts that ask for specific UI components—form fields, navigation menus, interactive elements—without explicitly mentioning accessibility requirements. This gives us a baseline of what the model considers “standard” output. I included one prompt that specifically requested accessibility features to see if the model could improve when guided. I suspected it would often add ARIA attributes without addressing underlying issues, but I wanted to validate that too.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Response Collection&lt;/strong&gt; : For each prompt, I generated 10 iterations at high temperature (0.95) to capture the model’s range of responses. Each unique response got saved as an individual HTML file for analysis.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Systematic Analysis&lt;/strong&gt; : I manually review each generated code snippet, cataloging accessibility errors, warnings, and missed opportunities. I tried using the LLM as a judge, but even with a detailed rubric, the results were poor. My eval looked specifically for things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Improper semantic HTML usage&lt;/li&gt;
&lt;li&gt;Missing or incorrect ARIA attributes&lt;/li&gt;
&lt;li&gt;Keyboard navigation issues&lt;/li&gt;
&lt;li&gt;Screen reader compatibility problems&lt;/li&gt;
&lt;li&gt;Form labeling errors&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When I identified errors, I remediated them and committed the remediated file to the repo with a commit message that included all of the issues and warnings on its own line.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diff-Based Retesting&lt;/strong&gt; : I wanted to see if diff data could improve future codegen requests, so I created a tool to generate a collection of &lt;code&gt;.diff&lt;/code&gt; files for each pattern that included the commit message as a header in each file. I then used those diff files as part of a new instance of the prompt to test whether the model can improve its output when guided.&lt;/p&gt;

&lt;h2&gt;
  
  
  # What did I learn?
&lt;/h2&gt;

&lt;p&gt;After analyzing hundreds of generated code snippets, the results are sobering. The model consistently demonstrates what I’d describe as superficial awareness without true understanding — it knows accessibility concepts exist but fundamentally misunderstands their purpose and proper implementation.&lt;/p&gt;

&lt;p&gt;Here are some of the patterns I’ve documented:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Form Label Disasters&lt;/strong&gt; : When asked to create a required text field, the model failed to include a visible label:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;inputtype=“text”id=“orangeColor”name=“orangeColor”requiredplaceholder=“What color is an orange?”&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sure, the &lt;code&gt;placeholder&lt;/code&gt; attribute is there, and in a pinch it will be included in a field’s accessible name calculation, but sighted users will lose the label as soon as they start typing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ARIA Attribute Confusion&lt;/strong&gt; : The model would routinely involce ARIA for no reason:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;labelfor=“color-question”&amp;gt;What color is an orange? &amp;lt;spanstyle=“color: red;”&amp;gt;*&amp;lt;/span&amp;gt;&amp;lt;/label&amp;gt;&amp;lt;inputtype=“text”id=“color-question”name=“color-question”requiredaria-required=“true”aria-labelledby=“color-question”&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here the &lt;code&gt;for&lt;/code&gt; attribute already establishes the relationship between the label and input, so &lt;code&gt;aria-labelledby&lt;/code&gt; is redundant. A bit of a nitpick, but the &lt;code&gt;aria-required="true"&lt;/code&gt; is also unnecessary since the native &lt;code&gt;required&lt;/code&gt; attribute already conveys that information to assistive technologies. &lt;code&gt;aria-required="true"&lt;/code&gt; is only needed when creating custom form controls non-semantic markup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Redundant ARIA&lt;/strong&gt; : Keeping on the ARIA redundancy, consider examples like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;inputtype=“radio”id=“option1”aria-labelledby=“label1”aria-label=“Option 1”&amp;gt;&amp;lt;labelfor=“option1”id=“label1”&amp;gt;Option 1&amp;lt;/label&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This redundancy raises the question &lt;em&gt;why‽&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Required Field Misapplication&lt;/strong&gt; : For checkbox groups where users need to select “one or more,” the model often adds &lt;code&gt;required&lt;/code&gt; to individual checkboxes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;fieldset&amp;gt;&amp;lt;legend&amp;gt;What fruits do you like?&amp;lt;/legend&amp;gt;&amp;lt;div&amp;gt;&amp;lt;inputtype=“checkbox”id=“bananas”name=“fruits”value=“bananas”required&amp;gt;&amp;lt;labelfor=“bananas”&amp;gt;Bananas&amp;lt;/label&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;div&amp;gt;&amp;lt;inputtype=“checkbox”id=“oranges”name=“fruits”value=“oranges”required&amp;gt;&amp;lt;labelfor=“oranges”&amp;gt;Oranges&amp;lt;/label&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;div&amp;gt;&amp;lt;inputtype=“checkbox”id=“apples”name=“fruits”value=“apples”required&amp;gt;&amp;lt;labelfor=“apples”&amp;gt;Apples&amp;lt;/label&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;divstyle=“color: red;display: none;”id=“validation-error”&amp;gt;You must choose one or more fruits&amp;lt;/div&amp;gt;&amp;lt;/fieldset&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This breaks the intended behavior—if any checkbox is marked required, it must be checked for form validation to pass. For a web component that addresses this limitation in HTML, see my post “&lt;a href="https://dev.to/notebook/requirement-rules-for-checkboxes/"&gt;Requirement Rules for Checkboxes&lt;/a&gt;.”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Grouped Field Confusion&lt;/strong&gt; : Not understanding when to use &lt;code&gt;fieldset&lt;/code&gt; and &lt;code&gt;legend&lt;/code&gt; (or at least using &lt;code&gt;role="group"&lt;/code&gt; and &lt;code&gt;aria-labelledby&lt;/code&gt;) on a field group:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div&amp;gt;&amp;lt;label&amp;gt;Select Theme:&amp;lt;/label&amp;gt;&amp;lt;div&amp;gt;&amp;lt;inputtype=“radio”id=“light”name=“theme”value=“light”&amp;gt;&amp;lt;labelfor=“light”&amp;gt;Light&amp;lt;/label&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;div&amp;gt;&amp;lt;inputtype=“radio”id=“dark”name=“theme”value=“dark”&amp;gt;&amp;lt;labelfor=“dark”&amp;gt;Dark&amp;lt;/label&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;div&amp;gt;&amp;lt;inputtype=“radio”id=“high-contrast”name=“theme”value=“high-contrast”&amp;gt;&amp;lt;labelfor=“high-contrast”&amp;gt;High Contrast&amp;lt;/label&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;You can change this later&amp;lt;/p&amp;gt;&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ideally, this would be a &lt;code&gt;fieldset&lt;/code&gt; with a &lt;code&gt;legend&lt;/code&gt; and the descriptive text would appear right after the &lt;code&gt;legend&lt;/code&gt; and be associated with the group using &lt;code&gt;aria-describedby&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Color-Only Error Indication&lt;/strong&gt; : Generating error states that rely solely on color changes without text indicators or proper ARIA attributes to convey the error state to screen readers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unnecessary Role Additions&lt;/strong&gt; : Adding redundant roles like &lt;code&gt;role="radiogroup"&lt;/code&gt; to properly structured fieldsets containing radio inputs, where the native semantics already provide the correct accessibility tree.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Missing Error State Management&lt;/strong&gt; : Failing to include &lt;code&gt;aria-invalid="true"&lt;/code&gt; on fields with errors or properly associate error messages with their corresponding form controls.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lack of Wayfinding Help&lt;/strong&gt; : Failing to include navigational labels and &lt;code&gt;aria-current="page"&lt;/code&gt; in a breadcrumb nav.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adding Unnecessary JavaScript&lt;/strong&gt; : Even though it was instructed to only generate JavaScript when absolutely necessary, the model would often inject JavaScript for simple tasks that could be handled with HTML and CSS alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  # How Does This Help?
&lt;/h2&gt;

&lt;p&gt;Here’s where things get interesting — and hopeful. When I retested using prompts that included accessibility hints, the model’s output improved dramatically. Not just slightly better, but often going from fundamentally broken to genuinely accessible.&lt;/p&gt;

&lt;p&gt;For example, when I added diff data related to fieldset use to a prompt about radio button groups, the model switched from generating meaningless &lt;code&gt;div&lt;/code&gt; wrappers to proper semantic structures.&lt;/p&gt;

&lt;p&gt;This suggests the model can produce quality code if properly primed. It also indicates that the training data likely lacks sufficient examples of well-implemented accessible components. If the model had been trained on a richer dataset of accessible code, it might not need such explicit guidance to produce good results.&lt;/p&gt;

&lt;h2&gt;
  
  
  # Where Do We Go From Here?
&lt;/h2&gt;

&lt;p&gt;These findings point to several concrete approaches for improving accessibility in AI-generated code:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Enhanced Training Data&lt;/strong&gt; : The models need exposure to more high-quality, accessible code examples. Current training data clearly overrepresents inaccessible implementations. We need comprehensive datasets of properly implemented accessible components across different frameworks and use cases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Accessibility-Aware Fine-Tuning&lt;/strong&gt; : Post-training refinement specifically focused on accessibility compliance could help models prioritize inclusive patterns. This could involve training on accessibility-annotated code pairs — showing inaccessible implementations alongside their accessible counterparts, like the diffs do.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prompt Engineering Guidelines&lt;/strong&gt; : Tool creators should integrate accessibility considerations into their default system prompts. Instead of just asking for “clean, semantic HTML,” prompts should provide detailed instructions to demonstrate accessibility best practices rather than pointing at often vague guidelines like WCAG."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Integrated Accessibility Validation&lt;/strong&gt; : IDE integrations should include real-time accessibility linting of AI-generated code, providing immediate feedback and suggestions for improvement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Community-Contributed Training Data&lt;/strong&gt; : We should coordinate our efforts to produce an open source, high-quality accessible code dataset so that this data can be integrated into future models.&lt;/p&gt;




&lt;p&gt;The data from this project provides a roadmap for where to focus these efforts. We’re not dealing with models that are fundamentally incapable of generating accessible code — we’re dealing with models that haven’t been properly trained to prioritize accessibility by default.&lt;/p&gt;

&lt;h2&gt;
  
  
  # Want to Get Involved?
&lt;/h2&gt;

&lt;p&gt;If you want to conduct similar evaluations with your preferred models or specific use cases, I’ve created a template repository with the testing framework: &lt;a href="https://github.com/aarongustafson/CodeGen-Model-Eval-and-Refine-Tools" rel="noopener noreferrer"&gt;CodeGen Model Eval and Refine Tools&lt;/a&gt;. It includes the Python testing harness, prompt templates, and analysis guidelines to get you started.&lt;/p&gt;

&lt;p&gt;The complete findings, methodology details, and code samples for my research are available &lt;a href="https://github.com/aarongustafson/testing-llm-code-a11y" rel="noopener noreferrer"&gt;on GitHub&lt;/a&gt;. I encourage you to dig into the data — it’s eye-opening and frustrating, yes, but ultimately actionable.&lt;/p&gt;

&lt;p&gt;There are other projects and research exploring this space as well. A few worth checking out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://aimac.ai/" rel="noopener noreferrer"&gt;AIMAC&lt;/a&gt; - The AI Model Accessibility Checker (AIMAC) Leaderboard measures how well LLMs generate accessible HTML pages using neutral prompts without specific accessibility guidance. Checks are performed with axe-core.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/microsoft/a11y-llm-eval" rel="noopener noreferrer"&gt;A11y LLM Evaluation Harness and Dataset&lt;/a&gt; - A more recent research project to evaluate how well various LLM models generate accessible HTML content.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;We’re at a critical moment where the patterns established in AI-assisted development will shape the accessibility of the web for years to come. We can either let this technology amplify existing accessibility problems, or we can tackle the problems head-on and be part of the solution.&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>aiml</category>
      <category>html</category>
    </item>
    <item>
      <title>Passing Your CSS Theme to `canvas`</title>
      <dc:creator>Aaron Gustafson</dc:creator>
      <pubDate>Thu, 01 May 2025 21:49:27 +0000</pubDate>
      <link>https://dev.to/aarongustafson/passing-your-css-theme-to-canvas-2gfn</link>
      <guid>https://dev.to/aarongustafson/passing-your-css-theme-to-canvas-2gfn</guid>
      <description>&lt;p&gt;While working on a recent project I noticed an issue with a &lt;code&gt;canvas&lt;/code&gt;-based audio visualization when I toggled between light and dark modes. When I’d originally set it up I was browsing in dark mode and the light visualization stroke showed up perfectly on the dark background, but it was invisible when viewed using the light theme (which I’d neglected to test). I searched around, but didn’t find any articles on easy ways to make &lt;code&gt;canvas&lt;/code&gt; respond nicely to user preserences, so I thought I’d share (in brief) how I solved it.&lt;/p&gt;

&lt;h2&gt;
  
  
  # The CSS Setup
&lt;/h2&gt;

&lt;p&gt;The themeing of this particular project uses &lt;a href="https://developer.mozilla.org/docs/Web/CSS/CSS_cascading_variables/Using_CSS_custom_properties" rel="noopener noreferrer"&gt;CSS custom properties&lt;/a&gt;. For simplicty I’m going to set up two named colors and then use two theme-specific custom properties to apply them in the default light theme and the dark theme:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;:root{–color-dark: #222;–color-light:rgba(255, 255, 255, 0.5);–color-background:var(–color-light);–color-foreground:var(–color-dark);}@media(prefers-color-scheme: dark){:root{–color-background:var(–color-dark);–color-foreground:var(–color-light);}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  # Applying the Theme to Canvas
&lt;/h2&gt;

&lt;p&gt;To get the theme into my &lt;code&gt;canvas&lt;/code&gt;-related code, I set up a &lt;code&gt;theme&lt;/code&gt; object to hold the values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const theme ={};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, I wrote a function to pull in the theme colors using &lt;a href="https://developer.mozilla.org/docs/Web/API/Window/getComputedStyle" rel="noopener noreferrer"&gt;&lt;code&gt;window.getComputedStyle()&lt;/code&gt;&lt;/a&gt;. After defining the function, I call it immediately to populate the &lt;code&gt;theme&lt;/code&gt; object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;functionimportTheme(){theme.foreground =window.getComputedStyle(document.documentElement).getPropertyValue(“–color-foreground”).trim()||“black”;theme.background =window.getComputedStyle(document.documentElement).getPropertyValue(“–color-background”).trim()||“white”;}importTheme();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I set this up with just two theme colors, but you can import as many (or few) as you like. Be sure to set a sensible default or fallback for each color though, just in case your theme’s custom property names change.&lt;/p&gt;

&lt;p&gt;With this in place, I can set my &lt;code&gt;canvas&lt;/code&gt; animation’s colors by referencing them from the &lt;code&gt;theme&lt;/code&gt; object. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;context.fillStyle = theme.foreground;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  # Keeping Things in Sync
&lt;/h2&gt;

&lt;p&gt;The final bit of magic comes when you add an event listener to a &lt;code&gt;MediaQueryList&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const mediaQuery = window.matchMedia(“(prefers-color-scheme: dark)”);mediaQuery.addEventListener(“change”, importTheme);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here I’ve used &lt;code&gt;matchMedia()&lt;/code&gt; to get a &lt;code&gt;MediaQueryList&lt;/code&gt; object. Typically we use the &lt;code&gt;matches&lt;/code&gt; property of this object to establish whether the media query currently matches or not. A lesser-known option, however, is that you can attach an event listener to it that will be triggered whenever the query’s status changes. So cool! With this in place, the &lt;code&gt;canvas&lt;/code&gt; contents will update whenever the user’s theme changes. Here’s an example of that:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=pALIuO5uHUA" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=pALIuO5uHUA&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This video demonstrates how a canvas element rendering a dark sine wave against a light background can miraculously transform into a light sine wave against a dark background using CSS custom properties and a bit of JavaScript.&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  # Demo
&lt;/h1&gt;

&lt;p&gt;I put together &lt;a href="https://codepen.io/aarongustafson/pen/LEEQyqg" rel="noopener noreferrer"&gt;a quick demo of this&lt;/a&gt; in a fork of &lt;a href="https://codepen.io/alvinshaw/pen/mdEKggg" rel="noopener noreferrer"&gt;Alvin Shaw’s Canvas Sine Wave Experiment&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Hopefully this is helpful to someone out there. Happy themeing!&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>animation</category>
      <category>css</category>
      <category>design</category>
    </item>
    <item>
      <title>A Web Component for Conditional Dependent Fields</title>
      <dc:creator>Aaron Gustafson</dc:creator>
      <pubDate>Wed, 14 Aug 2024 03:26:03 +0000</pubDate>
      <link>https://dev.to/aarongustafson/a-web-component-for-conditional-dependent-fields-1cdn</link>
      <guid>https://dev.to/aarongustafson/a-web-component-for-conditional-dependent-fields-1cdn</guid>
      <description>&lt;p&gt;A few weeks back I released &lt;a href="https://dev.to/aarongustafson/requirement-rules-for-checkboxes-3g88-temp-slug-6030457"&gt;a web component to enable you to add requirement rules to checkbox groups&lt;/a&gt;. Continuing in the form utility space, I’ve created a new web component that allows you to make fields required based on the values of other fields: &lt;code&gt;form-required-if&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;form-required-if&lt;/code&gt; web component, which is based on &lt;a href="https://github.com/easy-designs/jquery.easy-dependent-required-fields.js" rel="noopener noreferrer"&gt;a jQuery plugin I’d written in 2012&lt;/a&gt;, looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form-required-if&lt;/span&gt;
  &lt;span class="na"&gt;conditions=&lt;/span&gt;&lt;span class="s"&gt;"email=*"&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;
    Required if there’s an email value
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"depends-on-email"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form-required-if&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You wrap any field (and its &lt;code&gt;label&lt;/code&gt;) in the component and then declare the conditions under which it should be required in the &lt;code&gt;conditions&lt;/code&gt; attribute.&lt;/p&gt;

&lt;h2&gt;
  
  
  # Defining the requirement conditions
&lt;/h2&gt;

&lt;p&gt;Each condition is a key/value pair where the key aligns to the &lt;code&gt;name&lt;/code&gt; of the field you need to observe and the value is the value that could trigger the dependency. If any value should trigger the dependency, you use an asterisk () as the value. In the example above, the field will become required when any value is assigned to the field matching &lt;code&gt;[name="email"]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This &lt;code&gt;conditions&lt;/code&gt; attribute can be populated with as many or as few dependencies as make sense for your use case. Multiple conditions are separated by double vertical pipes (&lt;code&gt;||&lt;/code&gt; a.k.a. &lt;em&gt;or&lt;/em&gt;) as in this example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form-required-if&lt;/span&gt; 
  &lt;span class="na"&gt;conditions=&lt;/span&gt;&lt;span class="s"&gt;"email=*||test=3"&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;
    Depends on email or test field
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"depends-on-email-or-test"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form-required-if&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here the field depends on one of the following conditions being true:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;the field matching &lt;code&gt;[name="email"]&lt;/code&gt; has a value, &lt;em&gt;or&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;the field matching &lt;code&gt;[name="test"]&lt;/code&gt; has a value of “3”&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the field you reference doesn’t exist, no errors will be thrown, it will just quietly exit.&lt;/p&gt;

&lt;h2&gt;
  
  
  # Visually indicating a field is required
&lt;/h2&gt;

&lt;p&gt;If you typically use an asterisk or similar to indicate a field is required, this web component can support that through one or both of the following attributes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;indicator&lt;/code&gt; - This attribute is where you define the indicator itself. It could be something as simple as a string (e.g., &lt;em&gt;), or even full-blown HTML.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;indicator-placement&lt;/code&gt; - As you can probably guess, this attribute is used to set the position of the indicator. If you want it at the start of the label text, you give it the value “before.” If you want it after the text, you use “after” or don’t use the attribute at all. Indicators will be placed after the label text by default.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s an example with a custom indicator that is HTML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form-required-if&lt;/span&gt;
  &lt;span class="na"&gt;conditions=&lt;/span&gt;&lt;span class="s"&gt;"email=*"&lt;/span&gt;
  &lt;span class="na"&gt;indicator=&lt;/span&gt;&lt;span class="s"&gt;"&amp;lt;b&amp;gt;&amp;lt;/b&amp;gt;"&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;
    Depends on email
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"dep2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form-required-if&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you don’t include markup in your indicator, it will be automatically wrapped in &lt;code&gt;span&lt;/code&gt; when injected into the DOM. The &lt;code&gt;hidden&lt;/code&gt; and &lt;code&gt;aria-hidden&lt;/code&gt; attributes are used to toggle its visibility, relative to the requirement state of the field.&lt;/p&gt;

&lt;h2&gt;
  
  
  # Demo
&lt;/h2&gt;

&lt;p&gt;I put together &lt;a href="https://aarongustafson.github.io/form-required-if/demo.html" rel="noopener noreferrer"&gt;a relatively simple demo of the web component&lt;/a&gt; over on GitHub:&lt;/p&gt;

&lt;h2&gt;
  
  
  # Grab It
&lt;/h2&gt;

&lt;p&gt;You can view the entire project (and suggest enhancements) over on &lt;a href="https://github.com/aarongustafson/form-required-if" rel="noopener noreferrer"&gt;the &lt;code&gt;form-required-if&lt;/code&gt; component’s Github repo&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>progressiveenhancement</category>
      <category>forms</category>
      <category>html</category>
      <category>javascript</category>
    </item>
    <item>
      <title>On CrowdStrike, dependencies, and building robust products on the web</title>
      <dc:creator>Aaron Gustafson</dc:creator>
      <pubDate>Thu, 25 Jul 2024 17:20:26 +0000</pubDate>
      <link>https://dev.to/aarongustafson/on-crowdstrike-dependencies-and-building-robust-products-on-the-web-38k0</link>
      <guid>https://dev.to/aarongustafson/on-crowdstrike-dependencies-and-building-robust-products-on-the-web-38k0</guid>
      <description>&lt;p&gt;I have no opinion on CrowdStrike as a company or service. I’ve never used their products. In fact, prior to &lt;a href="https://wikipedia.org/wiki/2024_CrowdStrike_incident" rel="noopener noreferrer"&gt;the incident last week&lt;/a&gt;, I had only a passing familiarity with their name — likely from headlines in the tech press I’d scrolled past at some point in time. I now have a vague understanding of what they do, but that’s only based on what I learned about &lt;a href="https://www.crowdstrike.com/wp-content/uploads/2024/07/CrowdStrike-PIR-Executive-Summary.pdf" rel="noopener noreferrer"&gt;the cause of the incident&lt;/a&gt;. In reflecting on this unfortunate incident, I can’t help but think of the lesson it holds for web designers and developers.&lt;/p&gt;

&lt;p&gt;The incident was caused when a bug in CrowdStrike’s code made it out into production. The results were catastrophic: It caused roughly 8.5 million servers to crash. Hospitals weren’t able to serve the people that needed them. Airline passengers were stranded. People couldn’t access their money in banks. Folks in distress could not get the emergency services they needed. On top of that, the financial fallout is estimated to be somewhere around US$10 billion.&lt;/p&gt;

&lt;p&gt;Bugs happen. I’d hate to be the person who was responsible for that particular bug (or the people in the quality assurance team that should have caught it), but the reality is that none of us who write code write &lt;em&gt;perfect&lt;/em&gt; code. We all make mistakes and sometimes those mistakes make it into production. Other times, the code we write works perfectly during development and testing, but causes an unexpected issue in production. Sometimes only in very specific “edge case” circumstances that we didn’t have the foresight to consider.&lt;/p&gt;

&lt;p&gt;Which brings me to the lesson I took away from the CrowdStrike incident: minimize the impact dependencies can have on your customers’ ability to complete critical tasks. In other words, &lt;a href="https://www.smashingmagazine.com/2016/05/developing-dependency-awareness/" rel="noopener noreferrer"&gt;develop dependency awareness&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The web is a hostile operating environment. Sometimes network connections are slow to resolve or time out completely, which may cause issues with your JavaScript, may result in broken images or videos, or could make it so your user never receives your CSS. Sometimes 3rd party scripts ship bugs that can hose your site completely. Sometimes a customer’s browser plugin can wreak havoc on your site by adjusting markup or injecting code. If any (or all) of those things were to happen, could your customers still accomplish their key tasks? Could they even understand your site at all?&lt;/p&gt;

&lt;p&gt;This is why it’s so critical to start with a fully-functional website that relies only on semantic, accessible HTML and regular ol’ links and form submissions. They aren’t sexy, but they’re solid. Then &lt;em&gt;progressively enhance&lt;/em&gt; that experience to improve things when the CSS is downloaded. And improve some more when your JavaScript executes properly. Build an awareness for the kinds of dependencies you have in your code so you can ensure there is always a fallback.&lt;/p&gt;

&lt;p&gt;When I think about building robust websites like this, I often think of the Chrysler Imperial. The 1964-1966 model is one of the few cars that has been outright banned from entering demolition derby events. It is just too well built. It just takes the hits and keeps on driving. We should aspire to that kind of resilience in the websites we build.&lt;/p&gt;

&lt;p&gt;Bugs happen. Can your site withstand them? Or will you let the failure of a single dependency (a.k.a., site fragility) ruin your customers’ day?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=-9GGDOUDLhc&amp;amp;start=7&amp;amp;end=17" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=-9GGDOUDLhc&amp;amp;start=7&amp;amp;end=17&lt;/a&gt;&lt;/p&gt;

</description>
      <category>progressiveenhancement</category>
      <category>css</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Requirement Rules for Checkboxes</title>
      <dc:creator>Aaron Gustafson</dc:creator>
      <pubDate>Fri, 05 Jul 2024 21:08:34 +0000</pubDate>
      <link>https://dev.to/aarongustafson/requirement-rules-for-checkboxes-29p2</link>
      <guid>https://dev.to/aarongustafson/requirement-rules-for-checkboxes-29p2</guid>
      <description>&lt;p&gt;HTML checkboxes debuted as &lt;a href="https://datatracker.ietf.org/doc/html/rfc1866#section-8.1.2.3" rel="noopener noreferrer"&gt;part of HTML 2.0 in 1995&lt;/a&gt;. Our ability to mark an individual checkbox as being required became part of the HTML5 spec that published in 2014. A decade later, we can still only make checkboxes required on a case-by-case basis. To overcome this limitation, I had created &lt;a href="https://github.com/easy-designs/easy-checkbox-required.js" rel="noopener noreferrer"&gt;a jQuery plugin that allowed me to indicate that a user should choose a specific number of items from within a checkbox group&lt;/a&gt;. Yesterday I turned that plugin into a web component: &lt;a href="https://github.com/aarongustafson/form-required-checkboxes" rel="noopener noreferrer"&gt;&lt;code&gt;form-required-checkboxes&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  # Markup Assumptions
&lt;/h2&gt;

&lt;p&gt;Before I tuck into the details, I’ll start by saying that the web component begins with the assumption that you are following best practices with respect to form markup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your checkbox group should be in a &lt;code&gt;fieldset&lt;/code&gt; with a &lt;code&gt;legend&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;All of the checkbox elements must have the same &lt;code&gt;name&lt;/code&gt; (e.g., “foo[]”).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words, they should look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;fieldset&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;legend&amp;gt;&lt;/span&gt;Group 1 label&lt;span class="nt"&gt;&amp;lt;/legend&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
          &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"checkbox"&lt;/span&gt;
          &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"foo[]"&lt;/span&gt;
          &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        First item
      &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
          &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"checkbox"&lt;/span&gt;
          &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"foo[]"&lt;/span&gt;
          &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"2"&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; 
        Second item
      &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- options continue --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/fieldset&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To use the web component, you wrap the group in a &lt;code&gt;form-required-checkboxes&lt;/code&gt; element and then include the JavaScript to initialize it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form-required-checkboxes&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;fieldset&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;legend&amp;gt;&lt;/span&gt;Group 1 label&lt;span class="nt"&gt;&amp;lt;/legend&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- etc. --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/fieldset&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form-required-checkboxes&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- at the end of your document --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script
  &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/js/components/form-required-checkboxes.js"&lt;/span&gt; 
  &lt;span class="na"&gt;async&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you’re following right along, there’s an error waiting for you in the &lt;code&gt;console&lt;/code&gt; — we need to set the requirement rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  # The API
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;form-required-checkboxes&lt;/code&gt; element requires at least one attribute to function, but using some of the others you can more fully customize the experience for users:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;required&lt;/code&gt; - Represents the range of required values. You can set this up in one of three ways depending on your needs:

&lt;ul&gt;
&lt;li&gt;Single number (e.g., 3) requires exactly that number of choices.&lt;/li&gt;
&lt;li&gt;Range (e.g., 3-5) requires a minimum of the first number and a max of the second number be chosen.&lt;/li&gt;
&lt;li&gt;Max (e.g., 0-3) requires a minimum of zero and a max of the second number to be chosen.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;notice&lt;/code&gt; (optional) - This is a string description that explains details of the required value in plan language. If you don’t supply one, the component will create one for you. This description will be added as a &lt;code&gt;small&lt;/code&gt; element within the component (as a sibling to the &lt;code&gt;fieldset&lt;/code&gt;).&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;error&lt;/code&gt; (optional) - This is a string validation error you’d like to be shown when the validation criteria is not met. By default the component will use the notice text, but this gives you more flexibility.&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  # Demo
&lt;/h2&gt;

&lt;p&gt;I put together &lt;a href="https://aarongustafson.github.io/form-required-checkboxes/demo.html" rel="noopener noreferrer"&gt;a relatively simple demo of the web component&lt;/a&gt; over on GitHub.&lt;/p&gt;

&lt;h2&gt;
  
  
  # Grab It
&lt;/h2&gt;

&lt;p&gt;You can view the entire project (and suggest enhancements) over on &lt;a href="https://github.com/aarongustafson/form-required-checkboxes" rel="noopener noreferrer"&gt;the component’s Github repo&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>forms</category>
      <category>html</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
