{"version":"https://jsonfeed.org/version/1","title":"Aaron Gustafson: Content tagged progressive enhancement","description":"The latest 20 posts and links tagged progressive enhancement.","home_page_url":"https://www.aaron-gustafson.com","feed_url":"https://www.aaron-gustafson.com/feeds/progressive-enhancement.json","author":{"name":"Aaron Gustafson","url":"https://www.aaron-gustafson.com"},"icon":"https://www.aaron-gustafson.com/i/og-logo.png","favicon":"https://www.aaron-gustafson.com/favicon.png","expired":false,"items":[{"id":"https://www.aaron-gustafson.com/notebook/links/accessible-faux-nested-interactive-controls/","title":"🔗 Accessible faux-nested interactive controls","summary":"Eric Bailey walks through a clever pattern for getting the feel of nested interactive controls without actually violating the platform’s semantics.","content_html":"<p>Eric Bailey walks through a clever pattern for getting the feel of nested interactive controls without sacrifice the platform’s semantics to get there.</p>\n<p>In this piece, Eric digs into accessible name length, accidental activation, stacking context weirdness, and progressive enhancement too. That kind of thoroughness matters because patterns like this can go sideways in a hurry when folks cargo-cult only the CSS and skip the reasoning.</p>\n","social_text":"This is a clever pattern from Eric Bailey: keep the larger click target, keep the secondary actions, and don’t sacrifice semantics to get there.","url":"https://www.aaron-gustafson.com/notebook/links/accessible-faux-nested-interactive-controls/","external_url":"https://piccalil.li/blog/accessible-faux-nested-interactive-controls/","tags":["accessibility","CSS","progressive enhancement"],"image":"https://piccalil.b-cdn.net/api/og-image?slug=accessible-faux-nested-interactive-controls/","date_published":"2026-04-29T12:35:00Z"},{"id":"https://www.aaron-gustafson.com/notebook/links/under-the-hood-of-mdns-new-frontend/","title":"🔗 Under the hood of MDN’s new frontend","summary":"This is a fascinating look at how MDN rebuilt its frontend around server-rendered HTML, web components, and a clear understanding of where interactivity actually belongs.","content_html":"<p>This is a fascinating look at how MDN rebuilt its frontend around server-rendered HTML, web components, and a clear understanding of where interactivity actually belongs (and how to deliver it).</p>\n<p>I particularly appreciated how plainly this post describes a mismatch a lot of teams create for themselves: wrapping largely-static content in an app shell, then shipping a pile of JavaScript just to re-assert what the server already knew. MDN’s new approach feels refreshing, despite being grounded in age-old best practices: Keep the content first-class, treat interactivity as optional (and isolated), and only ship what the page actually needs.</p>\n<p>There’s a lot to like here, but more than anything I love seeing MDN embrace an architecture that’s truly aligned with the platform it documents.</p>\n","social_text":"This is a fascinating architecture write-up from MDN: fewer abstractions, less shipped JavaScript, and interactivity where it actually belongs.","url":"https://www.aaron-gustafson.com/notebook/links/under-the-hood-of-mdns-new-frontend/","external_url":"https://developer.mozilla.org/en-US/blog/mdn-front-end-deep-dive/","tags":["web components","web development","progressive enhancement"],"date_published":"2026-04-24T12:05:00Z"},{"id":"https://www.aaron-gustafson.com/notebook/links/nice-select/","title":"🔗 Nice Select","summary":"This is a terrific demonstration of how far the platform has come: a richly styled <code>select</code> that leans on native semantics, native accessibility, and progressive enhancement instead of fighting the browser.","content_html":"<p>This is a terrific demonstration of how far the platform has come: a richly styled <code>select</code> that leans on native semantics, native accessibility, and progressive enhancement instead of fighting the browser.</p>\n<p>What I especially appreciate here is the restraint. Yes, there’s some clever CSS and a little JavaScript to help with positioning, but the whole thing is built on top of the platform, not in spite of it. That’s the interesting takeaway for me: we can build interfaces that feel bespoke without throwing away the reliability and accessibility native controls already give us.</p>\n","social_text":"This is exactly the sort of UI work I love seeing: ambitious, polished, and still rooted in native controls and progressive enhancement.","url":"https://www.aaron-gustafson.com/notebook/links/nice-select/","external_url":"https://nerdy.dev/nice-select","tags":["CSS","HTML","progressive enhancement"],"image":"https://nerdy.dev/media/nice-selects.jpg","date_published":"2026-04-23T12:00:00Z"},{"id":"https://www.aaron-gustafson.com/notebook/visual-validation-feedback-for-form-fields/","title":"✍🏻 Visual Validation Feedback for Form Fields","summary":"The <code>form-validation-list</code> web component provides real-time visual feedback on validation requirements, showing users which rules they have satisfied as they type.","content_html":"<p>Password requirements, username rules, input format constraints: forms often have multiple validation requirements, but users frequently do not find out whether they are meeting them until they hit submit. The <code>form-validation-list</code> web component changes that by providing real-time visual feedback as users type, showing exactly which requirements are met and which are not.</p>\n<p><ins datetime=\"2026-04-30T00:00:00+00:00\"><strong>Update:</strong> This post has been refreshed to cover the component’s current loading options, throttled input behavior, accessibility model, and localization hooks.</ins></p>\n<p>This is a modern replacement for my old <a href=\"https://github.com/easy-designs/jquery.easy-validation-rules.js\">jQuery Easy Validation Rules</a> plugin, reimagined as a web component with native form validation integration.</p>\n<hr>\n<p>To get started, associate the component with an <code>input</code> element using the <code>for</code> attribute and define your validation rules:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>form</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>label</span> <span class=\"token attr-name\">for</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>username<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>Username:<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>label</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>input</span> <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>text<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">id</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>username<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">name</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>username<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">required</span> <span class=\"token punctuation\">/></span></span>\n\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>form-validation-list</span> <span class=\"token attr-name\">for</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>username<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>ul</span><span class=\"token punctuation\">></span></span>\n      <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>li</span> <span class=\"token attr-name\">data-pattern</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>[A-Z]+<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>At least one capital letter<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>li</span><span class=\"token punctuation\">></span></span>\n      <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>li</span> <span class=\"token attr-name\">data-pattern</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>[a-z]+<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>At least one lowercase letter<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>li</span><span class=\"token punctuation\">></span></span>\n      <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>li</span> <span class=\"token attr-name\">data-pattern</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>[\\d]+<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>At least one number<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>li</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>ul</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>form-validation-list</span><span class=\"token punctuation\">></span></span>\n\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>button</span> <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>submit<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>Submit<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>button</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>form</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>By default, validation runs on the <code>input</code> event with a 250ms throttle. Matched rules get a checkmark (✓), unmatched rules get an X (✗), and while someone is typing the component announces a concise progress summary instead of repeatedly re-reading the whole rule list. When all rules match, the field is valid and the form can be submitted.</p>\n<h2 id=\"what%E2%80%99s-happening-under-the-hood%3F\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#what%E2%80%99s-happening-under-the-hood%3F\" aria-hidden=\"true\">#</a> What’s happening under the hood?</h2>\n<p>The component:</p>\n<ol>\n<li>Associates with an input via the <code>for</code> attribute (just like a <code>label</code> element)</li>\n<li>Finds all elements with <code>data-pattern</code> attributes</li>\n<li>Tests the input value against each pattern when the configured trigger fires</li>\n<li>Adds <code>validation-matched</code> or <code>validation-unmatched</code> classes and visual indicators accordingly</li>\n<li>Inserts localized, visually hidden state text once the field has a value</li>\n<li>Updates a single polite live region while users type</li>\n<li>Uses <code>setCustomValidity()</code> to integrate with native form validation</li>\n<li>Prevents form submission until all rules match</li>\n</ol>\n<p>The cascade animation, controlled by <code>each-delay</code>, creates a pleasant visual effect as rules are checked sequentially. It is a small touch, but a nice one.</p>\n<h2 id=\"whose-rules%3F-your-rules.\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#whose-rules%3F-your-rules.\" aria-hidden=\"true\">#</a> Whose rules? Your rules.</h2>\n<p>Define rules using regular expression patterns in the <code>data-pattern</code> attribute:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>form-validation-list</span> <span class=\"token attr-name\">for</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>password<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>ul</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token comment\">&lt;!-- Length requirements --></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>li</span> <span class=\"token attr-name\">data-pattern</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>.{8,}<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>At least 8 characters<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>li</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>li</span> <span class=\"token attr-name\">data-pattern</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>.{8,32}<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>Between 8 and 32 characters<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>li</span><span class=\"token punctuation\">></span></span>\n\n    <span class=\"token comment\">&lt;!-- Character type requirements --></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>li</span> <span class=\"token attr-name\">data-pattern</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>[A-Z]+<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>At least one uppercase letter<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>li</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>li</span> <span class=\"token attr-name\">data-pattern</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>[a-z]+<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>At least one lowercase letter<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>li</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>li</span> <span class=\"token attr-name\">data-pattern</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>[\\d]+<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>At least one number<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>li</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>li</span> <span class=\"token attr-name\">data-pattern</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>[!@#$%^&amp;*]+<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>At least one special character<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>li</span><span class=\"token punctuation\">></span></span>\n\n    <span class=\"token comment\">&lt;!-- Format requirements --></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>li</span> <span class=\"token attr-name\">data-pattern</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>.+@.+\\..+<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>Valid email format<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>li</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>li</span> <span class=\"token attr-name\">data-pattern</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>^[a-zA-Z0-9]+$<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>Only letters and numbers<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>li</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>ul</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>form-validation-list</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>Each pattern is a standard JavaScript regular expression. The component tests the <code>input</code> value against all patterns on the configured trigger, using throttled <code>input</code> events by default.</p>\n<h2 id=\"input-event-too-noisy%3F-no-worries.\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#input-event-too-noisy%3F-no-worries.\" aria-hidden=\"true\">#</a> Input event too noisy? No worries.</h2>\n<p>By default, validation runs on the <code>input</code> event with a 250ms throttle. If you want immediate feedback while typing, set <code>input-throttle=&quot;0&quot;</code>. If you’d rather wait until the field loses focus, switch the <code>trigger-event</code> to <code>&quot;blur&quot;</code>:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>form-validation-list</span> <span class=\"token attr-name\">for</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>email<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">trigger-event</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>blur<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>ul</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>li</span> <span class=\"token attr-name\">data-pattern</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>.+@.+<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>Contains @ symbol<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>li</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>li</span> <span class=\"token attr-name\">data-pattern</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>.+@.+\\..+<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>Valid email format<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>li</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>ul</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>form-validation-list</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>With this attribute in place, validation runs immediately when the field loses focus. In this mode, <code>input-throttle</code> is ignored and the component keeps the full criteria list available to assistive technology while someone types.</p>\n<h2 id=\"wanna-adjust-the-cascade-delay%3F-go-for-it.\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#wanna-adjust-the-cascade-delay%3F-go-for-it.\" aria-hidden=\"true\">#</a> Wanna adjust the cascade delay? Go for it.</h2>\n<p>Use the <code>each-delay</code> attribute to control the delay between checking each rule. The default speed is 150ms, but you can tune it to any number of milliseconds:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>form-validation-list</span> <span class=\"token attr-name\">for</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>password<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">each-delay</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>100<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n  <span class=\"token comment\">&lt;!-- rules --></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>form-validation-list</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>Set it to “0” to remove the cascade effect entirely and check all rules simultaneously.</p>\n<h2 id=\"need-full-design-control%3F-you-got-it.\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#need-full-design-control%3F-you-got-it.\" aria-hidden=\"true\">#</a> Need full design control? You got it.</h2>\n<p>If you want full design control over the component, you can absolutely have it. The whole component operates in light DOM, so your styles will pierce through. And you can customize <code>class</code> names for integration with CSS frameworks using a set of attributes on the <code>form-validation-list</code> element. The <code>field-valid-class</code> and <code>field-invalid-class</code> attributes control the class names applied to the <code>input</code> field itself, while the <code>rule-matched-class</code> and <code>rule-unmatched-class</code> attributes control the <code>class</code> names applied to each rule item.</p>\n<p>Here’s a complete example:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>style</span><span class=\"token punctuation\">></span></span><span class=\"token style\"><span class=\"token language-css\">\n  <span class=\"token selector\">.is-valid</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token property\">border-color</span><span class=\"token punctuation\">:</span> green<span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n  <span class=\"token selector\">.is-invalid</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token property\">border-color</span><span class=\"token punctuation\">:</span> red<span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n  <span class=\"token selector\">.rule-pass</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token property\">color</span><span class=\"token punctuation\">:</span> green<span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n  <span class=\"token selector\">.rule-fail</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token property\">color</span><span class=\"token punctuation\">:</span> red<span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n</span></span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>style</span><span class=\"token punctuation\">></span></span>\n\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>form-validation-list</span>\n  <span class=\"token attr-name\">for</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>username<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">field-valid-class</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>is-valid<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">field-invalid-class</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>is-invalid<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">rule-matched-class</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>rule-pass<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">rule-unmatched-class</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>rule-fail<span class=\"token punctuation\">\"</span></span>\n<span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>ul</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>li</span> <span class=\"token attr-name\">data-pattern</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>.{5,}<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>At least 5 characters<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>li</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>li</span> <span class=\"token attr-name\">data-pattern</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>[!@#]+<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>Special char (!@#)<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>li</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>ul</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>form-validation-list</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>This approach lets you use <code>class</code> names that match your existing CSS architecture, rather than making one small component dictate terms to the rest of your styles.</p>\n<p>You can also override the per-instance icon glyphs with the <code>rule-matched-icon</code> and <code>rule-unmatched-icon</code> attributes, or control the shared visual styling using CSS custom properties:</p>\n<ul>\n<li><code>--rule-matched-icon</code> - Content for matched state (default: “✓”)</li>\n<li><code>--rule-unmatched-icon</code> - Content for unmatched state (default: “✗”)</li>\n<li><code>--rule-icon-size</code> - Size of icons (default: 1em)</li>\n<li><code>--rule-matched-color</code> - Color for matched rules (default: green)</li>\n<li><code>--rule-unmatched-color</code> - Color for unmatched rules (default: red)</li>\n</ul>\n<p>The older <code>--validation-*</code> custom property names are still supported as legacy aliases.</p>\n<p>Here’s an example of that:</p>\n<pre class=\"language-css\" tabindex=\"0\"><code class=\"language-css\"><span class=\"token selector\">form-validation-list</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token property\">--rule-matched-icon</span><span class=\"token punctuation\">:</span> <span class=\"token string\">\"✅\"</span><span class=\"token punctuation\">;</span>\n  <span class=\"token property\">--rule-unmatched-icon</span><span class=\"token punctuation\">:</span> <span class=\"token string\">\"❌\"</span><span class=\"token punctuation\">;</span>\n  <span class=\"token property\">--rule-icon-size</span><span class=\"token punctuation\">:</span> 1.2em<span class=\"token punctuation\">;</span>\n  <span class=\"token property\">--rule-matched-color</span><span class=\"token punctuation\">:</span> #28a745<span class=\"token punctuation\">;</span>\n  <span class=\"token property\">--rule-unmatched-color</span><span class=\"token punctuation\">:</span> #dc3545<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<h2 id=\"typescript-or-framework-project%3F-you%E2%80%99re-covered.\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#typescript-or-framework-project%3F-you%E2%80%99re-covered.\" aria-hidden=\"true\">#</a> TypeScript or framework project? You’re covered.</h2>\n<p>The package now ships with bundled type definitions and reflects its core properties and attributes in both directions. That makes it a much better fit for TypeScript, JSX, SSR, and declarative framework setups where properties may be assigned before the custom element upgrades.</p>\n<h2 id=\"bit-of-a-control-freak%3F-there%E2%80%99s-an-api.\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#bit-of-a-control-freak%3F-there%E2%80%99s-an-api.\" aria-hidden=\"true\">#</a> Bit of a control freak? There’s an API.</h2>\n<p>If you really want to get into the weeds, you can also listen for validation changes in your JavaScript code:</p>\n<pre class=\"language-javascript\" tabindex=\"0\"><code class=\"language-javascript\"><span class=\"token keyword\">const</span> validationList <span class=\"token operator\">=</span> document<span class=\"token punctuation\">.</span><span class=\"token function\">querySelector</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"form-validation-list\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\nvalidationList<span class=\"token punctuation\">.</span><span class=\"token function\">addEventListener</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"form-validation-list:validated\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">event</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> isValid<span class=\"token punctuation\">,</span> matchedRules<span class=\"token punctuation\">,</span> totalRules<span class=\"token punctuation\">,</span> field <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> event<span class=\"token punctuation\">.</span>detail<span class=\"token punctuation\">;</span>\n  console<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">Matched </span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>matchedRules<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token string\"> of </span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>totalRules<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token string\"> rules</span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  console<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">Field is </span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>isValid <span class=\"token operator\">?</span> <span class=\"token string\">\"valid\"</span> <span class=\"token operator\">:</span> <span class=\"token string\">\"invalid\"</span><span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre>\n<p>The event fires after validation completes and gives you the current state. Nice and tidy.</p>\n<p>You can also manually trigger validation and check the element’s current state at any time:</p>\n<pre class=\"language-javascript\" tabindex=\"0\"><code class=\"language-javascript\"><span class=\"token keyword\">const</span> validationList <span class=\"token operator\">=</span> document<span class=\"token punctuation\">.</span><span class=\"token function\">querySelector</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"form-validation-list\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token comment\">// Trigger validation</span>\n<span class=\"token keyword\">const</span> isValid <span class=\"token operator\">=</span> validationList<span class=\"token punctuation\">.</span><span class=\"token function\">validate</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\nconsole<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Is valid:\"</span><span class=\"token punctuation\">,</span> isValid<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token comment\">// Check current state</span>\nconsole<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Current state:\"</span><span class=\"token punctuation\">,</span> validationList<span class=\"token punctuation\">.</span>isValid<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre>\n<h2 id=\"global-site%3F-relaje!\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#global-site%3F-relaje!\" aria-hidden=\"true\">#</a> Global site? <i lang=\"es\">Relaje!</i></h2>\n<p>If you need the component to work in different languages, that’s totally doable. You can customize three separate pieces of copy: the browser validation message (<code>validation-message</code>), the live summary announced while typing (<code>announcement</code>), and the per-rule hidden status text (<code>rule-matched-alt</code> and <code>rule-unmatched-alt</code>). All of the message templates support the <code>{matched}</code> and <code>{total}</code> placeholders:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token comment\">&lt;!-- Spanish --></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>form-validation-list</span>\n  <span class=\"token attr-name\">for</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>contrasena<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">announcement</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>{matched} de {total} criterios cumplidos<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">rule-matched-alt</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>Criterio cumplido<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">rule-unmatched-alt</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>Criterio pendiente<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">validation-message</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>Por favor, cumple todos los requisitos ({matched} de {total})<span class=\"token punctuation\">\"</span></span>\n<span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>ul</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>li</span> <span class=\"token attr-name\">data-pattern</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>[A-Z]+<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>Al menos una letra mayúscula<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>li</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>li</span> <span class=\"token attr-name\">data-pattern</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>[a-z]+<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>Al menos una letra minúscula<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>li</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>li</span> <span class=\"token attr-name\">data-pattern</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>[\\d]+<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>Al menos un número<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>li</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>ul</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>form-validation-list</span><span class=\"token punctuation\">></span></span>\n\n<span class=\"token comment\">&lt;!-- French --></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>form-validation-list</span>\n  <span class=\"token attr-name\">for</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>mot-de-passe<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">announcement</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>{matched} critères satisfaits sur {total}<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">rule-matched-alt</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>Critère satisfait<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">rule-unmatched-alt</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>Critère non satisfait<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">validation-message</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>Veuillez satisfaire à toutes les exigences ({matched} sur {total})<span class=\"token punctuation\">\"</span></span>\n<span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>ul</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>li</span> <span class=\"token attr-name\">data-pattern</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>[A-Z]+<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>Au moins une lettre majuscule<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>li</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>li</span> <span class=\"token attr-name\">data-pattern</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>[a-z]+<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>Au moins une lettre minuscule<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>li</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>li</span> <span class=\"token attr-name\">data-pattern</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>[\\d]+<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>Au moins un chiffre<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>li</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>ul</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>form-validation-list</span><span class=\"token punctuation\">></span></span></code></pre>\n<h2 id=\"is-it-a-progressive-enhancement%3F-heck-yeah!\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#is-it-a-progressive-enhancement%3F-heck-yeah!\" aria-hidden=\"true\">#</a> Is it a progressive enhancement? Heck yeah!</h2>\n<p>The component uses light DOM, so if JavaScript fails, users still see the validation requirements as a standard list. They can read what is expected even without the visual feedback. Your server-side validation still does the important enforcement work regardless… right? <em>Right?</em></p>\n<h2 id=\"is-it-screen-reader-accessible%3F-yep.\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#is-it-screen-reader-accessible%3F-yep.\" aria-hidden=\"true\">#</a> Is it screen reader accessible? Yep.</h2>\n<p>The component is built with accessibility in mind:</p>\n<ul>\n<li><strong>Proper description support</strong>: The validation list is automatically associated with the <code>input</code> via <code>aria-describedby</code>, and if the field already has <code>aria-describedby</code>, the original value is preserved.</li>\n<li><strong>A concise announcement model</strong>: With the default <code>trigger-event=&quot;input&quot;</code>, the component temporarily suspends the full criteria list from <code>aria-describedby</code> while someone types and uses a single polite live region to announce progress instead.</li>\n<li><strong>State restoration on blur</strong>: When focus leaves the field, any pending validation timeouts are cleared and the full criteria list is restored so returning to the field announces the final criteria state.</li>\n<li><strong>Localized rule state</strong>: Once the field has a value, each rule gets visually hidden localized state text in the DOM, which is more robust than relying on CSS-generated content alone.</li>\n</ul>\n<p>If you have suggestions for other ways to improve the accessibility of this component, please <a href=\"https://github.com/aarongustafson/form-validation-list/issues\">open an issue on GitHub</a>.</p>\n<h2 id=\"does-it-integrate-with-the-browser%E2%80%99s-validation-engine%3F-naturally.\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#does-it-integrate-with-the-browser%E2%80%99s-validation-engine%3F-naturally.\" aria-hidden=\"true\">#</a> Does it integrate with the browser’s validation engine? Naturally.</h2>\n<p>The component uses <code>setCustomValidity()</code> to participate in native form validation:</p>\n<ul>\n<li>When all rules match, custom validity is cleared</li>\n<li>When rules don’t match, a custom validity message is set</li>\n<li>Form submission is prevented until all rules pass</li>\n<li>Works with <code>:valid</code> and <code>:invalid</code> CSS pseudo-classes</li>\n<li>Compatible with the Constraint Validation API</li>\n</ul>\n<pre class=\"language-javascript\" tabindex=\"0\"><code class=\"language-javascript\"><span class=\"token keyword\">const</span> form <span class=\"token operator\">=</span> document<span class=\"token punctuation\">.</span><span class=\"token function\">querySelector</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"form\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">const</span> field <span class=\"token operator\">=</span> document<span class=\"token punctuation\">.</span><span class=\"token function\">getElementById</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"username\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\nform<span class=\"token punctuation\">.</span><span class=\"token function\">addEventListener</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"submit\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">e</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">!</span>form<span class=\"token punctuation\">.</span><span class=\"token function\">checkValidity</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    e<span class=\"token punctuation\">.</span><span class=\"token function\">preventDefault</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    console<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Validation failed:\"</span><span class=\"token punctuation\">,</span> field<span class=\"token punctuation\">.</span>validationMessage<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre>\n<h2 id=\"here%E2%80%99s-a-real-world-example\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#here%E2%80%99s-a-real-world-example\" aria-hidden=\"true\">#</a> Here’s a real-world example</h2>\n<p>Here’s a complete password validation setup:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>form</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>label</span> <span class=\"token attr-name\">for</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>password<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>Password:<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>label</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>input</span> <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>password<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">id</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>password<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">name</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>password<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">required</span> <span class=\"token punctuation\">/></span></span>\n\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>form-validation-list</span> <span class=\"token attr-name\">for</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>password<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>ul</span><span class=\"token punctuation\">></span></span>\n      <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>li</span> <span class=\"token attr-name\">data-pattern</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>.{8,}<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>At least 8 characters<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>li</span><span class=\"token punctuation\">></span></span>\n      <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>li</span> <span class=\"token attr-name\">data-pattern</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>[A-Z]+<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>At least one uppercase letter<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>li</span><span class=\"token punctuation\">></span></span>\n      <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>li</span> <span class=\"token attr-name\">data-pattern</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>[a-z]+<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>At least one lowercase letter<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>li</span><span class=\"token punctuation\">></span></span>\n      <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>li</span> <span class=\"token attr-name\">data-pattern</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>[\\d]+<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>At least one number<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>li</span><span class=\"token punctuation\">></span></span>\n      <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>li</span> <span class=\"token attr-name\">data-pattern</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>[!@#$%^&amp;*]+<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n        At least one special character (!@#$%^&amp;*)\n      <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>li</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>ul</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>form-validation-list</span><span class=\"token punctuation\">></span></span>\n\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>button</span> <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>submit<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>Submit<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>button</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>form</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>Users see exactly which requirements they have met and which they still need to satisfy. That tends to be a lot kinder than springing the whole list on them after submit.</p>\n<h2 id=\"play-with-it\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#play-with-it\" aria-hidden=\"true\">#</a> Play with it</h2>\n<p>Check out <a href=\"https://aarongustafson.github.io/form-validation-list/demo/\">the demo</a> with various examples:</p>\n<figure id=\"fig-2025-12-06-09\" class=\"media-container\">\n<fullscreen-control class=\"talk__slides__embed video-embed__video\">\n<iframe src=\"https://aarongustafson.github.io/form-validation-list/demo/\" class=\"talk__slides__embed video-embed__video\" frameborder=\"0\"></iframe>\n</fullscreen-control>\n</figure>\n<h2 id=\"grab-it\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#grab-it\" aria-hidden=\"true\">#</a> Grab it</h2>\n<p>View the project on <a href=\"https://github.com/aarongustafson/form-validation-list\">GitHub</a>.</p>\n<p>Install via <code>npm</code>:</p>\n<pre class=\"language-bash\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"token function\">npm</span> <span class=\"token function\">install</span> @aarongustafson/form-validation-list</code></pre>\n<p>For most projects, import the guarded auto-definition helper:</p>\n<pre class=\"language-javascript\" tabindex=\"0\"><code class=\"language-javascript\"><span class=\"token keyword\">import</span> <span class=\"token string\">\"@aarongustafson/form-validation-list/define.js\"</span><span class=\"token punctuation\">;</span></code></pre>\n<p>If you want to control the tag name yourself, import <code>FormValidationListElement</code> and register it manually.</p>\n<p>Happy validating!</p>\n","social_text":"New #WebComponent: Show users which validation requirements they’ve met — as they type.","url":"https://www.aaron-gustafson.com/notebook/visual-validation-feedback-for-form-fields/","tags":["web components","progressive enhancement","forms","HTML","JavaScript","web forms","user experience"],"date_published":"2026-04-22T20:17:47Z"},{"id":"https://www.aaron-gustafson.com/notebook/never-lose-form-progress-again/","title":"✍🏻 Never Lose Form Progress Again","summary":"Browsers crash. Tabs close. Life happens. Here’s a web component that saves form progress so your users don’t have to start over from scratch.","content_html":"<p>Few things are more annoying than losing your progress halfway through a form. Maybe the browser crashes. Maybe the tab gets closed. Maybe your kid yells from the other room and you come back three hours later wondering why you ever thought now was a good time to fill out a mortgage application. Whatever the cause, <code>form-saver</code> makes those interruptions a lot less obnoxious. Which is nice, because forms are usually annoying enough on their own.</p>\n<p>At its core, <code>form-saver</code> is a small web component that wraps a form, keeps an eye on it, stores values in <code>localStorage</code>, and restores them when the page loads again. Better yet, it clears out saved data after a successful submission so you’re not accidentally resurrecting stale information the next time someone stops by. Nobody wants yesterday’s half-finished support request shambling back to life.</p>\n<h2 id=\"basic-usage\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#basic-usage\" aria-hidden=\"true\">#</a> Basic usage</h2>\n<p>All you need to do is wrap your form in the component:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>form-saver</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>form</span> <span class=\"token attr-name\">action</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>/contact<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">method</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>post<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>label</span><span class=\"token punctuation\">></span></span>\n      Name\n      <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>input</span> <span class=\"token attr-name\">name</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>name<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">autocomplete</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>name<span class=\"token punctuation\">\"</span></span> <span class=\"token punctuation\">/></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>label</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>label</span><span class=\"token punctuation\">></span></span>\n      Email\n      <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>input</span> <span class=\"token attr-name\">name</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>email<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>email<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">autocomplete</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>email<span class=\"token punctuation\">\"</span></span> <span class=\"token punctuation\">/></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>label</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>label</span><span class=\"token punctuation\">></span></span>\n      Message\n      <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>textarea</span> <span class=\"token attr-name\">name</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>message<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>textarea</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>label</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>button</span> <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>submit<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>Send<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>button</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>form</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>form-saver</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>That’s it. The component targets the first descendant <code>form</code>, saves values as users type or make changes, and restores them when they come back. No extra plumbing. Just a form with a slightly better memory than most of us have before coffee — depending on the day, that may not be a terribly high bar, but still.</p>\n<p>This is especially handy for forms that are a little more involved than a simple email signup. Job applications, checkout flows, support requests, and multi-question onboarding forms all benefit from a little resilience. So do the people filling them out, who generally have better things to do than retype the same answers because a tab got squirrelly.</p>\n<h2 id=\"what-actually-gets-saved%3F\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#what-actually-gets-saved%3F\" aria-hidden=\"true\">#</a> What actually gets saved?</h2>\n<p><code>form-saver</code> supports the controls most of us reach for every day:</p>\n<ul>\n<li>Text-style <code>input</code> fields</li>\n<li><code>textarea</code> elements,</li>\n<li><code>select</code> elements (including multi-selects), and</li>\n<li><code>checkbox</code> and <code>radio</code> controls.</li>\n</ul>\n<p>File inputs are intentionally excluded.</p>\n<p>Because the component works in light DOM, your form remains your form. Your labels, validation, layout, and CSS continue to work exactly as they did before. <code>form-saver</code> just adds a bit of memory and, ideally, cuts down on a few muttered curses.</p>\n<h2 id=\"want-to-keep-a-few-fields-after-submit%3F\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#want-to-keep-a-few-fields-after-submit%3F\" aria-hidden=\"true\">#</a> Want to keep a few fields after submit?</h2>\n<p>In many cases, clearing everything after a successful submission is the right call. Sometimes, though, it makes sense to keep a few details around. Maybe you want to preserve a visitor’s name and email address on a contact form while clearing the message body. That way they do not have to keep retyping the boring bits. Nobody wakes up excited to enter their email address for the fourth time.</p>\n<p>That is what the <code>retain</code> attribute is for:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>form-saver</span> <span class=\"token attr-name\">retain</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>name email<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>form</span> <span class=\"token attr-name\">action</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>/contact<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">method</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>post<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n    …\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>form</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>form-saver</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>After a successful submission, <code>name</code> and <code>email</code> stick around, but <code>message</code> gets cleared. Simple, sensible, and less likely to leave someone staring at your form like it just betrayed them personally.</p>\n<h2 id=\"better-yet%2C-let-users-decide\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#better-yet%2C-let-users-decide\" aria-hidden=\"true\">#</a> Better yet, let users decide</h2>\n<p>Persisting form data can be incredibly helpful, but there is a human side to this too. Just because we <em>can</em> keep someone’s information around does not necessarily mean we <em>should</em> do it without asking. That is where <code>retain-choice</code> comes in. It lets you be useful without getting presumptuous.</p>\n<p>Add it alongside <code>retain</code> and <code>form-saver</code> will inject an opt-in checkbox for the user. Nice and easy:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>form-saver</span>\n  <span class=\"token attr-name\">retain</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>name email<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">retain-choice</span>\n  <span class=\"token attr-name\">retain-choice-label</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>Store my contact information for later<span class=\"token punctuation\">\"</span></span>\n<span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>form</span> <span class=\"token attr-name\">action</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>/contact<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">method</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>post<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n    …\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>form</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>form-saver</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>By default, that checkbox is inserted just before the first submit control. If the user leaves it unchecked, the retained fields are cleared along with everything else after submit. If they opt in, those selected fields remain. Their call, as it should be. Gotta love a little informed consent.</p>\n<p>Need to place that control somewhere more appropriate in your layout? Use <code>retain-choice-container</code> to point to a CSS selector:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>form-saver</span>\n  <span class=\"token attr-name\">retain</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>name email<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">retain-choice</span>\n  <span class=\"token attr-name\">retain-choice-label</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>Remember my details next time<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">retain-choice-container</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>.form-footer<span class=\"token punctuation\">\"</span></span>\n<span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>form</span> <span class=\"token attr-name\">action</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>/contact<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">method</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>post<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n    …\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>div</span> <span class=\"token attr-name\">class</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>form-footer<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n      <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>button</span> <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>submit<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>Send<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>button</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>div</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>form</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>form-saver</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>That gives you a lot more control over the experience without making you build the retention UI yourself.</p>\n<h2 id=\"need-a-custom-storage-key%3F\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#need-a-custom-storage-key%3F\" aria-hidden=\"true\">#</a> Need a custom storage key?</h2>\n<p>By default, <code>form-saver</code> derives its storage key from the wrapped form’s method and action, which is usually exactly what you want. It keeps different forms from stepping on one another and keeps the setup nice and boring. Boring is good.</p>\n<p>If you need something more explicit, you can provide your own <code>storage-key</code>:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>form-saver</span> <span class=\"token attr-name\">storage-key</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>checkout:shipping-address<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>form</span> <span class=\"token attr-name\">action</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>/checkout/shipping<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">method</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>post<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>label</span><span class=\"token punctuation\">></span></span>\n      Street Address\n      <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>input</span> <span class=\"token attr-name\">name</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>street-address<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">autocomplete</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>street-address<span class=\"token punctuation\">\"</span></span> <span class=\"token punctuation\">/></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>label</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>label</span><span class=\"token punctuation\">></span></span>\n      Postal Code\n      <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>input</span> <span class=\"token attr-name\">name</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>postal-code<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">autocomplete</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>postal-code<span class=\"token punctuation\">\"</span></span> <span class=\"token punctuation\">/></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>label</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>button</span> <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>submit<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>Continue<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>button</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>form</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>form-saver</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>This is useful when a form’s URL is not stable or when you want multiple views to intentionally share the same saved state. Sometimes explicit is just easier. Sometimes it is the only way to stay sane.</p>\n<h2 id=\"want-to-drive-it-yourself%3F\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#want-to-drive-it-yourself%3F\" aria-hidden=\"true\">#</a> Want to drive it yourself?</h2>\n<p>If you need more direct control, the component exposes a few methods:</p>\n<pre class=\"language-javascript\" tabindex=\"0\"><code class=\"language-javascript\"><span class=\"token keyword\">const</span> saver <span class=\"token operator\">=</span> document<span class=\"token punctuation\">.</span><span class=\"token function\">querySelector</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"form-saver\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token comment\">// Persist the current state</span>\nsaver<span class=\"token punctuation\">.</span><span class=\"token function\">saveFormState</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token comment\">// Restore previously saved values</span>\nsaver<span class=\"token punctuation\">.</span><span class=\"token function\">restoreFormState</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token comment\">// Clear out anything stored for this form</span>\nsaver<span class=\"token punctuation\">.</span><span class=\"token function\">clearSavedData</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre>\n<p>That can be useful when you want to pair it with your own UI, analytics, or some custom workflow around save and restore. Or when you just like being the one driving and do not fully trust anything labeled “automatic.”</p>\n<h2 id=\"progressive-enhancement%2C-as-usual\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#progressive-enhancement%2C-as-usual\" aria-hidden=\"true\">#</a> Progressive enhancement, as usual</h2>\n<p>This component follows a pattern I am always going to favor: start with a perfectly ordinary form, then layer on the enhancement. If JavaScript fails, the form still works. Users can still fill it out and submit it. They just will not get the recovery behavior. Annoying, perhaps, but not catastrophic. And that is very much the point.</p>\n<p>That’s a pretty good trade-off.</p>\n<p>And because saved values are only cleared after a successful submit flow, you do not lose everything just because client-side validation blocked submission or some other script got clever at exactly the wrong moment. That matters. A lot of “smart” form experiences are only smart right up until they are not.</p>\n<h2 id=\"demo\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#demo\" aria-hidden=\"true\">#</a> Demo</h2>\n<p>If you want to kick the tires, I put together <a href=\"https://aarongustafson.github.io/form-saver/demo/\">a live demo</a> with examples of the retention options as well:</p>\n<figure id=\"fig-2026-04-20-01\" class=\"media-container\">\n<fullscreen-control class=\"talk__slides__embed video-embed__video\">\n<iframe src=\"https://aarongustafson.github.io/form-saver/demo/\" class=\"talk__slides__embed video-embed__video\" frameborder=\"0\"></iframe>\n</fullscreen-control>\n</figure>\n<h2 id=\"grab-it\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#grab-it\" aria-hidden=\"true\">#</a> Grab it</h2>\n<p>The project is available on <a href=\"https://github.com/aarongustafson/form-saver\">GitHub</a>, and you can install it from npm:</p>\n<pre class=\"language-bash\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"token function\">npm</span> <span class=\"token function\">install</span> @aarongustafson/form-saver</code></pre>\n<p>If you want the easiest path, just import it and let the component register itself:</p>\n<pre class=\"language-javascript\" tabindex=\"0\"><code class=\"language-javascript\"><span class=\"token keyword\">import</span> <span class=\"token string\">\"@aarongustafson/form-saver\"</span><span class=\"token punctuation\">;</span></code></pre>\n<p>If you would rather define it yourself, you can import the class directly:</p>\n<pre class=\"language-javascript\" tabindex=\"0\"><code class=\"language-javascript\"><span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> FormSaverElement <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"@aarongustafson/form-saver/form-saver.js\"</span><span class=\"token punctuation\">;</span>\n\ncustomElements<span class=\"token punctuation\">.</span><span class=\"token function\">define</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"form-saver\"</span><span class=\"token punctuation\">,</span> FormSaverElement<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre>\n<p>Either way, you wind up with a more forgiving form experience and a little less needless frustration for the people filling it out. Which, in my book, is a pretty solid deal. The bar for delight in forms is often just “don’t make me do that again,” and honestly, I’ll take it.</p>\n","social_text":"Browsers crash. Tabs close. Life happens. Here’s a web component that saves form progress so your users don’t have to start over from scratch.","url":"https://www.aaron-gustafson.com/notebook/never-lose-form-progress-again/","tags":["web components","forms","HTML","JavaScript","progressive enhancement","web forms","user experience"],"date_published":"2026-04-20T23:59:08Z"},{"id":"https://www.aaron-gustafson.com/notebook/links/people-are-not-static-we-are-dynamic-in-order-to-meet-our-needs-at-any-point-in-our-lives-or-day-the-uis-we-create-must-be-able-to-adapt-to-us-not-the-other-way-around-/","title":"🔗 Different contexts, different tools, same person","summary":"People are not static, we are dynamic. In order to meet our needs at any point in our lives or day, the UIs we create must be able to adapt to us — not the other way around.","content_html":"<p>People are not static, we are dynamic. In order to meet our needs at any point in our lives or day, the UIs we create must be able to adapt to us — not the other way around.</p>\n<blockquote>\n<p>I know someone that uses her screen reader on her mobile phone, but when she’s on her desktop computer, she uses a mangifier.</p>\n<p>Different contexts, different tools, same person.</p>\n<p>I know someone that uses voice controls on his computer. He uses direct commands like “Click Contact Us” when he’s near the start of his day, and commands like “Click link, twelve” when he’s near the end of his day with lower energy and less clear speech and a dry mouth.</p>\n<p>Different energy/capacity, same tools, same person.</p>\n<p>I know someone that uses a switch on his computer. He also uses the onscreen keyboard on his computer. The one that he chooses reflects the task he’s trying to accomplish and how he can minimize switching between the tools.</p>\n<p>Different task, same context, same tools, same person.</p>\n<p>Disability is not black and white… it’s every shade of every colour.</p>\n</blockquote>\n","social_text":"People are not static, we are dynamic. In order to meet our needs at any point in our lives or day, the UIs we create must be able to adapt to us — not the other way around.","url":"https://www.aaron-gustafson.com/notebook/links/people-are-not-static-we-are-dynamic-in-order-to-meet-our-needs-at-any-point-in-our-lives-or-day-the-uis-we-create-must-be-able-to-adapt-to-us-not-the-other-way-around-/","external_url":"https://www.linkedin.com/posts/derekfeatherstone_accessibility-disability-activity-7434648295420870656-mH3o","tags":["accessibility","progressive enhancement","inclusive design"],"image":"https://static.licdn.com/aero-v1/sc/h/c45fy346jw096z9pbphyyhdz7","date_published":"2026-03-05T18:38:46Z"},{"id":"https://www.aaron-gustafson.com/notebook/links/why-we-teach-our-students-progressive-enhancement/","title":"🔗 Why we teach our students progressive enhancement","content_html":"<p>Excellent post and primer on progressive enhancement. I particularly love this framing:</p>\n<blockquote>\n<p>progressive enhancement is not about preventing failure, it’s about defining what must always work.</p>\n</blockquote>\n<p>I also want one of those ”Wat als de JavaScript uit staat?“ stickers.</p>\n","social_text":"“[P]rogressive enhancement is not about preventing failure, it’s about defining what must always work.”","url":"https://www.aaron-gustafson.com/notebook/links/why-we-teach-our-students-progressive-enhancement/","external_url":"https://cydstumpel.nl/why-we-teach-our-students-progressive-enhancement/","tags":["progressive enhancement","web development","web design"],"image":"https://cydstumpel.nl/wp-content/uploads/og-images/og-work-1900.jpg?v=1772487917","date_published":"2026-02-06T19:10:02Z"},{"id":"https://www.aaron-gustafson.com/notebook/repeatable-form-fields-made-simple/","title":"✍🏻 Repeatable Form Fields Made Simple","summary":"Need to let users add multiple email addresses, phone numbers, or other field groups? The <code>form-repeatable</code> web component handles the duplication, renumbering, and form submission automatically.","content_html":"<p>Sometimes you need users to provide multiple instances of the same information—multiple email addresses, phone numbers, team members, or emergency contacts. The <code>form-repeatable</code> web component makes this straightforward, handling field duplication, automatic renumbering, and seamless form submission via the ElementInternals API.</p>\n<p>All you need to do is provide a single field group and the component handles the rest:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>form</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>form-repeatable</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>div</span><span class=\"token punctuation\">></span></span>\n      <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>label</span> <span class=\"token attr-name\">for</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>stop-1<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>Stop 1<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>label</span><span class=\"token punctuation\">></span></span>\n      <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>input</span> <span class=\"token attr-name\">id</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>stop-1<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>text<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">name</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>stops[]<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>div</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>form-repeatable</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>form</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>The <code>form-repeatable</code> component treats its first child as a template and injects a <code>button</code> that allows users to repeat the field. When users click “Add Another” (the default “add” button text), the following happens:</p>\n<ol>\n<li>The template is cloned,</li>\n<li>Any numbers are auto-incremented (“Stop 1” → “Stop 2”, <code>stop-1</code> → <code>stop-2</code>),</li>\n<li>A new group is added to the component,</li>\n<li>A “remove” button is added when there’s more than the minimum number of groups (1 by default), and</li>\n<li>Form values update automatically via <code>ElementInternals</code>.</li>\n</ol>\n<p>That last piece is crucial. The plugin is a fully-participating member in the parent form:</p>\n<ul>\n<li>All inputs are collected and submitted automatically</li>\n<li>Values are added to <code>FormData</code></li>\n<li>In-built form reset is respected</li>\n<li>A form’s disabled state is respected</li>\n</ul>\n<p>No special handling required — it works like any native form control.</p>\n<h2 id=\"need-customized-buttons%3F-you-bet!\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#need-customized-buttons%3F-you-bet!\" aria-hidden=\"true\">#</a> Need customized buttons? You bet!</h2>\n<p>If you don’t like the default text or your site is in another language — no biggie! You can define your own button labels using the <code>add-label</code> and <code>remove-label</code> attributes:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>form-repeatable</span> <span class=\"token attr-name\">add-label</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>Add Another Item<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">remove-label</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>Delete<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>div</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>label</span> <span class=\"token attr-name\">for</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>item-1<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>Item 1<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>label</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>input</span> <span class=\"token attr-name\">id</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>item-1<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>text<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">name</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>items[]<span class=\"token punctuation\">\"</span></span> <span class=\"token punctuation\">/></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>div</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>form-repeatable</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>With that simple change, the add button reads “Add Another Item” and each remove button reads “Delete”. To improve the experience for screen reader users, the <code>remove-label</code> value is combined with the associated label/legend to create accessible names like “Delete Item 1” which is far more helpful.</p>\n<h2 id=\"already-have-values-to-show%3F-no-problem.\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#already-have-values-to-show%3F-no-problem.\" aria-hidden=\"true\">#</a> Already have values to show? No problem.</h2>\n<p>If your form needs to start with multiple groups already filled in, just provide them as child elements:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>form</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>form-repeatable</span> <span class=\"token attr-name\">min</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>2<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>div</span><span class=\"token punctuation\">></span></span>\n      <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>label</span> <span class=\"token attr-name\">for</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>phone-1<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>Phone 1<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>label</span><span class=\"token punctuation\">></span></span>\n      <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>input</span> <span class=\"token attr-name\">id</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>phone-1<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>tel<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">name</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>phones[]<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">value</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>555-0100<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>div</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>div</span><span class=\"token punctuation\">></span></span>\n      <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>label</span> <span class=\"token attr-name\">for</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>phone-2<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>Phone 2<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>label</span><span class=\"token punctuation\">></span></span>\n      <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>input</span> <span class=\"token attr-name\">id</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>phone-2<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>tel<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">name</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>phones[]<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">value</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>555-0101<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>div</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>div</span><span class=\"token punctuation\">></span></span>\n      <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>label</span> <span class=\"token attr-name\">for</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>phone-3<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>Phone 3<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>label</span><span class=\"token punctuation\">></span></span>\n      <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>input</span> <span class=\"token attr-name\">id</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>phone-3<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>tel<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">name</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>phones[]<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>div</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>form-repeatable</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>form</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>All the children will become groups managed by the component and their existing values are preserved. Perfect progressive enhancement!</p>\n<h2 id=\"need-to-do-something-a-little-more-complex%3F-i-got-you.\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#need-to-do-something-a-little-more-complex%3F-i-got-you.\" aria-hidden=\"true\">#</a> Need to do something a little more complex? I got you.</h2>\n<p>You’re not limited to repeating a single field. Each group can contain multiple, related fields. Here’s an example with a <code>fieldset</code> for guest information:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>form-repeatable</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>fieldset</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>legend</span><span class=\"token punctuation\">></span></span>Guest 1<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>legend</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>label</span> <span class=\"token attr-name\">for</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>guest-name-1<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>Name<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>label</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>input</span> <span class=\"token attr-name\">id</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>guest-name-1<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>text<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">name</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>guest-name-1<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>label</span> <span class=\"token attr-name\">for</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>guest-email-1<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>Email<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>label</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>input</span> <span class=\"token attr-name\">id</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>guest-email-1<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>email<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">name</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>guest-email-1<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>fieldset</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>form-repeatable</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>When this gets picked up by the component, the whole <code>fieldset</code> will become the template. When users add new Guests, all of the numeric values — whether in text or attributes — increment automatically when new groups are added. So in this case, the <code>legend</code> will update, as will the <code>for</code> attribute on the <code>label</code> and <code>id</code> and <code>name</code> attributes on the <code>input</code>.</p>\n<h2 id=\"need-to-constrain-the-responses%3F-you-got-it.\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#need-to-constrain-the-responses%3F-you-got-it.\" aria-hidden=\"true\">#</a> Need to constrain the responses? You got it.</h2>\n<p>Use the <code>min</code> and <code>max</code> attributes to control the number of allowed groups:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>form-repeatable</span>\n  <span class=\"token attr-name\">min</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>2<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">max</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>5<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">add-label</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>Add Team Member<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">remove-label</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>Remove<span class=\"token punctuation\">\"</span></span>\n<span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>div</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>label</span> <span class=\"token attr-name\">for</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>member-1<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>Team Member 1<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>label</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>input</span> <span class=\"token attr-name\">id</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>member-1<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>text<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">name</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>members[]<span class=\"token punctuation\">\"</span></span> <span class=\"token punctuation\">/></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>div</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>form-repeatable</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>This creates a component that:</p>\n<ul>\n<li>Starts with 1 member,</li>\n<li>Requires adding new members until the <code>min</code> threshold (2) is met,</li>\n<li>Cannot have fewer than 2 team members,</li>\n<li>Cannot have more than 5 team members, and</li>\n<li>Uses custom button labels.</li>\n</ul>\n<p>The remove buttons are not shown when at the minimum threshold (1 by default) and the add button disappears when you hit the maximum.</p>\n<h2 id=\"prefer-an-explicit-template%3F-bring-it!\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#prefer-an-explicit-template%3F-bring-it!\" aria-hidden=\"true\">#</a> Prefer an explicit <code>template</code>? Bring it!</h2>\n<p>This component can accept a <code>template</code> element containing the fields you want to repeat. Just drop in <code>{n}</code> placeholders where you want the sequential numbers to appear:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>form-repeatable</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>template</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>div</span><span class=\"token punctuation\">></span></span>\n      <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>label</span> <span class=\"token attr-name\">for</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>email-{n}<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>Email {n}<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>label</span><span class=\"token punctuation\">></span></span>\n      <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>input</span> <span class=\"token attr-name\">id</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>email-{n}<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>email<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">name</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>emails[]<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>div</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>template</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>form-repeatable</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>When hoisted into the component, the <code>template</code> element is removed from the light DOM and used internally.</p>\n<h2 id=\"here%E2%80%99s-what-you-need-to-know-about-styling-it\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#here%E2%80%99s-what-you-need-to-know-about-styling-it\" aria-hidden=\"true\">#</a> Here’s what you need to know about styling it</h2>\n<p>The component uses Shadow DOM to encapsulate its internal structure, but you can style it using CSS parts and custom properties. It also adopts your global styles automatically.</p>\n<p>The component uses CSS Grid by default:</p>\n<ul>\n<li><strong>Two columns</strong>: Content in column 1, remove buttons aligned inline end in column 2</li>\n<li><strong>Subgrid</strong>: Each group uses <code>subgrid</code> to align with parent grid</li>\n<li><strong>Add button</strong>: Appears below all groups</li>\n</ul>\n<p>You can use CSS parts to style the buttons and field groups. Here are some examples:</p>\n<pre class=\"language-css\" tabindex=\"0\"><code class=\"language-css\"><span class=\"token comment\">/* Style all buttons */</span>\n<span class=\"token selector\">form-repeatable::part(button)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token property\">padding</span><span class=\"token punctuation\">:</span> 0.5rem 1rem<span class=\"token punctuation\">;</span>\n  <span class=\"token property\">border-radius</span><span class=\"token punctuation\">:</span> 4px<span class=\"token punctuation\">;</span>\n  <span class=\"token property\">cursor</span><span class=\"token punctuation\">:</span> pointer<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token comment\">/* Style the add button */</span>\n<span class=\"token selector\">form-repeatable::part(add-button)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token property\">background</span><span class=\"token punctuation\">:</span> #28a745<span class=\"token punctuation\">;</span>\n  <span class=\"token property\">color</span><span class=\"token punctuation\">:</span> white<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token comment\">/* Style remove buttons */</span>\n<span class=\"token selector\">form-repeatable::part(remove-button)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token property\">background</span><span class=\"token punctuation\">:</span> #dc3545<span class=\"token punctuation\">;</span>\n  <span class=\"token property\">color</span><span class=\"token punctuation\">:</span> white<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token comment\">/* Customize the grid layout */</span>\n<span class=\"token selector\">form-repeatable::part(groups)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token property\">grid-template-columns</span><span class=\"token punctuation\">:</span> 1fr auto<span class=\"token punctuation\">;</span>\n  <span class=\"token property\">gap</span><span class=\"token punctuation\">:</span> 1rem<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token comment\">/* Style each group */</span>\n<span class=\"token selector\">form-repeatable::part(group)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token property\">padding</span><span class=\"token punctuation\">:</span> 1rem<span class=\"token punctuation\">;</span>\n  <span class=\"token property\">background</span><span class=\"token punctuation\">:</span> #f8f9fa<span class=\"token punctuation\">;</span>\n  <span class=\"token property\">border-radius</span><span class=\"token punctuation\">:</span> 4px<span class=\"token punctuation\">;</span>\n  <span class=\"token property\">margin-bottom</span><span class=\"token punctuation\">:</span> 0.5rem<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<p>Available parts:</p>\n<ul>\n<li><code>groups</code> - Container for all groups (CSS grid by default)</li>\n<li><code>group</code> - Each repeatable group wrapper</li>\n<li><code>content</code> - Container for group’s fields</li>\n<li><code>group-controls</code> - Container for the remove button</li>\n<li><code>controls</code> - Container for the add button</li>\n<li><code>button</code> - All buttons</li>\n<li><code>add-button</code> - The add button</li>\n<li><code>remove-button</code> - All remove buttons</li>\n</ul>\n<h2 id=\"want-to-keep-a-watchful-eye%3F-you%E2%80%99re-extra%2C-but-sure.\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#want-to-keep-a-watchful-eye%3F-you%E2%80%99re-extra%2C-but-sure.\" aria-hidden=\"true\">#</a> Want to keep a watchful eye? You’re extra, but sure.</h2>\n<p>You can listen for when groups are added or removed and run your own custom code:</p>\n<pre class=\"language-javascript\" tabindex=\"0\"><code class=\"language-javascript\"><span class=\"token keyword\">const</span> repeatable <span class=\"token operator\">=</span> document<span class=\"token punctuation\">.</span><span class=\"token function\">querySelector</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"form-repeatable\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\nrepeatable<span class=\"token punctuation\">.</span><span class=\"token function\">addEventListener</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"form-repeatable:added\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">event</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n  console<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Group added. Total groups:\"</span><span class=\"token punctuation\">,</span> event<span class=\"token punctuation\">.</span>detail<span class=\"token punctuation\">.</span>groupCount<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\nrepeatable<span class=\"token punctuation\">.</span><span class=\"token function\">addEventListener</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"form-repeatable:removed\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">event</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n  console<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Group removed. Total groups:\"</span><span class=\"token punctuation\">,</span> event<span class=\"token punctuation\">.</span>detail<span class=\"token punctuation\">.</span>groupCount<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre>\n<h2 id=\"go-with-the-progressive-enhancement-flow\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#go-with-the-progressive-enhancement-flow\" aria-hidden=\"true\">#</a> Go with the progressive enhancement flow</h2>\n<p>If JavaScript fails, users see the initial field group(s) and can fill them in. They can’t add more, but nothing breaks. Make sure your minimum count accommodates users without JavaScript.</p>\n<h2 id=\"demo\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#demo\" aria-hidden=\"true\">#</a> Demo</h2>\n<p>Explore <a href=\"https://aarongustafson.github.io/form-repeatable/demo/\">the demo</a> with various examples:</p>\n<figure id=\"fig-2025-12-06-07\" class=\"media-container\">\n<fullscreen-control class=\"talk__slides__embed video-embed__video\">\n<iframe src=\"https://aarongustafson.github.io/form-repeatable/demo/\" class=\"talk__slides__embed video-embed__video\" frameborder=\"0\"></iframe>\n</fullscreen-control>\n</figure>\n<h2 id=\"grab-it-now\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#grab-it-now\" aria-hidden=\"true\">#</a> Grab it now</h2>\n<p>Check out the project on <a href=\"https://github.com/aarongustafson/form-repeatable\">GitHub</a>. Install via npm:</p>\n<pre class=\"language-bash\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"token function\">npm</span> <span class=\"token function\">install</span> @aarongustafson/form-repeatable</code></pre>\n<p>Import and go:</p>\n<pre class=\"language-javascript\" tabindex=\"0\"><code class=\"language-javascript\"><span class=\"token keyword\">import</span> <span class=\"token string\">\"@aarongustafson/form-repeatable\"</span><span class=\"token punctuation\">;</span></code></pre>\n<p>This single component instance manages all your repeatable field groups with native form participation — no framework required.</p>\n","social_text":"Add repeatable form field groups with automatic numbering and native form participation.","url":"https://www.aaron-gustafson.com/notebook/repeatable-form-fields-made-simple/","tags":["forms","HTML","JavaScript","progressive enhancement","web components","web forms"],"date_published":"2026-01-31T00:04:36Z"},{"id":"https://www.aaron-gustafson.com/notebook/fullscreen-video-and-iframes-made-easy/","title":"✍🏻 Fullscreen Video and Iframes Made Easy","summary":"The fullscreen-control web component adds fullscreen capabilities to any video or iframe element with a single wrapper and zero configuration.","content_html":"<p>Adding fullscreen capabilities to videos and embedded iframes shouldn’t require wrestling with prefixed APIs or managing focus states. The <code>fullscreen-control</code> web component handles all of that for you — just wrap it around the element. The component handles the rest as a discrete progressive enhancement.</p>\n<h2 id=\"easy-peasy\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#easy-peasy\" aria-hidden=\"true\">#</a> Easy-peasy</h2>\n<p>Here’s a simple example using a <code>video</code> element:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>fullscreen-control</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>video</span> <span class=\"token attr-name\">src</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>video.mp4<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>video</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>fullscreen-control</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>With that in place, the component</p>\n<ul>\n<li>Adds a styleable button for launching fullscreen control over the contained element,</li>\n<li>Handles browser prefixes as needed,</li>\n<li>Manages focus automatically,</li>\n<li>Rigs up the necessary keyboard events (e.g. <kbd>Escape</kbd> to exit), and</li>\n<li>Assigns the relevant ARIA attributes.</li>\n</ul>\n<p>The component uses light DOM, so your <code>video</code> stays in the regular DOM tree and all your existing CSS continues to work.</p>\n<h2 id=\"fullscreen-iframes\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#fullscreen-iframes\" aria-hidden=\"true\">#</a> Fullscreen iframes</h2>\n<p>Need to embed a YouTube video, slide deck, or code demo? The component works with <code>iframe</code> elements too:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>fullscreen-control</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>iframe</span>\n    <span class=\"token attr-name\">src</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>https://www.youtube.com/embed/dQw4w9WgXcQ<span class=\"token punctuation\">\"</span></span>\n    <span class=\"token attr-name\">width</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>560<span class=\"token punctuation\">\"</span></span>\n    <span class=\"token attr-name\">height</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>315<span class=\"token punctuation\">\"</span></span>\n    <span class=\"token attr-name\">title</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>YouTube video player<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>iframe</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>fullscreen-control</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>The component automatically adds the necessary <code>allow=&quot;fullscreen&quot;</code> and <code>allowfullscreen</code> attributes, including prefixed versions for broader compatibility.</p>\n<h2 id=\"customizable-button-text\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#customizable-button-text\" aria-hidden=\"true\">#</a> Customizable <code>button</code> text</h2>\n<p>You can change the <code>button</code> label to match your site’s language or writing style by setting the <code>button-text</code> attribute:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>fullscreen-control</span> <span class=\"token attr-name\">button-text</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>全画面表示<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>video</span> <span class=\"token attr-name\">src</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>video.mp4<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>video</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>fullscreen-control</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>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 <code>{name}</code> token. For example:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>fullscreen-control</span> <span class=\"token attr-name\">button-text</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>View {name} fullscreen<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>video</span> <span class=\"token attr-name\">src</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>video.mp4<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">aria-label</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>Product demo<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>video</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>fullscreen-control</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>This creates a <code>button</code> with the text “View Product demo fullscreen”. The component looks for <code>aria-label</code>, <code>title</code>, or other native naming on the wrapped element and uses that to make the <code>button</code> contextual.</p>\n<h2 id=\"distinct-screen-reader-labels\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#distinct-screen-reader-labels\" aria-hidden=\"true\">#</a> Distinct screen reader labels</h2>\n<p>If you want the visible label and accessible button name to differ, use the <code>button-label</code> attribute. Like <code>button-text</code>, it can also inject the accessible name of the controlled element using the <code>{name}</code> token:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>fullscreen-control</span>\n  <span class=\"token attr-name\">button-text</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>Fullscreen<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">button-label</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>View {name} in fullscreen mode<span class=\"token punctuation\">\"</span></span>\n<span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>iframe</span> <span class=\"token attr-name\">src</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>https://example.com<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">title</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>Product teaser<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span> <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>iframe</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>fullscreen-control</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>This code will generate a <code>button</code> that visually reads “Fullscreen”, but is announced as “View Product teaser in fullscreen mode” to screen readers. In mode cases, <code>button-text</code> 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.</p>\n<h2 id=\"focus-management\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#focus-management\" aria-hidden=\"true\">#</a> Focus management</h2>\n<p>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.</p>\n<h2 id=\"need-more-control%3F\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#need-more-control%3F\" aria-hidden=\"true\">#</a> Need more control?</h2>\n<p>Want to manage the component yourself? The component exposes three methods:</p>\n<pre class=\"language-javascript\" tabindex=\"0\"><code class=\"language-javascript\"><span class=\"token keyword\">const</span> control <span class=\"token operator\">=</span> document<span class=\"token punctuation\">.</span><span class=\"token function\">querySelector</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"fullscreen-control\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token comment\">// Enter fullscreen</span>\n<span class=\"token keyword\">await</span> control<span class=\"token punctuation\">.</span><span class=\"token function\">enterFullscreen</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token comment\">// Exit fullscreen</span>\n<span class=\"token keyword\">await</span> control<span class=\"token punctuation\">.</span><span class=\"token function\">exitFullscreen</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token comment\">// Toggle fullscreen state</span>\ncontrol<span class=\"token punctuation\">.</span><span class=\"token function\">toggleFullscreen</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre>\n<p>These handle all the browser prefixes and error handling for you.</p>\n<p>There are also a set of events you can tap into when the fullscreen state changes:</p>\n<pre class=\"language-javascript\" tabindex=\"0\"><code class=\"language-javascript\"><span class=\"token keyword\">const</span> control <span class=\"token operator\">=</span> document<span class=\"token punctuation\">.</span><span class=\"token function\">querySelector</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"fullscreen-control\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\ncontrol<span class=\"token punctuation\">.</span><span class=\"token function\">addEventListener</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"fullscreen-control:enter\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n  console<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Entered fullscreen mode\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\ncontrol<span class=\"token punctuation\">.</span><span class=\"token function\">addEventListener</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"fullscreen-control:exit\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n  console<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Exited fullscreen mode\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre>\n<p>These events give you the ability to pause other media, track analytics, and the like.</p>\n<h2 id=\"style-the-button\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#style-the-button\" aria-hidden=\"true\">#</a> Style the button</h2>\n<p>Since the component uses light DOM, you can style the button directly with CSS:</p>\n<pre class=\"language-css\" tabindex=\"0\"><code class=\"language-css\"><span class=\"token selector\">fullscreen-control button</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token property\">background</span><span class=\"token punctuation\">:</span> #ff6b6b<span class=\"token punctuation\">;</span>\n  <span class=\"token property\">color</span><span class=\"token punctuation\">:</span> white<span class=\"token punctuation\">;</span>\n  <span class=\"token property\">border</span><span class=\"token punctuation\">:</span> none<span class=\"token punctuation\">;</span>\n  <span class=\"token property\">padding</span><span class=\"token punctuation\">:</span> 0.75rem 1.5rem<span class=\"token punctuation\">;</span>\n  <span class=\"token property\">border-radius</span><span class=\"token punctuation\">:</span> 20px<span class=\"token punctuation\">;</span>\n  <span class=\"token property\">font-weight</span><span class=\"token punctuation\">:</span> bold<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token selector\">fullscreen-control button:hover</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token property\">background</span><span class=\"token punctuation\">:</span> #ff5252<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<p>The button is positioned absolutely by default (top-right corner), but you can adjust this with CSS custom properties:</p>\n<pre class=\"language-css\" tabindex=\"0\"><code class=\"language-css\"><span class=\"token selector\">fullscreen-control</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token property\">--fullscreen-control-button-inset-block-start</span><span class=\"token punctuation\">:</span> 1rem<span class=\"token punctuation\">;</span>\n  <span class=\"token property\">--fullscreen-control-button-inset-inline-end</span><span class=\"token punctuation\">:</span> 1rem<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<p>This uses logical properties, so it adapts automatically to different writing modes.</p>\n<h2 id=\"installation\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#installation\" aria-hidden=\"true\">#</a> Installation</h2>\n<p>Install via npm:</p>\n<pre class=\"language-bash\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"token function\">npm</span> <span class=\"token function\">install</span> @aarongustafson/fullscreen-control</code></pre>\n<p>Then import it in your JavaScript:</p>\n<pre class=\"language-javascript\" tabindex=\"0\"><code class=\"language-javascript\"><span class=\"token keyword\">import</span> <span class=\"token string\">\"@aarongustafson/fullscreen-control/define.js\"</span><span class=\"token punctuation\">;</span></code></pre>\n<p>Or load it from a CDN for quick prototyping:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>script</span> <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>module<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span><span class=\"token script\"><span class=\"token language-javascript\">\n  <span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> defineFullscreenControl <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"https://unpkg.com/@aarongustafson/fullscreen-control@latest/define.js?module\"</span><span class=\"token punctuation\">;</span>\n  <span class=\"token function\">defineFullscreenControl</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n</span></span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>script</span><span class=\"token punctuation\">></span></span></code></pre>\n<h2 id=\"browser-support\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#browser-support\" aria-hidden=\"true\">#</a> Browser support</h2>\n<p>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.</p>\n<h2 id=\"demo-and-source-code\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#demo-and-source-code\" aria-hidden=\"true\">#</a> Demo and source code</h2>\n<p>Check out the <a href=\"https://aarongustafson.github.io/fullscreen-control/demo/\">live demo</a> to see all the features in action, or grab the code from <a href=\"https://github.com/aarongustafson/fullscreen-control\">GitHub</a>.</p>\n","social_text":"Add fullscreen controls to videos and iframes with progressive enhancement. One wrapper, zero hassle.","url":"https://www.aaron-gustafson.com/notebook/fullscreen-video-and-iframes-made-easy/","tags":["web components","progressive enhancement","HTML","video","accessibility","media"],"date_published":"2025-12-29T17:17:27Z"},{"id":"https://www.aaron-gustafson.com/notebook/dynamic-datalist-autocomplete-from-an-api/","title":"✍🏻 Dynamic Datalist: Autocomplete from an API","summary":"The <code>datalist</code> element is great for autocomplete, but it’s static. The <code>dynamic-datalist</code> web component brings dynamic, API-driven suggestions to your text fields as users type.","content_html":"<p>HTML’s <code>datalist</code> element provides native autocomplete functionality, but it’s entirely static—you have to know all the options up front. The <code>dynamic-datalist</code> 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.</p>\n<p>This component is a modern replacement for <a href=\"https://github.com/easy-designs/jquery.easy-predictive-typing.js\">my old jQuery predictive typing plugin</a>. I’ve reimagined it as a standards-based web component.</p>\n<h2 id=\"basic-usage\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#basic-usage\" aria-hidden=\"true\">#</a> Basic usage</h2>\n<p>To use the component, wrap it around your <code>input</code> field and specify an endpoint:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>dynamic-datalist</span> <span class=\"token attr-name\">endpoint</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>/api/search<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>label</span> <span class=\"token attr-name\">for</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>search<span class=\"token punctuation\">\"</span></span>\n    <span class=\"token punctuation\">></span></span>Search\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>input</span>\n      <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>text<span class=\"token punctuation\">\"</span></span>\n      <span class=\"token attr-name\">id</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>search<span class=\"token punctuation\">\"</span></span>\n      <span class=\"token attr-name\">name</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>search<span class=\"token punctuation\">\"</span></span>\n      <span class=\"token attr-name\">placeholder</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>Type to search...<span class=\"token punctuation\">\"</span></span>\n    <span class=\"token punctuation\">/></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>label</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>dynamic-datalist</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>As users type, the component makes GET requests to that endpoint, passing in the typed value as the “query” parameter (e.g., <code>/api/search?query=WHAT_THE_USER_TYPED</code>). The response from the endpoint is used to populates a dynamic <code>datalist</code> element with the results.</p>\n<p>The structure of the response should be JSON with an <code>options</code> array of string values:</p>\n<pre class=\"language-json\" tabindex=\"0\"><code class=\"language-json\"><span class=\"token punctuation\">{</span>\n  <span class=\"token property\">\"options\"</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">[</span><span class=\"token string\">\"option 1\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"option 2\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"option 3\"</span><span class=\"token punctuation\">]</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<h2 id=\"how-it-works\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#how-it-works\" aria-hidden=\"true\">#</a> How it works</h2>\n<p>Under the hood, the component:</p>\n<ol>\n<li>Adopts (or creates) a <code>datalist</code> element for your <code>input</code>,</li>\n<li>Listens for “input” events,</li>\n<li>Debounces requests (waiting at least 250ms) to avoid overwhelming your API,</li>\n<li>Sends requests to your endpoint with the current value of the <code>input</code>,</li>\n<li>Reads back the JSON response,</li>\n<li>Updates the <code>datalist</code> <code>option</code> elements, and</li>\n<li>Dispatches the update event.</li>\n</ol>\n<p>All of this happens transparently—users just see autocomplete suggestions appearing as they type.</p>\n<h2 id=\"need-post%3F\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#need-post%3F\" aria-hidden=\"true\">#</a> Need POST?</h2>\n<p>You can change the submission method via the <code>method</code> attribute:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>dynamic-datalist</span> <span class=\"token attr-name\">endpoint</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>/api/lookup<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">method</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>post<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>label</span> <span class=\"token attr-name\">for</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>lookup<span class=\"token punctuation\">\"</span></span>\n    <span class=\"token punctuation\">></span></span>Lookup\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>input</span> <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>text<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">id</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>lookup<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">name</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>lookup<span class=\"token punctuation\">\"</span></span> <span class=\"token punctuation\">/></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>label</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>dynamic-datalist</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>This sends a POST request with a JSON body: <code>{ &quot;query&quot;: &quot;...&quot; }</code>. Currently GET and POST are supported, but I could add more if folks want them.</p>\n<h2 id=\"custom-variable-names\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#custom-variable-names\" aria-hidden=\"true\">#</a> Custom variable names</h2>\n<p>As I mentioned, the component uses “query” as the parameter name by default, but you can easily change it via the <code>key</code> attribute:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>dynamic-datalist</span> <span class=\"token attr-name\">endpoint</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>/api/terms<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">key</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>term<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>label</span> <span class=\"token attr-name\">for</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>search<span class=\"token punctuation\">\"</span></span>\n    <span class=\"token punctuation\">></span></span>Term search\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>input</span> <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>text<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">id</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>search<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">name</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>term<span class=\"token punctuation\">\"</span></span> <span class=\"token punctuation\">/></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>label</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>dynamic-datalist</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>This sends the GET request <code>/api/terms?term=...</code>.</p>\n<h2 id=\"working-with-existing-datalists\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#working-with-existing-datalists\" aria-hidden=\"true\">#</a> Working with existing datalists</h2>\n<p>If your <code>input</code> already has a <code>datalist</code> defined, the component will inherit it and replace the existing options with the fetched results, which makes for a nice progressive enhancement:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>dynamic-datalist</span> <span class=\"token attr-name\">endpoint</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>/api/cities<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>label</span> <span class=\"token attr-name\">for</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>city<span class=\"token punctuation\">\"</span></span>\n    <span class=\"token punctuation\">></span></span>City\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>input</span>\n      <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>text<span class=\"token punctuation\">\"</span></span>\n      <span class=\"token attr-name\">id</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>city<span class=\"token punctuation\">\"</span></span>\n      <span class=\"token attr-name\">list</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>cities-list<span class=\"token punctuation\">\"</span></span>\n      <span class=\"token attr-name\">placeholder</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>Type a city…<span class=\"token punctuation\">\"</span></span>\n    <span class=\"token punctuation\">/></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>label</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>datalist</span> <span class=\"token attr-name\">id</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>cities-list<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>option</span><span class=\"token punctuation\">></span></span>New York<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>option</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>option</span><span class=\"token punctuation\">></span></span>Los Angeles<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>option</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>option</span><span class=\"token punctuation\">></span></span>Chicago<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>option</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>datalist</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>dynamic-datalist</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>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.</p>\n<h2 id=\"event-handling\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#event-handling\" aria-hidden=\"true\">#</a> Event handling</h2>\n<p>If you want to tap into the component’s event system, it fires three custom events:</p>\n<ul>\n<li><code>dynamic-datalist:ready</code> - Fired when the component initializes</li>\n<li><code>dynamic-datalist:update</code> - Fired when the <code>datalist</code> is updated with new options</li>\n<li><code>dynamic-datalist:error</code> - Fired when an error occurs fetching data</li>\n</ul>\n<pre class=\"language-javascript\" tabindex=\"0\"><code class=\"language-javascript\"><span class=\"token keyword\">const</span> element <span class=\"token operator\">=</span> document<span class=\"token punctuation\">.</span><span class=\"token function\">querySelector</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"dynamic-datalist\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\nelement<span class=\"token punctuation\">.</span><span class=\"token function\">addEventListener</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"dynamic-datalist:ready\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">e</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n  console<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Component ready:\"</span><span class=\"token punctuation\">,</span> e<span class=\"token punctuation\">.</span>detail<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\nelement<span class=\"token punctuation\">.</span><span class=\"token function\">addEventListener</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"dynamic-datalist:update\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">e</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n  console<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Options updated:\"</span><span class=\"token punctuation\">,</span> e<span class=\"token punctuation\">.</span>detail<span class=\"token punctuation\">.</span>options<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\nelement<span class=\"token punctuation\">.</span><span class=\"token function\">addEventListener</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"dynamic-datalist:error\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">e</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n  console<span class=\"token punctuation\">.</span><span class=\"token function\">error</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Error:\"</span><span class=\"token punctuation\">,</span> e<span class=\"token punctuation\">.</span>detail<span class=\"token punctuation\">.</span>error<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre>\n<p>Each event provides helpful <code>detail</code> objects with references to the <code>input</code>, <code>datalist</code>, and other relevant data.</p>\n<h2 id=\"demo\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#demo\" aria-hidden=\"true\">#</a> Demo</h2>\n<p>Check out <a href=\"https://aarongustafson.github.io/dynamic-datalist/demo/\">the demo</a> for live examples (there are also <a href=\"https://aarongustafson.github.io/dynamic-datalist/demo/unpkg.html\">unpkg</a> and <a href=\"https://aarongustafson.github.io/dynamic-datalist/demo/esm.html\">ESM</a> builds if you want to test CDN delivery):</p>\n<figure id=\"fig-2025-12-06-03\" class=\"media-container\">\n<fullscreen-control class=\"talk__slides__embed video-embed__video\">\n<iframe src=\"https://aarongustafson.github.io/dynamic-datalist/demo/\" class=\"talk__slides__embed video-embed__video\" frameborder=\"0\"></iframe>\n</fullscreen-control>\n</figure>\n<h2 id=\"grab-it\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#grab-it\" aria-hidden=\"true\">#</a> Grab it</h2>\n<p>The project is available on <a href=\"https://github.com/aarongustafson/dynamic-datalist\">GitHub</a>. You can also install via npm:</p>\n<pre class=\"language-bash\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"token function\">npm</span> <span class=\"token function\">install</span> @aarongustafson/dynamic-datalist</code></pre>\n<p>If you go that route, there are a few ways to register the element depending on your build setup:</p>\n<h3 id=\"option-1%3A-define-it-yourself\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#option-1%3A-define-it-yourself\" aria-hidden=\"true\">#</a> Option 1: Define it yourself</h3>\n<pre class=\"language-javascript\" tabindex=\"0\"><code class=\"language-javascript\"><span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> DynamicDatalistElement <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"@aarongustafson/dynamic-datalist\"</span><span class=\"token punctuation\">;</span>\n\ncustomElements<span class=\"token punctuation\">.</span><span class=\"token function\">define</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"dynamic-datalist\"</span><span class=\"token punctuation\">,</span> DynamicDatalistElement<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre>\n<h3 id=\"option-2%3A-let-the-helper-guard-registration\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#option-2%3A-let-the-helper-guard-registration\" aria-hidden=\"true\">#</a> Option 2: Let the helper guard registration</h3>\n<pre class=\"language-javascript\" tabindex=\"0\"><code class=\"language-javascript\"><span class=\"token keyword\">import</span> <span class=\"token string\">\"@aarongustafson/dynamic-datalist/define.js\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token comment\">// or, when you need to wait:</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> defineDynamicDatalist <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"@aarongustafson/dynamic-datalist/define.js\"</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token function\">defineDynamicDatalist</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre>\n<h3 id=\"option-3%3A-drop-the-helper-in-via-a-%3Cscript%3E-tag\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#option-3%3A-drop-the-helper-in-via-a-%3Cscript%3E-tag\" aria-hidden=\"true\">#</a> Option 3: Drop the helper in via a <code>&lt;script&gt;</code> tag</h3>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>script</span>\n  <span class=\"token attr-name\">src</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>./node_modules/@aarongustafson/dynamic-datalist/define.js<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>module<span class=\"token punctuation\">\"</span></span>\n<span class=\"token punctuation\">></span></span><span class=\"token script\"></span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>script</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>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.</p>\n","social_text":"Want API-driven autocomplete suggestions in your forms? Here’s a web component that makes it happen.","url":"https://www.aaron-gustafson.com/notebook/dynamic-datalist-autocomplete-from-an-api/","tags":["forms","HTML","JavaScript","progressive enhancement","web components","web forms","API"],"date_published":"2025-12-16T19:46:29Z"},{"id":"https://www.aaron-gustafson.com/notebook/lazy-loading-images-based-on-screen-size/","title":"✍🏻 Lazy Loading Images Based on Screen Size","summary":"The lazy-img web component goes beyond native lazy loading and srcset—it can completely skip loading images on small screens, saving bandwidth where it matters most.","content_html":"<p>Native lazy loading and <code>srcset</code> are great, but they have a limitation: they always load <em>some</em> variant of the image. The <code>lazy-img</code> 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.</p>\n<p>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?</p>\n<h2 id=\"the-performance-benefit\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#the-performance-benefit\" aria-hidden=\"true\">#</a> The performance benefit</h2>\n<p>Unlike <code>picture</code> or <code>srcset</code>, which always load some image variant, <code>lazy-img</code> can <strong>completely skip loading images</strong> on screens or containers below your specified threshold. Set <code>min-inline-size=&quot;768px&quot;</code> and mobile users will never download that image at all—saving data and speeding up page loads.</p>\n<p>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 <code>loaded</code> and <code>qualifies</code> attributes (which we’ll get to shortly).</p>\n<h2 id=\"basic-usage\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#basic-usage\" aria-hidden=\"true\">#</a> Basic usage</h2>\n<p>The <code>lazy-img</code> works pretty much identically to a regular <code>img</code> element, with all the attributes you know and love:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>lazy-img</span> <span class=\"token attr-name\">src</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>image.jpg<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">alt</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>A beautiful image<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span> <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>lazy-img</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>But that’s not very interesting. The real power comes from conditional loading.</p>\n<h2 id=\"container-queries-(default)\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#container-queries-(default)\" aria-hidden=\"true\">#</a> Container queries (default)</h2>\n<p>Load an image only when its container reaches a minimum width:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>lazy-img</span> <span class=\"token attr-name\">src</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>large-image.jpg<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">alt</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>Large image<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">min-inline-size</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>500px<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>lazy-img</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>The image loads when the <code>lazy-img</code> element’s container reaches 500px wide. This is the default query mode—it uses <code>ResizeObserver</code> to watch the container size.</p>\n<h2 id=\"media-queries\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#media-queries\" aria-hidden=\"true\">#</a> Media queries</h2>\n<p>You can lazy load images based on viewport width instead by switching to media query mode:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>lazy-img</span>\n  <span class=\"token attr-name\">src</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>desktop-image.jpg<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">alt</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>Desktop image<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">min-inline-size</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>768px<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">query</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>media<span class=\"token punctuation\">\"</span></span>\n<span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>lazy-img</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>With this configuration, the image loads when the browser window is at least 768px wide.</p>\n<h2 id=\"view-mode-(scroll-based-loading)\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#view-mode-(scroll-based-loading)\" aria-hidden=\"true\">#</a> View mode (scroll-based loading)</h2>\n<p>Load images when they scroll into view using <code>IntersectionObserver</code> by switching to the “view” query type:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>lazy-img</span> <span class=\"token attr-name\">src</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>image.jpg<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">alt</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>Loads when scrolled into view<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">query</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>view<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>lazy-img</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>The default behavior (<code>view-range-start=&quot;entry 0%&quot;</code>) loads as soon as any part of the image enters the viewport.</p>\n<p>Control when images load with the <code>view-range-start</code> attribute:</p>\n<p><strong>Load when 50% visible:</strong></p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>lazy-img</span>\n  <span class=\"token attr-name\">src</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>image.jpg<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">alt</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>Loads when half visible<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">query</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>view<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">view-range-start</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>entry 50%<span class=\"token punctuation\">\"</span></span>\n<span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>lazy-img</span><span class=\"token punctuation\">></span></span></code></pre>\n<p><strong>Preload before entering viewport:</strong></p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>lazy-img</span>\n  <span class=\"token attr-name\">src</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>image.jpg<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">alt</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>Preloads 200px before visible<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">query</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>view<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">view-range-start</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>entry -200px<span class=\"token punctuation\">\"</span></span>\n<span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>lazy-img</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>This creates a smooth user experience—images are already loaded by the time users scroll to them.</p>\n<h2 id=\"responsive-images\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#responsive-images\" aria-hidden=\"true\">#</a> Responsive images</h2>\n<p>As with regular images, you can use <code>srcset</code> and <code>sizes</code> for responsive images:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>lazy-img</span>\n  <span class=\"token attr-name\">src</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>image-800.jpg<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">srcset</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>image-400.jpg 400w,\n          image-800.jpg 800w,\n          image-1200.jpg 1200w<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">sizes</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>(max-width: 600px) 400px,\n         (max-width: 1000px) 800px,\n         1200px<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">alt</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>Responsive image<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">min-inline-size</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>400px<span class=\"token punctuation\">\"</span></span>\n<span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>lazy-img</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>The component waits until the conditions are met before loading a real image and the browser takes over from there.</p>\n<h2 id=\"named-breakpoints\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#named-breakpoints\" aria-hidden=\"true\">#</a> Named breakpoints</h2>\n<p>You can also define named breakpoints using CSS custom properties:</p>\n<pre class=\"language-css\" tabindex=\"0\"><code class=\"language-css\"><span class=\"token selector\">:root</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token property\">--lazy-img-mq</span><span class=\"token punctuation\">:</span> small<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token atrule\"><span class=\"token rule\">@media</span> <span class=\"token punctuation\">(</span><span class=\"token property\">min-width</span><span class=\"token punctuation\">:</span> 768px<span class=\"token punctuation\">)</span></span> <span class=\"token punctuation\">{</span>\n  <span class=\"token selector\">:root</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token property\">--lazy-img-mq</span><span class=\"token punctuation\">:</span> medium<span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token atrule\"><span class=\"token rule\">@media</span> <span class=\"token punctuation\">(</span><span class=\"token property\">min-width</span><span class=\"token punctuation\">:</span> 1024px<span class=\"token punctuation\">)</span></span> <span class=\"token punctuation\">{</span>\n  <span class=\"token selector\">:root</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token property\">--lazy-img-mq</span><span class=\"token punctuation\">:</span> large<span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<p>Then reference them in your markup:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>lazy-img</span>\n  <span class=\"token attr-name\">src</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>image.jpg<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">alt</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>Image with named breakpoints<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">named-breakpoints</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>medium, large<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">query</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>media<span class=\"token punctuation\">\"</span></span>\n<span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>lazy-img</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>The image loads when <code>--lazy-img-mq</code> matches “medium” or “large”.</p>\n<h2 id=\"preventing-layout-shift\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#preventing-layout-shift\" aria-hidden=\"true\">#</a> Preventing layout shift</h2>\n<p>As with regular images, don’t forget to use <code>width</code> and <code>height</code> attributes to prevent Cumulative Layout Shift (CLS):</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>lazy-img</span>\n  <span class=\"token attr-name\">src</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>image.jpg<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">alt</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>A beautiful image<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">width</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>800<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">height</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>600<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">min-inline-size</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>768px<span class=\"token punctuation\">\"</span></span>\n<span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>lazy-img</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>The browser reserves the correct space while the image loads, preventing content from jumping around.</p>\n<h2 id=\"state-attributes-for-styling\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#state-attributes-for-styling\" aria-hidden=\"true\">#</a> State attributes for styling</h2>\n<p>The component provides <code>loaded</code> and <code>qualifies</code> attributes you can use in CSS:</p>\n<pre class=\"language-css\" tabindex=\"0\"><code class=\"language-css\"><span class=\"token comment\">/* Hide images that loaded but no longer meet conditions */</span>\n<span class=\"token selector\">lazy-img[loaded]:not([qualifies])</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token property\">display</span><span class=\"token punctuation\">:</span> none<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token comment\">/* Show a placeholder for images that qualify but haven't loaded */</span>\n<span class=\"token selector\">lazy-img[qualifies]:not([loaded])::before</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token property\">content</span><span class=\"token punctuation\">:</span> <span class=\"token string\">\"Loading…\"</span><span class=\"token punctuation\">;</span>\n  <span class=\"token property\">display</span><span class=\"token punctuation\">:</span> block<span class=\"token punctuation\">;</span>\n  <span class=\"token property\">padding</span><span class=\"token punctuation\">:</span> 2em<span class=\"token punctuation\">;</span>\n  <span class=\"token property\">background</span><span class=\"token punctuation\">:</span> #f0f0f0<span class=\"token punctuation\">;</span>\n  <span class=\"token property\">text-align</span><span class=\"token punctuation\">:</span> center<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<h2 id=\"events\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#events\" aria-hidden=\"true\">#</a> Events</h2>\n<p>If you crave control, you can add your own functionality by listening for when images load:</p>\n<pre class=\"language-javascript\" tabindex=\"0\"><code class=\"language-javascript\"><span class=\"token keyword\">const</span> lazyImg <span class=\"token operator\">=</span> document<span class=\"token punctuation\">.</span><span class=\"token function\">querySelector</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"lazy-img\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\nlazyImg<span class=\"token punctuation\">.</span><span class=\"token function\">addEventListener</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"lazy-img:loaded\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">event</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n  console<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Image loaded:\"</span><span class=\"token punctuation\">,</span> event<span class=\"token punctuation\">.</span>detail<span class=\"token punctuation\">.</span>src<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre>\n<h2 id=\"performance\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#performance\" aria-hidden=\"true\">#</a> Performance</h2>\n<p>The component is highly optimized:</p>\n<ul>\n<li><strong>Throttled resize</strong>: Resize events are throttled to prevent excessive checks</li>\n<li><strong>Shared <code>ResizeObserver</code></strong>: Multiple images observing the same container share a single ResizeObserver</li>\n<li><strong>Shared window resize listener</strong>: Media query mode shares a single window resize listener</li>\n<li><strong>Shared <code>IntersectionObserver</code></strong>: View mode with the same <code>view-range-start</code> shares an <code>IntersectionObserver</code></li>\n<li><strong>Clean disconnection</strong>: Properly cleans up observers when elements are removed</li>\n</ul>\n<p>Even with hundreds of <code>lazy-img</code> elements on a page, performance remains excellent.</p>\n<h2 id=\"progressive-enhancement\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#progressive-enhancement\" aria-hidden=\"true\">#</a> Progressive enhancement</h2>\n<p>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.</p>\n<p>For critical images that are part of your content, use standard <code>img</code> tags. Use <code>lazy-img</code> for conditional enhancements.</p>\n<h2 id=\"demo\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#demo\" aria-hidden=\"true\">#</a> Demo</h2>\n<p>Explore <a href=\"https://aarongustafson.github.io/lazy-img/demo/\">the demo</a> to see container queries, media queries, scroll-based loading, and more in action:</p>\n<figure id=\"fig-2025-12-06-04\" class=\"media-container\">\n<fullscreen-control class=\"talk__slides__embed video-embed__video\">\n<iframe src=\"https://aarongustafson.github.io/lazy-img/demo/\" class=\"talk__slides__embed video-embed__video\" frameborder=\"0\"></iframe>\n</fullscreen-control>\n</figure>\n<h2 id=\"grab-it\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#grab-it\" aria-hidden=\"true\">#</a> Grab it</h2>\n<p>Check out the project on <a href=\"https://github.com/aarongustafson/lazy-img\">GitHub</a>. Install via npm:</p>\n<pre class=\"language-bash\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"token function\">npm</span> <span class=\"token function\">install</span> @aarongustafson/lazy-img</code></pre>\n<p>Import and use:</p>\n<pre class=\"language-javascript\" tabindex=\"0\"><code class=\"language-javascript\"><span class=\"token keyword\">import</span> <span class=\"token string\">\"@aarongustafson/lazy-img\"</span><span class=\"token punctuation\">;</span></code></pre>\n<p>Based on my original <a href=\"https://github.com/easy-designs/easy-lazy-images.js\">Easy Lazy Images</a> concept, reimagined as a modern custom element.</p>\n","social_text":"Want to skip loading images entirely on mobile? Here’s a web component that does just that.","url":"https://www.aaron-gustafson.com/notebook/lazy-loading-images-based-on-screen-size/","tags":["web components","progressive enhancement","HTML","performance","images","responsive design"],"date_published":"2025-12-10T17:15:54Z"},{"id":"https://www.aaron-gustafson.com/notebook/a-web-component-for-obfuscating-form-fields/","title":"✍🏻 A Web Component for Obfuscating Form Fields","summary":"There’s no standard way to make a field readable while editing but obfuscated at rest. The <code>form-obfuscator</code> web component fills that gap, giving you control over how sensitive data appears when fields aren’t focused.","content_html":"<p>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 <code>form-obfuscator</code> web component does exactly that.</p>\n<h2 id=\"basic-usage\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#basic-usage\" aria-hidden=\"true\">#</a> Basic usage</h2>\n<p>Wrap any text field in the component and it will automatically obfuscate the value when the field loses focus:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>form-obfuscator</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>label</span> <span class=\"token attr-name\">for</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>secret-key-1<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>What was your first pet’s name?<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>label</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>input</span> <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>text<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">id</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>secret-key-1<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">name</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>secret-key-1<span class=\"token punctuation\">\"</span></span> <span class=\"token punctuation\">/></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>form-obfuscator</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>When users click into the field, they see the actual value. When they click away, it’s replaced with asterisks (*). The real value is preserved in a hidden field for form submission.</p>\n<h2 id=\"custom-obfuscation-characters\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#custom-obfuscation-characters\" aria-hidden=\"true\">#</a> Custom obfuscation characters</h2>\n<p>If you don’t like asterisks, you can specify any character you like:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>form-obfuscator</span> <span class=\"token attr-name\">character</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>•<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>label</span> <span class=\"token attr-name\">for</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>account<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>Account Number<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>label</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>input</span> <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>text<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">id</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>account<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">name</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>account<span class=\"token punctuation\">\"</span></span> <span class=\"token punctuation\">/></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>form-obfuscator</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>Or get creative:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>form-obfuscator</span> <span class=\"token attr-name\">character</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>🤐<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>label</span> <span class=\"token attr-name\">for</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>ssn<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>Social Security Number<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>label</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>input</span> <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>text<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">id</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>ssn<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">name</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>ssn<span class=\"token punctuation\">\"</span></span> <span class=\"token punctuation\">/></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>form-obfuscator</span><span class=\"token punctuation\">></span></span></code></pre>\n<h2 id=\"pattern-based-obfuscation\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#pattern-based-obfuscation\" aria-hidden=\"true\">#</a> Pattern-based obfuscation</h2>\n<p>Sometimes you want to show part of the value while hiding the rest. The <code>pattern</code> attribute lets you specify which characters to keep visible:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>form-obfuscator</span> <span class=\"token attr-name\">pattern</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>\\d{4}$<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>label</span> <span class=\"token attr-name\">for</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>ssn<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>Social Security Number<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>label</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>input</span> <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>text<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">id</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>ssn<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">name</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>ssn<span class=\"token punctuation\">\"</span></span> <span class=\"token punctuation\">/></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>form-obfuscator</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>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.</p>\n<h2 id=\"limiting-displayed-characters\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#limiting-displayed-characters\" aria-hidden=\"true\">#</a> Limiting displayed characters</h2>\n<p>Use the <code>maxlength</code> attribute to cap how many characters appear when obfuscated:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>form-obfuscator</span> <span class=\"token attr-name\">maxlength</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>4<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>label</span> <span class=\"token attr-name\">for</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>password<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>Password<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>label</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>input</span> <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>text<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">id</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>password<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">name</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>password<span class=\"token punctuation\">\"</span></span> <span class=\"token punctuation\">/></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>form-obfuscator</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>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.</p>\n<h2 id=\"custom-replacement-functions\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#custom-replacement-functions\" aria-hidden=\"true\">#</a> Custom replacement functions</h2>\n<p>For complete control, you can provide a JavaScript function via the <code>replacer</code> attribute:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>script</span><span class=\"token punctuation\">></span></span><span class=\"token script\"><span class=\"token language-javascript\">\n  window<span class=\"token punctuation\">.</span><span class=\"token function-variable function\">emailReplacer</span> <span class=\"token operator\">=</span> <span class=\"token keyword\">function</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">var</span> username <span class=\"token operator\">=</span> arguments<span class=\"token punctuation\">[</span><span class=\"token number\">0</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">[</span><span class=\"token number\">1</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">var</span> domain <span class=\"token operator\">=</span> arguments<span class=\"token punctuation\">[</span><span class=\"token number\">0</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">[</span><span class=\"token number\">2</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">return</span> username<span class=\"token punctuation\">.</span><span class=\"token function\">replace</span><span class=\"token punctuation\">(</span><span class=\"token regex\"><span class=\"token regex-delimiter\">/</span><span class=\"token regex-source language-regex\">.</span><span class=\"token regex-delimiter\">/</span><span class=\"token regex-flags\">g</span></span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"*\"</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">+</span> domain<span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n</span></span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>script</span><span class=\"token punctuation\">></span></span>\n\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>form-obfuscator</span>\n  <span class=\"token attr-name\">pattern</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>^(.*?)(@.+)$<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">replacer</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>return emailReplacer(arguments)<span class=\"token punctuation\">\"</span></span>\n<span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>label</span> <span class=\"token attr-name\">for</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>email<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>Email Address<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>label</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>input</span> <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>text<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">id</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>email<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">name</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>email<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">value</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>user@example.com<span class=\"token punctuation\">\"</span></span> <span class=\"token punctuation\">/></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>form-obfuscator</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>This example uses a pattern to separate the username from the domain, then obfuscates only the username portion, leaving <code>@example.com</code> visible.</p>\n<p>Here’s another practical example for credit cards:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>script</span><span class=\"token punctuation\">></span></span><span class=\"token script\"><span class=\"token language-javascript\">\n  <span class=\"token keyword\">function</span> <span class=\"token function\">cardNumberReplacer</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">var</span> beginning <span class=\"token operator\">=</span> arguments<span class=\"token punctuation\">[</span><span class=\"token number\">0</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">[</span><span class=\"token number\">1</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">var</span> final_digits <span class=\"token operator\">=</span> arguments<span class=\"token punctuation\">[</span><span class=\"token number\">0</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">[</span><span class=\"token number\">2</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">return</span> beginning<span class=\"token punctuation\">.</span><span class=\"token function\">replace</span><span class=\"token punctuation\">(</span><span class=\"token regex\"><span class=\"token regex-delimiter\">/</span><span class=\"token regex-source language-regex\">\\d</span><span class=\"token regex-delimiter\">/</span><span class=\"token regex-flags\">g</span></span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"*\"</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">+</span> final_digits<span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n</span></span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>script</span><span class=\"token punctuation\">></span></span>\n\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>form-obfuscator</span>\n  <span class=\"token attr-name\">pattern</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>^((?:[\\d]+\\-)+)(\\d+)$<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">replacer</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>return cardNumberReplacer(arguments)<span class=\"token punctuation\">\"</span></span>\n<span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>label</span> <span class=\"token attr-name\">for</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>cc<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>Credit Card<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>label</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>input</span> <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>text<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">id</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>cc<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">name</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>cc<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">value</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>1234-5678-9012-3456<span class=\"token punctuation\">\"</span></span> <span class=\"token punctuation\">/></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>form-obfuscator</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>This displays as <code>****-****-****-3456</code>, showing only the last group of digits.</p>\n<h2 id=\"combining-attributes\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#combining-attributes\" aria-hidden=\"true\">#</a> Combining attributes</h2>\n<p>You can combine these attributes for sophisticated obfuscation patterns:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>form-obfuscator</span> <span class=\"token attr-name\">pattern</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>\\d{4}$<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">character</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>•<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">maxlength</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>16<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>label</span> <span class=\"token attr-name\">for</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>card<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>Credit Card<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>label</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>input</span> <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>text<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">id</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>card<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">name</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>card<span class=\"token punctuation\">\"</span></span> <span class=\"token punctuation\">/></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>form-obfuscator</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>This keeps the last 4 digits visible, uses bullets for obfuscation, and limits the display to 16 characters total.</p>\n<h2 id=\"event-handling\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#event-handling\" aria-hidden=\"true\">#</a> Event handling</h2>\n<p>The component dispatches custom events when values are hidden or revealed:</p>\n<pre class=\"language-javascript\" tabindex=\"0\"><code class=\"language-javascript\"><span class=\"token keyword\">const</span> obfuscator <span class=\"token operator\">=</span> document<span class=\"token punctuation\">.</span><span class=\"token function\">querySelector</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"form-obfuscator\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\nobfuscator<span class=\"token punctuation\">.</span><span class=\"token function\">addEventListener</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"form-obfuscator:hide\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">e</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n  console<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Field obfuscated:\"</span><span class=\"token punctuation\">,</span> e<span class=\"token punctuation\">.</span>detail<span class=\"token punctuation\">.</span>field<span class=\"token punctuation\">.</span>value<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\nobfuscator<span class=\"token punctuation\">.</span><span class=\"token function\">addEventListener</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"form-obfuscator:reveal\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">e</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n  console<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Field revealed:\"</span><span class=\"token punctuation\">,</span> e<span class=\"token punctuation\">.</span>detail<span class=\"token punctuation\">.</span>field<span class=\"token punctuation\">.</span>value<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre>\n<p>You can access both the visible field and the hidden field through <code>event.detail.field</code> and <code>event.detail.hidden</code> respectively.</p>\n<h2 id=\"how-it-works\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#how-it-works\" aria-hidden=\"true\">#</a> How it works</h2>\n<p>The component creates a hidden <code>input</code> field to store the actual value for form submission. When the visible field loses focus, it:</p>\n<ol>\n<li>Copies the current value to the hidden field</li>\n<li>Applies your obfuscation rules to create the display value</li>\n<li>Updates the visible field with the obfuscated value</li>\n<li>Dispatches the <code>form-obfuscator:hide</code> event</li>\n</ol>\n<p>When the field gains focus, it:</p>\n<ol>\n<li>Restores the real value from the hidden field</li>\n<li>Updates the visible field</li>\n<li>Dispatches the <code>form-obfuscator:reveal</code> event</li>\n</ol>\n<p>The source order ensures the hidden field is the one that gets submitted with the form.</p>\n<h2 id=\"progressive-enhancement\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#progressive-enhancement\" aria-hidden=\"true\">#</a> Progressive enhancement</h2>\n<p>The component makes no assumptions about your markup—it works with any text-style <code>input</code> element. If JavaScript fails to load, the field behaves like a normal <code>input</code>, which is exactly what you want. Users can still enter and submit values; they just won’t get the obfuscation behavior.</p>\n<h2 id=\"demo\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#demo\" aria-hidden=\"true\">#</a> Demo</h2>\n<p>I’ve created <a href=\"https://aarongustafson.github.io/form-obfuscator/demo/\">a comprehensive demo page showing the various configuration options</a> over on GitHub:</p>\n<figure class=\"video-embed video-embed--4x3\">\n<fullscreen-control class=\"talk__slides__embed video-embed__video\">\n<iframe src=\"https://aarongustafson.github.io/form-obfuscator/demo/\" class=\"talk__slides__embed video-embed__video\" frameborder=\"0\"></iframe>\n</fullscreen-control>\n</figure>\n<h2 id=\"grab-it\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#grab-it\" aria-hidden=\"true\">#</a> Grab it</h2>\n<p>Check out the full project over on <a href=\"https://github.com/aarongustafson/form-obfuscator\">GitHub</a> or install via <code>npm</code>:</p>\n<pre class=\"language-bash\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"token function\">npm</span> <span class=\"token function\">install</span> @aarongustafson/form-obfuscator</code></pre>\n<p>Import and use:</p>\n<pre class=\"language-javascript\" tabindex=\"0\"><code class=\"language-javascript\"><span class=\"token keyword\">import</span> <span class=\"token string\">\"@aarongustafson/form-obfuscator\"</span><span class=\"token punctuation\">;</span></code></pre>\n<p>No dependencies, just a straightforward way to add field obfuscation to your forms.</p>\n","social_text":"Need to obfuscate form field values when they’re not being edited? Here’s a web component for that.","url":"https://www.aaron-gustafson.com/notebook/a-web-component-for-obfuscating-form-fields/","tags":["forms","HTML","JavaScript","progressive enhancement","web components","web forms","security"],"date_published":"2025-12-06T20:03:47Z"},{"id":"https://www.aaron-gustafson.com/notebook/a-web-component-for-conditionally-displaying-fields/","title":"✍🏻 A Web Component for Conditionally Displaying Fields","summary":"Sometimes you only want a field to show when certain other fields have a (particular) value. The <code>form-show-if</code> web component enables that.","content_html":"<p>Building on my recent work in the <a href=\"https://www.aaron-gustafson.com/notebook/series/forms/\">form utility space</a>, I’ve created a new web component that allows you to conditionally display form fields based on the values of other fields: <code>form-show-if</code>.</p>\n<p>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.</p>\n<p>You set up <code>form-show-if</code> like this:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>form-show-if</span> <span class=\"token attr-name\">conditions</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>contact_method=phone<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>label</span> <span class=\"token attr-name\">for</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>phone<span class=\"token punctuation\">\"</span></span>\n    <span class=\"token punctuation\">></span></span>Phone Number\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>input</span> <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>tel<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">id</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>phone<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">name</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>phone<span class=\"token punctuation\">\"</span></span> <span class=\"token punctuation\">/></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>label</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>form-show-if</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>You wrap any field and its <code>label</code> in the component and then declare the conditions under which it should be displayed in the <code>conditions</code> attribute.</p>\n<h2 id=\"defining-the-display-conditions\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#defining-the-display-conditions\" aria-hidden=\"true\">#</a> Defining the display conditions</h2>\n<p>Each condition is a key/value pair where the key aligns to the <code>name</code> 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 (<code>*</code>) 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.</p>\n<p>The <code>conditions</code> attribute can be populated with as many dependencies as you need. Multiple conditions are separated by double vertical pipes (<code>||</code>), as in this example:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>form-show-if</span> <span class=\"token attr-name\">conditions</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>contact_method=phone||contact_method=text_message<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>label</span> <span class=\"token attr-name\">for</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>phone-number<span class=\"token punctuation\">\"</span></span>\n    <span class=\"token punctuation\">></span></span>Phone Number\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>input</span> <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>tel<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">id</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>phone<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">name</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>phone<span class=\"token punctuation\">\"</span></span> <span class=\"token punctuation\">/></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>label</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>form-show-if</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>Here the field depends on one of the following conditions being true:</p>\n<ol>\n<li>the field matching <code>[name=&quot;contact_method&quot;]</code> has a value of “phone” <em>or</em></li>\n<li>the field matching <code>[name=&quot;contact_method&quot;]</code> has a value of “text_message”</li>\n</ol>\n<p>If the field you reference doesn’t exist, no errors will be thrown—it will just quietly exit.</p>\n<h2 id=\"customizing-the-show%2Fhide-behavior\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#customizing-the-show%2Fhide-behavior\" aria-hidden=\"true\">#</a> Customizing the show/hide behavior</h2>\n<p>By default, the component uses the <code>hidden</code> attribute to hide the wrapped content when it’s not needed. But you can customize this behavior using CSS classes instead:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>form-show-if</span>\n  <span class=\"token attr-name\">conditions</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>shipping-method=express<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">disabled-class</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>fade-out<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">enabled-class</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>fade-in<span class=\"token punctuation\">\"</span></span>\n<span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>label</span> <span class=\"token attr-name\">for</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>delivery-date<span class=\"token punctuation\">\"</span></span>\n    <span class=\"token punctuation\">></span></span>Express Delivery Date\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>input</span> <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>date<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">id</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>delivery-date<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">name</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>delivery-date<span class=\"token punctuation\">\"</span></span> <span class=\"token punctuation\">/></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>label</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>form-show-if</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>When using custom classes:</p>\n<ul>\n<li><strong><code>disabled-class</code></strong> is applied when the condition is not met (field should be hidden)</li>\n<li><strong><code>enabled-class</code></strong> is applied when the condition is met (field should be shown)</li>\n</ul>\n<p>Both are optional. Just remember that if you define a <code>disabled-class</code>, the <code>hidden</code> attribute will not be used — you will need to accessibly hide the content yourself.</p>\n<p>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.</p>\n<h2 id=\"handling-form-state-properly\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#handling-form-state-properly\" aria-hidden=\"true\">#</a> Handling form state properly</h2>\n<p>The component doesn’t just toggle visibility—it also manages the form state correctly. When fields are hidden, they’re automatically disabled using the <code>disabled</code> 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.</p>\n<p>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.</p>\n<h2 id=\"real-world-examples\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#real-world-examples\" aria-hidden=\"true\">#</a> Real-world examples</h2>\n<p>Here are some practical use cases where this component shines:</p>\n<p><strong>“Other” option handling:</strong></p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>fieldset</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>legend</span><span class=\"token punctuation\">></span></span>How did you hear about us?<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>legend</span><span class=\"token punctuation\">></span></span>\n\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>label</span><span class=\"token punctuation\">></span></span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>input</span> <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>radio<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">name</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>source<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">value</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>google<span class=\"token punctuation\">\"</span></span> <span class=\"token punctuation\">/></span></span> Google<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>label</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>label</span><span class=\"token punctuation\">></span></span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>input</span> <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>radio<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">name</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>source<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">value</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>friend<span class=\"token punctuation\">\"</span></span> <span class=\"token punctuation\">/></span></span> Friend<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>label</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>label</span><span class=\"token punctuation\">></span></span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>input</span> <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>radio<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">name</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>source<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">value</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>other<span class=\"token punctuation\">\"</span></span> <span class=\"token punctuation\">/></span></span> Other<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>label</span><span class=\"token punctuation\">></span></span>\n\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>form-show-if</span> <span class=\"token attr-name\">conditions</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>source=other<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>label</span> <span class=\"token attr-name\">for</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>source-other<span class=\"token punctuation\">\"</span></span>\n      <span class=\"token punctuation\">></span></span>Please specify\n      <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>input</span> <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>text<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">id</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>source-other<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">name</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>source-other<span class=\"token punctuation\">\"</span></span> <span class=\"token punctuation\">/></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>label</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>form-show-if</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>fieldset</span><span class=\"token punctuation\">></span></span></code></pre>\n<p><strong>Specific value matching:</strong></p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>form-show-if</span> <span class=\"token attr-name\">conditions</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>email=test@example.com<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>label</span> <span class=\"token attr-name\">for</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>debug-info<span class=\"token punctuation\">\"</span></span>\n    <span class=\"token punctuation\">></span></span>Debug Information\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>textarea</span> <span class=\"token attr-name\">id</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>debug-info<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">name</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>debug-info<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>textarea</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>label</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>small</span><span class=\"token punctuation\">></span></span>This field only shows for test accounts<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>small</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>form-show-if</span><span class=\"token punctuation\">></span></span></code></pre>\n<h2 id=\"progressive-enhancement-in-action\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#progressive-enhancement-in-action\" aria-hidden=\"true\">#</a> Progressive enhancement in action</h2>\n<p>Like all good web components, <code>form-show-if</code> 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.</p>\n<p>The component is lightweight, has no dependencies, and works in all modern browsers.</p>\n<h2 id=\"demo\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#demo\" aria-hidden=\"true\">#</a> Demo</h2>\n<p>I’ve put together <a href=\"https://aarongustafson.github.io/form-show-if/demo/\">a comprehensive demo showing various use cases and configurations</a> over on GitHub.</p>\n<p>The demo includes examples of:</p>\n<ul>\n<li>Basic show/hide functionality</li>\n<li>Multiple condition logic</li>\n<li>Custom CSS class integration</li>\n<li>Complex form scenarios with radio buttons and checkboxes</li>\n<li>Different field grouping approaches</li>\n</ul>\n<figure class=\"video-embed video-embed--4x3\">\n<fullscreen-control class=\"talk__slides__embed video-embed__video\">\n<iframe src=\"https://aarongustafson.github.io/form-show-if/demo/\" class=\"talk__slides__embed video-embed__video\" frameborder=\"0\"></iframe>\n</fullscreen-control>\n</figure>\n<h2 id=\"grab-it\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#grab-it\" aria-hidden=\"true\">#</a> Grab it</h2>\n<p>You can view the entire project (and suggest enhancements) over on <a href=\"https://github.com/aarongustafson/form-show-if\">the form-show-if component’s GitHub repo</a>. The component is available as both a standard script and an ES module, so you can integrate it however works best for your project.</p>\n<p>Installation is straightforward—just include the script in your page and start using the <code>form-show-if</code> element. No build step required, no framework dependencies, just clean, standards-based progressive enhancement.</p>\n","social_text":"Sometimes you only want a field to show when certain other fields have a (particular) value. The `form-show-if` web component enables that.","url":"https://www.aaron-gustafson.com/notebook/a-web-component-for-conditionally-displaying-fields/","tags":["web components","web forms","progressive enhancement","HTML"],"date_published":"2025-10-20T17:05:50Z"},{"id":"https://www.aaron-gustafson.com/publications/books/learning-web-design-sixth-edition/","title":"📗 Learning Web Design","content_html":"<p>Do you want to build web pages but have no prior experience? This friendly guide is the perfect place to start. You'll begin at square one, learning how the web and web pages work, and then steadily build from there. By the end of the book, you'll have the skills to create a simple site with multicolumn pages that adapt for mobile devices.</p>\n","url":"https://www.oreilly.com/library/view/learning-web-design/9781098137670/","tags":["accessibility","CSS","HTML","JavaScript","progressive enhancement","responsive web design","web design","web development","web standards"],"date_published":"2025-05-01T00:00:00Z"},{"id":"https://www.aaron-gustafson.com/notebook/a-web-component-for-conditional-dependent-fields/","title":"✍🏻 A Web Component for Conditional Dependent Fields","summary":"Sometimes you need to require a value in one form field only if another field is filled in or if that field has a specific value. I ported an old jQuery plugin of mine that made this work to a web component you can use easily right now.","content_html":"<p>A few weeks back I released <a href=\"https://www.aaron-gustafson.com/notebook/requirement-rules-for-checkboxes/\">a web component to enable you to add requirement rules to checkbox groups</a>. 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: <code>form-required-if</code>.</p>\n<p>The <code>form-required-if</code> web component, which is based on <a href=\"https://github.com/easy-designs/jquery.easy-dependent-required-fields.js\">a jQuery plugin I’d written in 2012</a>, looks like this:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>form-required-if</span> <span class=\"token attr-name\">conditions</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>email=*<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>label</span>\n    <span class=\"token punctuation\">></span></span>Required if there’s an email value\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>input</span> <span class=\"token attr-name\">name</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>depends-on-email<span class=\"token punctuation\">\"</span></span> <span class=\"token punctuation\">/></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>label</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>form-required-if</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>You wrap any field (and its <code>label</code>) in the component and then declare the conditions under which it should be required in the <code>conditions</code> attribute.</p>\n<h2 id=\"defining-the-requirement-conditions\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#defining-the-requirement-conditions\" aria-hidden=\"true\">#</a> Defining the requirement conditions</h2>\n<p>Each condition is a key/value pair where the key aligns to the <code>name</code> 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 <code>[name=&quot;email&quot;]</code>.</p>\n<p>This <code>conditions</code> 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 (<code>||</code> a.k.a. <em>or</em>) as in this example:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>form-required-if</span> <span class=\"token attr-name\">conditions</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>email=*||test=3<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>label</span>\n    <span class=\"token punctuation\">></span></span>Depends on email or test field\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>input</span> <span class=\"token attr-name\">name</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>depends-on-email-or-test<span class=\"token punctuation\">\"</span></span> <span class=\"token punctuation\">/></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>label</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>form-required-if</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>Here the field depends on one of the following conditions being true:</p>\n<ol>\n<li>the field matching <code>[name=&quot;email&quot;]</code> has a value, <em>or</em></li>\n<li>the field matching <code>[name=&quot;test&quot;]</code> has a value of “3”</li>\n</ol>\n<p>If the field you reference doesn’t exist, no errors will be thrown, it will just quietly exit.</p>\n<h2 id=\"visually-indicating-a-field-is-required\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#visually-indicating-a-field-is-required\" aria-hidden=\"true\">#</a> Visually indicating a field is required</h2>\n<p>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:</p>\n<ul>\n<li><code>indicator</code> - This attribute is where you define the indicator itself. It could be something as simple as a string (e.g., *), or even full-blown HTML.</li>\n<li><code>indicator-placement</code> - 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.</li>\n</ul>\n<p>Here’s an example with a custom indicator that is HTML:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>form-required-if</span> <span class=\"token attr-name\">conditions</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>email=*<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">indicator</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>&lt;b>*&lt;/b><span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>label</span>\n    <span class=\"token punctuation\">></span></span>Depends on email\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>input</span> <span class=\"token attr-name\">name</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>dep2<span class=\"token punctuation\">\"</span></span> <span class=\"token punctuation\">/></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>label</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>form-required-if</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>If you don’t include markup in your indicator, it will be automatically wrapped in <code>span</code> when injected into the DOM. The <code>hidden</code> and <code>aria-hidden</code> attributes are used to toggle its visibility, relative to the requirement state of the field.</p>\n<h2 id=\"demo\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#demo\" aria-hidden=\"true\">#</a> Demo</h2>\n<p>I put together <a href=\"https://aarongustafson.github.io/form-required-if/demo/\">a comprehensive demo of the web component</a> over on GitHub:</p>\n<figure class=\"video-embed video-embed--4x3\">\n<fullscreen-control class=\"talk__slides__embed video-embed__video\">\n<iframe src=\"https://aarongustafson.github.io/form-required-if/demo/\" class=\"talk__slides__embed video-embed__video\" frameborder=\"0\"></iframe>\n</fullscreen-control>\n</figure>\n<h2 id=\"grab-it\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#grab-it\" aria-hidden=\"true\">#</a> Grab It</h2>\n<p>You can view the entire project (and suggest enhancements) over on <a href=\"https://github.com/aarongustafson/form-required-if\">the <code>form-required-if</code> component’s Github repo</a>.</p>\n","social_text":"Sometimes you need to require a form field only if another field is filled in or has a specific value. Here’s a web component to enable that.","url":"https://www.aaron-gustafson.com/notebook/a-web-component-for-conditional-dependent-fields/","tags":["forms","HTML","JavaScript","progressive enhancement","web components","web forms"],"date_published":"2024-08-14T03:26:03Z"},{"id":"https://www.aaron-gustafson.com/notebook/links/don-t-use-js-for-that-moving-features-to-css-and-html-by-kilian-valkhof/","title":"🔗 Don&#39;t Use JS for That: Moving Features to CSS and HTML by Kilian Valkhof","content_html":"<p>This is a fantastic run-through of HTML and CSS features that help reduce our dependence on JavaScript (and improve #accessibility). Great work Kilian!</p>\n","social_text":"This is a fantastic run-through of #HTML and #CSS features that help reduce our dependence on #JavaScript (and improve #accessibility).","url":"https://www.aaron-gustafson.com/notebook/links/don-t-use-js-for-that-moving-features-to-css-and-html-by-kilian-valkhof/","external_url":"https://www.youtube.com/watch?v=IP_rtWEMR0o","tags":["HTML","CSS","JavaScript","progressive enhancement"],"date_published":"2024-07-29T17:22:19Z"},{"id":"https://www.aaron-gustafson.com/notebook/on-crowdstrike-dependencies-and-building-robust-products-on-the-web/","title":"✍🏻 On CrowdStrike, dependencies, and building robust products on the web","summary":"The CrowdStrike meltdown last week should be a cautionary tale for web designers and web developers.","content_html":"<p>I have no opinion on CrowdStrike as a company or service. I’ve never used their products. In fact, prior to <a href=\"https://wikipedia.org/wiki/2024_CrowdStrike_incident\">the incident last week</a>, 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 <a href=\"https://www.crowdstrike.com/wp-content/uploads/2024/07/CrowdStrike-PIR-Executive-Summary.pdf\">the cause of the incident</a>. In reflecting on this unfortunate incident, I can’t help but think of the lesson it holds for web designers and developers.</p>\n<p>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.</p>\n<p>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 <em>perfect</em> 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.</p>\n<p>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, <a href=\"https://www.smashingmagazine.com/2016/05/developing-dependency-awareness/\">develop dependency awareness</a>.</p>\n<p>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?</p>\n<p>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 <em>progressively enhance</em> 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.</p>\n<p>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.</p>\n<p>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?</p>\n<p><a href=\"https://www.youtube.com/watch?v=-9GGDOUDLhc&amp;start=7&amp;end=17\">https://www.youtube.com/watch?v=-9GGDOUDLhc&amp;start=7&amp;end=17</a></p>\n","social_text":"The #CrowdStrike meltdown last week should be a cautionary tale for #WebDesigners and #WebDevleopers.","url":"https://www.aaron-gustafson.com/notebook/on-crowdstrike-dependencies-and-building-robust-products-on-the-web/","tags":["progressive enhancement","hazards","CSS","JavaScript","web design","web development"],"date_published":"2024-07-25T17:20:26Z"},{"id":"https://www.aaron-gustafson.com/notebook/requirement-rules-for-checkboxes/","title":"✍🏻 Requirement Rules for Checkboxes","summary":"Currently, we can only make checkboxes required or not, individually. In some cases you need to be able to set a specific number of checkboxes that need to be checked. My <code>form-required-checkboxes</code> web component enables that.","content_html":"<p>HTML checkboxes debuted as <a href=\"https://datatracker.ietf.org/doc/html/rfc1866#section-8.1.2.3\">part of HTML 2.0 in 1995</a>. 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 <a href=\"https://github.com/easy-designs/easy-checkbox-required.js\">a jQuery plugin that allowed me to indicate that a user should choose a specific number of items from within a checkbox group</a>. Yesterday I turned that plugin into a web component: <a href=\"https://github.com/aarongustafson/form-required-checkboxes\"><code>form-required-checkboxes</code></a>.</p>\n<h2 id=\"markup-assumptions\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#markup-assumptions\" aria-hidden=\"true\">#</a> Markup Assumptions</h2>\n<p>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:</p>\n<ul>\n<li>Your checkbox group should be in a <code>fieldset</code> with a <code>legend</code></li>\n<li>All of the checkbox elements must have the same <code>name</code> (e.g., “foo[]”).</li>\n</ul>\n<p>In other words, they should look something like this:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>fieldset</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>legend</span><span class=\"token punctuation\">></span></span>Group 1 label<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>legend</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>ul</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>li</span><span class=\"token punctuation\">></span></span>\n      <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>label</span><span class=\"token punctuation\">></span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>input</span> <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>checkbox<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">name</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>foo[]<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">value</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>1<span class=\"token punctuation\">\"</span></span> <span class=\"token punctuation\">/></span></span>\n        First item\n      <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>label</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>li</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>li</span><span class=\"token punctuation\">></span></span>\n      <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>label</span><span class=\"token punctuation\">></span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>input</span> <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>checkbox<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">name</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>foo[]<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">value</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>2<span class=\"token punctuation\">\"</span></span> <span class=\"token punctuation\">/></span></span>\n        Second item\n      <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>label</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>li</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token comment\">&lt;!-- options continue --></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>ul</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>fieldset</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>To use the web component, you wrap the group in a <code>form-required-checkboxes</code> element and then include the JavaScript to initialize it.</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>form-required-checkboxes</span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>fieldset</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>legend</span><span class=\"token punctuation\">></span></span>Group 1 label<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>legend</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token comment\">&lt;!-- etc. --></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>fieldset</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>form-required-checkboxes</span><span class=\"token punctuation\">></span></span>\n\n<span class=\"token comment\">&lt;!-- at the end of your document --></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>script</span> <span class=\"token attr-name\">src</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>/js/web-components/form-required-checkboxes.js<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">async</span><span class=\"token punctuation\">></span></span><span class=\"token script\"></span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>script</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>If you’re following right along, there’s an error waiting for you in the <code>console</code> — we need to set the requirement rules.</p>\n<h2 id=\"the-api\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#the-api\" aria-hidden=\"true\">#</a> The API</h2>\n<p>The <code>form-required-checkboxes</code> element requires at least one attribute to function, but using some of the others you can more fully customize the experience for users:</p>\n<ul>\n<li><code>required</code> - Represents the range of required values. You can set this up in one of three ways depending on your needs:\n<ul>\n<li>Single number (e.g., 3) requires exactly that number of choices.</li>\n<li>Range (e.g., 3-5) requires a minimum of the first number and a max of the second number be chosen.</li>\n<li>Max (e.g., 0-3) requires a minimum of zero and a max of the second number to be chosen.</li>\n</ul>\n</li>\n<li><code>notice</code> (optional) - This is a string description that explains details of the required value in plain language. If you don’t supply one, the component will create one for you based on the current language (if supported). This description will be added as a <code>small</code> element within the component (as a sibling to the <code>fieldset</code>).</li>\n<li><code>error</code> (optional) - This is a string validation error you’d like to be shown when the validation criteria is not met. If not provided, an appropriate error message will be generated based on the current language (if supported).</li>\n<li><code>lang</code> (optional) - Language code for localized messages (e.g., “en,” “es,” “fr,” “de”). Falls back to the nearest ancestor’s <code>lang</code> attribute or the document language.</li>\n</ul>\n<h2 id=\"localization\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#localization\" aria-hidden=\"true\">#</a> Localization</h2>\n<p>The component now includes built-in translations for 16 languages: English, Chinese (Mandarin), Hindi, Spanish, French, Arabic, Bengali, Portuguese, Russian, Japanese, German, Punjabi, Javanese, Korean, Vietnamese, and Italian. Messages are automatically generated based on the <code>lang</code> attribute.</p>\n<p>You can use it like this:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>form-required-checkboxes</span> <span class=\"token attr-name\">required</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>3<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">lang</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>es<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>fieldset</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>legend</span><span class=\"token punctuation\">></span></span>Opciones<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>legend</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token comment\">&lt;!-- Will display: \"Elija 3 de la lista\" --></span>\n  <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>fieldset</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>form-required-checkboxes</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>The component automatically detects the language from the <code>lang</code> attribute on the element itself, the nearest ancestor element, or the document’s <code>lang</code> attribute, falling back to English if none is found.</p>\n<p>You can also register custom translations or override existing ones using the <code>FormRequiredCheckboxesElement.registerTranslations()</code> static method. Regional language codes (e.g., <code>en-US</code>, <code>es-MX</code>) automatically fall back to their base language.</p>\n<h2 id=\"demo\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#demo\" aria-hidden=\"true\">#</a> Demo</h2>\n<p>I put together <a href=\"https://aarongustafson.github.io/form-required-checkboxes/demo/\">a comprehensive demo of the web component</a> over on GitHub:</p>\n<figure class=\"video-embed video-embed--4x3\">\n<fullscreen-control class=\"talk__slides__embed video-embed__video\">\n<iframe src=\"https://aarongustafson.github.io/form-required-checkboxes/demo/\" class=\"talk__slides__embed video-embed__video\" frameborder=\"0\"></iframe>\n</fullscreen-control>\n</figure>\n<h2 id=\"grab-it\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#grab-it\" aria-hidden=\"true\">#</a> Grab It</h2>\n<p>You can view the entire project (and suggest enhancements) over on <a href=\"https://github.com/aarongustafson/form-required-checkboxes\">the component’s Github repo</a>.</p>\n","social_text":"Currently, we can only make checkboxes required or not, individually. In some cases you need to be able to set a specific number of checkboxes that need to be checked. My `form-required-checkboxes` web component enables that.","url":"https://www.aaron-gustafson.com/notebook/requirement-rules-for-checkboxes/","tags":["accessibility","forms","HTML","JavaScript","progressive enhancement","web components","web forms"],"date_published":"2024-07-05T21:08:34Z"},{"id":"https://www.aaron-gustafson.com/notebook/links/todo-app-with-no-client-side-javascript-using-lazarv-react-server/","title":"🔗 Todo app with no client-side JavaScript using @lazarv/react-server","content_html":"<p>I love straightforward examples, like this one, of how to build progressively enhanced experiences in frameworks like React.</p>\n<p>Step 2: Add in some HTML web components!</p>\n","social_text":"I love straightforward examples, like this one, of how to build progressively enhanced experiences in frameworks like React.","url":"https://www.aaron-gustafson.com/notebook/links/todo-app-with-no-client-side-javascript-using-lazarv-react-server/","external_url":"https://dev.to/lazarv/todo-app-with-no-client-side-javascript-using-lazarvreact-server-23ig","tags":["JavaScript","progressive enhancement"],"date_published":"2024-05-29T16:58:58Z"},{"id":"https://www.aaron-gustafson.com/notebook/links/an-even-faster-microsoft-edge/","title":"🔗 An even faster Microsoft Edge","content_html":"<p>Progressive enhancement for the win! This post from the Edge team demonstrates that producing markup directly rather than relying on JavaScript to do it for you is faster — even in the browser UI!</p>\n<blockquote>\n<p>In this project, we built an entirely new markup-first architecture that minimizes the size of our bundles of code, and the amount of JavaScript code that runs during the initialization path of the UI. This new internal UI architecture is more modular, and we now rely on a repository of web components that are tuned for performance on modern web engines.  We also came up with a set of web platform patterns that allow us to ship new browser features that stay within our markup-first architecture and that use optimal web platform capabilities.</p>\n</blockquote>\n","social_text":"Progressive enhancement for the win! This post from the Edge team demonstrates that producing markup directly rather than relying on JavaScript to do it for you is faster — even in the browser UI!","url":"https://www.aaron-gustafson.com/notebook/links/an-even-faster-microsoft-edge/","external_url":"https://blogs.windows.com/msedgedev/2024/05/28/an-even-faster-microsoft-edge/","tags":["progressive enhancement","browsers","HTML","CSS"],"date_published":"2024-05-29T16:32:54Z"}]}