<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/css" href="https://www.aaron-gustafson.com/c/feed.min.css" ?><feed xmlns="http://www.w3.org/2005/Atom"
      xmlns:amg="https://www.aaron-gustafson.com.com/amg-dtd/"><title>Aaron Gustafson: Latest Posts &amp; Links</title><subtitle>Hi there. My name is Aaron Gustafson and I work on the web.</subtitle><id>https://www.aaron-gustafson.com</id><link href="https://www.aaron-gustafson.com/feeds/all.xml" rel="self"/><link href="https://www.aaron-gustafson.com"/><author><name>Aaron Gustafson</name><uri>https://www.aaron-gustafson.com</uri></author><updated>2026-04-29T12:40:00Z</updated><entry><id>https://www.aaron-gustafson.com/notebook/links/ai-companies-will-fail-we-can-salvage-something-from-the-wreckage/</id><title type="html"><![CDATA[🔗 AI companies will fail. We can salvage something from the wreckage]]></title><link href="https://www.aaron-gustafson.com/notebook/links/ai-companies-will-fail-we-can-salvage-something-from-the-wreckage/" rel="alternate" type="text/html" /><link href="https://www.theguardian.com/us-news/ng-interactive/2026/jan/18/tech-ai-bubble-burst-reverse-centaur" rel="related" type="text/html" /><published>2026-04-29T12:40:00Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>Cory Doctorow offers a characteristically sharp critique of the AI bubble, but what I found most useful here is his framing of how these systems are actually meant to be deployed: not to empower workers, but to turn them into “reverse centaurs.”</p><p>That framing cuts through a lot of the marketing nonsense. The promise is always that AI will help people do their jobs better. The deployment story, far too often, is that a human gets stuck reviewing machine output at impossible speed while absorbing the blame when things go wrong. That’s not augmentation; it’s a liability dump.</p>]]></content><amg:twitter><![CDATA[This is a sharp and useful read from Cory Doctorow, especially his framing of workers being turned into ‘reverse centaurs’ and accountability sinks.]]></amg:twitter><amg:summary><![CDATA[Cory Doctorow offers a characteristically sharp critique of the AI bubble, but what I found most useful here is his framing of how these systems are actually meant to be deployed: not to empower workers, but to turn them into ‘reverse centaurs.’]]></amg:summary><summary type="html"><![CDATA[<p>Cory Doctorow offers a characteristically sharp critique of the AI bubble, but what I found most useful here is his framing of how these systems are actually meant to be deployed: not to empower workers, but to turn them into ‘reverse centaurs.’</p>]]></summary><category term="AI/ML" /><category term="society" /><category term="the future" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://i.guim.co.uk/img/media/3cab6e38d4ff57c0631ad4532131aa15878f8386/284_824_2216_1773/master/2216.jpg?width=1200&amp;height=630&amp;quality=85&amp;auto=format&amp;fit=crop&amp;precrop=40:21,offset-x50,offset-y0&amp;overlay-align=bottom%2Cleft&amp;overlay-width=100p&amp;overlay-base64=L2ltZy9zdGF0aWMvb3ZlcmxheXMvdGctZGVmYXVsdC5wbmc&amp;enable=upscale&amp;s=35399f4f1d76c88a6b1cc65f4fc711f5" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/links/accessible-faux-nested-interactive-controls/</id><title type="html"><![CDATA[🔗 Accessible faux-nested interactive controls]]></title><link href="https://www.aaron-gustafson.com/notebook/links/accessible-faux-nested-interactive-controls/" rel="alternate" type="text/html" /><link href="https://piccalil.li/blog/accessible-faux-nested-interactive-controls/" rel="related" type="text/html" /><published>2026-04-29T12:35:00Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<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><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>]]></content><amg:twitter><![CDATA[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.]]></amg:twitter><amg:summary><![CDATA[Eric Bailey walks through a clever pattern for getting the feel of nested interactive controls without actually violating the platform’s semantics.]]></amg:summary><summary type="html"><![CDATA[<p>Eric Bailey walks through a clever pattern for getting the feel of nested interactive controls without actually violating the platform’s semantics.</p>]]></summary><category term="accessibility" /><category term="CSS" /><category term="progressive enhancement" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://piccalil.b-cdn.net/api/og-image?slug=accessible-faux-nested-interactive-controls/" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/links/ai-assisted-coding-transforms-pdf-to-web-app-using-nys-design-system/</id><title type="html"><![CDATA[🔗 AI-assisted coding transforms PDF to web app using NYS Design System]]></title><link href="https://www.aaron-gustafson.com/notebook/links/ai-assisted-coding-transforms-pdf-to-web-app-using-nys-design-system/" rel="alternate" type="text/html" /><link href="https://www.linkedin.com/posts/plasticmind_designsystem-ai-civictech-share-7420295564430450688-0RM5/" rel="related" type="text/html" /><published>2026-04-29T12:30:00Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>This is a compelling illustration of what becomes possible when a design system is structured well enough to be usable by both people and machines.</p><p>What’s exciting to me here is not “AI turned a PDF into an app” so much as what made that possible: components, tokens, utilities, and enough design-system clarity to give the model something useful to work with. That’s a big part of the story so many people miss. If your system is inconsistent, vague, or poorly documented, AI is just going to amplify that mess.</p>]]></content><amg:twitter><![CDATA[A well-structured design system is not just a delivery mechanism for consistency; it’s also critical infrastructure for AI-assisted coding.]]></amg:twitter><amg:summary><![CDATA[This is a compelling illustration of what becomes possible when a design system is structured well enough to be usable by both people and machines.]]></amg:summary><summary type="html"><![CDATA[<p>This is a compelling illustration of what becomes possible when a design system is structured well enough to be usable by both people and machines.</p>]]></summary><category term="AI/ML" /><category term="pattern libraries" /><category term="web development" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://media.licdn.com/dms/image/v2/D5622AQFo6j6tbFv-DA/feedshare-shrink_800/B56ZvovfsWIcAo-/0/1769136324329?e=2147483647&amp;amp;v=beta&amp;amp;t=r42eIGZqMXi54JFEnnbEc0X6GBPXxDIBEq9mOos1Qm0" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/links/modern-css-feature-support-for-shadow-dom/</id><title type="html"><![CDATA[🔗 Modern CSS Feature Support For Shadow DOM]]></title><link href="https://www.aaron-gustafson.com/notebook/links/modern-css-feature-support-for-shadow-dom/" rel="alternate" type="text/html" /><link href="https://shadow-dom-css.adobe.com/" rel="related" type="text/html" /><published>2026-04-29T12:25:00Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>Adobe has produced a tremendously useful resource for anyone working with Shadow DOM and trying to understand where modern CSS features actually stand in practice, not just in theory.</p><p>What I especially like here is the framing: not simply “supported” or “unsupported,” but whether a feature is actually advisable in Shadow DOM, how it crosses boundaries, and where browsers still disagree. That’s the kind of nuance you need when you’re building real components instead of admiring specs from afar.</p>]]></content><amg:twitter><![CDATA[This is exactly the kind of resource the web platform needs more of: practical, test-backed guidance on how modern CSS features actually behave with Shadow DOM.]]></amg:twitter><amg:summary><![CDATA[This is a tremendously useful resource for anyone working with Shadow DOM and trying to understand where modern CSS features actually stand in practice, not just in theory.]]></amg:summary><summary type="html"><![CDATA[<p>This is a tremendously useful resource for anyone working with Shadow DOM and trying to understand where modern CSS features actually stand in practice, not just in theory.</p>]]></summary><category term="CSS" /><category term="web components" /><category term="developer tools" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/links/ai-is-locking-people-out-at-scale/</id><title type="html"><![CDATA[🔗 AI is locking people out. At Scale.]]></title><link href="https://www.aaron-gustafson.com/notebook/links/ai-is-locking-people-out-at-scale/" rel="alternate" type="text/html" /><link href="https://conesible.de/wab/" rel="related" type="text/html" /><published>2026-04-29T12:20:00Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>An important and sharply argued framing of AI-generated accessibility failures as a civil-rights problem, not just a quality-control issue.</p><blockquote><p>This is not a minor bug trend. It is a systematic civil-rights failure that has now found its way into software as a whole, through lightning-fast adoption of AI systems that are trained on over 20 years of institutional barriers.</p></blockquote><p>The numbers here are bad enough on their own, but what makes this especially troubling is where these systems are being deployed: education, healthcare, finance, employment, and other places people can’t simply opt out of. That’s why I appreciate how direct this project is about accountability. If we automate interface generation without putting accessibility at the center, we’re automating exclusion.</p>]]></content><amg:twitter><![CDATA[This is the right framing: inaccessible AI-generated interfaces are not a minor bug trend; they’re a civil-rights failure at scale.]]></amg:twitter><amg:summary><![CDATA[An important and sharply argued framing of AI-generated accessibility failures as a civil-rights problem, not just a quality-control issue.]]></amg:summary><summary type="html"><![CDATA[<p>An important and sharply argued framing of AI-generated accessibility failures as a civil-rights problem, not just a quality-control issue.</p>]]></summary><category term="accessibility" /><category term="AI/ML" /><category term="society" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/links/the-incredible-overcomplexity-of-the-shadcn-radio-button/</id><title type="html"><![CDATA[🔗 The Incredible Overcomplexity of the Shadcn Radio Button]]></title><link href="https://www.aaron-gustafson.com/notebook/links/the-incredible-overcomplexity-of-the-shadcn-radio-button/" rel="alternate" type="text/html" /><link href="https://paulmakeswebsites.com/writing/shadcn-radio-button/" rel="related" type="text/html" /><published>2026-04-29T12:15:00Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p><em>It’s just a radio button.</em></p><p>Paul’s teardown is a good illustration of how far modern front-end practice can drift from the simple, robust platform primitives we already have.</p><blockquote><p>If you can use a native HTML element or attribute with the semantics and behavior you require already built in, instead of re-purposing an element and adding an ARIA role, state or property to make it accessible, then do so.</p></blockquote><p>Reading this reminded me of a pair of pieces I’ve linked to before: <a href="/links/2015-09-01-making-radio-buttons-and-checkboxes-easier-to-use/">Making radio buttons and checkboxes easier to use</a> and <a href="/links/2022-12-11-there-can-be-only-one-options-for-building-choose-one-fields/">There can be only one: Options for building “choose one” fields</a>.</p><p>We have spent an astonishing amount of time and energy overcomplicating a control HTML already gives us, often in the name of developer experience or styleability. More often than not, all we’re really doing is trading resilience for ceremony.</p>]]></content><amg:twitter><![CDATA[It’s just a radio button. We really don’t need two component libraries, ARIA role remapping, and client-side JS to do what HTML already does.]]></amg:twitter><amg:summary><![CDATA[It’s just a radio button. Paul’s teardown is a good illustration of how far modern front-end practice can drift from the simple, robust platform primitives we already have.]]></amg:summary><summary type="html"><![CDATA[<p>It’s just a radio button. Paul’s teardown is a good illustration of how far modern front-end practice can drift from the simple, robust platform primitives we already have.</p>]]></summary><category term="HTML" /><category term="accessibility" /><category term="web development" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/links/accessibility-in-the-end-of-deterministic-design-again/</id><title type="html"><![CDATA[🔗 Accessibility in the End of Deterministic Design (Again)]]></title><link href="https://www.aaron-gustafson.com/notebook/links/accessibility-in-the-end-of-deterministic-design-again/" rel="alternate" type="text/html" /><link href="https://www.deque.com/axe-con/sessions/accessibility-in-the-end-of-deterministic-design-again/" rel="related" type="text/html" /><published>2026-04-29T12:10:00Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>The framing Anna Cook uses for her talk is an important one: accessibility isn’t something generative interfaces will magically solve; it’s the groundwork we need in order to make those systems trustworthy at all.</p><p>I also appreciate the callback in the title. We’ve been here before. The particulars may be new, but the core challenge is familiar: how do we build resilient, inclusive systems when the output is fluid, personalized, or otherwise beyond a designer’s exact control? Accessibility is not a bolt-on answer to that question; it’s where the answer has to begin.</p>]]></content><amg:twitter><![CDATA[Accessibility isn’t something generative interfaces will magically solve; it’s the groundwork we need in order to make them trustworthy at all.]]></amg:twitter><amg:summary><![CDATA[Accessibility isn’t something generative interfaces will magically solve; it’s the groundwork we need in order to make those systems trustworthy at all.]]></amg:summary><summary type="html"><![CDATA[<p>Accessibility isn’t something generative interfaces will magically solve; it’s the groundwork we need in order to make those systems trustworthy at all.</p>]]></summary><category term="accessibility" /><category term="AI/ML" /><category term="inclusive design" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/links/making-keyboard-navigation-effortless/</id><title type="html"><![CDATA[🔗 Making keyboard navigation effortless]]></title><link href="https://www.aaron-gustafson.com/notebook/links/making-keyboard-navigation-effortless/" rel="alternate" type="text/html" /><link href="https://blogs.windows.com/msedgedev/2026/03/05/making-keyboard-navigation-effortless/" rel="related" type="text/html" /><published>2026-04-29T12:05:00Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>I’m very excited by <code>focusgroup</code>: a declarative way to handle a bunch of keyboard-navigation patterns that currently require a lot of brittle JavaScript.</p><p>This is exactly the sort of thing I want to see the platform absorb. We’ve spent years rebuilding common interaction patterns with roving <code>tabindex</code>, focus management, and piles of event handling code. If the browser can take more of that on, we get lighter pages, more consistent experiences, and fewer opportunities for developers to get keyboard support wrong.</p>]]></content><amg:twitter><![CDATA[I’m very excited by `focusgroup`: less bespoke roving-`tabindex` code, more consistent keyboard UX, and a healthier platform overall.]]></amg:twitter><amg:summary><![CDATA[I’m very excited by <code>focusgroup</code>: a declarative way to handle a bunch of keyboard-navigation patterns that currently require a lot of brittle JavaScript.]]></amg:summary><summary type="html"><![CDATA[<p>I’m very excited by <code>focusgroup</code>: a declarative way to handle a bunch of keyboard-navigation patterns that currently require a lot of brittle JavaScript.</p>]]></summary><category term="accessibility" /><category term="HTML" /><category term="web standards" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/links/the-webaim-million-the-2026-report-on-the-accessibility-of-the-top-1000000-home-pages/</id><title type="html"><![CDATA[🔗 The WebAIM Million: The 2026 report on the accessibility of the top 1,000,000 home pages]]></title><link href="https://www.aaron-gustafson.com/notebook/links/the-webaim-million-the-2026-report-on-the-accessibility-of-the-top-1000000-home-pages/" rel="alternate" type="text/html" /><link href="https://webaim.org/projects/million/" rel="related" type="text/html" /><published>2026-04-29T12:00:00Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>The latest WebAIM Million is a sobering reminder that, at scale, the web is getting more complex faster than it is getting more accessible.</p><p>There are a lot of grim numbers in here, but the one that really sticks with me is how many of the same, eminently-fixable issues continue to dominate year after year. We’re still talking about contrast, missing labels, empty buttons, and missing alt text. In other words, this is not a story about edge cases. It’s a story about fundamentals.</p>]]></content><amg:twitter><![CDATA[The latest WebAIM Million is not encouraging: more complexity, more ARIA, and more detectable barriers across the top million home pages.]]></amg:twitter><amg:summary><![CDATA[The latest WebAIM Million is a sobering reminder that, at scale, the web is getting more complex faster than it is getting more accessible.]]></amg:summary><summary type="html"><![CDATA[<p>The latest WebAIM Million is a sobering reminder that, at scale, the web is getting more complex faster than it is getting more accessible.</p>]]></summary><category term="accessibility" /><category term="the web" /><category term="industry" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/links/under-the-hood-of-mdns-new-frontend/</id><title type="html"><![CDATA[🔗 Under the hood of MDN’s new frontend]]></title><link href="https://www.aaron-gustafson.com/notebook/links/under-the-hood-of-mdns-new-frontend/" rel="alternate" type="text/html" /><link href="https://developer.mozilla.org/en-US/blog/mdn-front-end-deep-dive/" rel="related" type="text/html" /><published>2026-04-24T12:05:00Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<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><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><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>]]></content><amg:twitter><![CDATA[This is a fascinating architecture write-up from MDN: fewer abstractions, less shipped JavaScript, and interactivity where it actually belongs.]]></amg:twitter><amg:summary><![CDATA[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.]]></amg:summary><summary type="html"><![CDATA[<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.</p>]]></summary><category term="web components" /><category term="web development" /><category term="progressive enhancement" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/links/endgame-for-the-open-web/</id><title type="html"><![CDATA[🔗 Endgame for the Open Web]]></title><link href="https://www.aaron-gustafson.com/notebook/links/endgame-for-the-open-web/" rel="alternate" type="text/html" /><link href="https://www.anildash.com/2026/03/27/endgame-open-web/" rel="related" type="text/html" /><published>2026-04-24T12:00:00Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>This is a sobering but necessary reminder that the open web’s current crisis is not theoretical; it’s already playing out across publishing, open source, standards, and shared infrastructure.</p><p>I appreciate how clearly Anil connects dots that too many people are still treating as separate problems. This isn’t just about AI summaries siphoning traffic from publishers or slop code drowning maintainers or bad actors ignoring long-standing norms like <code>robots.txt</code>. It’s all of those things at once. The through-line is extraction: taking value from open systems without giving anything back, then undermining the very ecosystems that made that extraction possible in the first place.</p><blockquote><p>The good of the web only exists because of the openness of the web. They can’t just keep on taking and taking without expecting people to finally draw a line and saying “enough”.</p></blockquote><p>If you care about the web as a public good, this post is worth your time.</p>]]></content><amg:twitter><![CDATA[This is a tough read, but an important one: the attacks on the open web are not abstract anymore.]]></amg:twitter><amg:summary><![CDATA[A sobering but necessary reminder that the open web’s current crisis is not theoretical; it’s already playing out across publishing, open source, standards, and shared infrastructure.]]></amg:summary><summary type="html"><![CDATA[<p>A sobering but necessary reminder that the open web’s current crisis is not theoretical; it’s already playing out across publishing, open source, standards, and shared infrastructure.</p>]]></summary><category term="the web" /><category term="AI/ML" /><category term="society" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.anildash.com/images/tunnel.jpg" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/links/slidevars/</id><title type="html"><![CDATA[🔗 slideVars]]></title><link href="https://www.aaron-gustafson.com/notebook/links/slidevars/" rel="alternate" type="text/html" /><link href="https://codepen.github.io/slideVars/" rel="related" type="text/html" /><published>2026-04-23T12:20:00Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>This is a lovely little utility from Chris Coyier that turns CSS custom properties into a live control panel.</p><p>It’s one of those tools that immediately makes me want to crack open a demo and start playing. What’s especially nice is that it doesn’t ask you to invent some parallel configuration universe if you don’t want to; it can just inspect the variables you already have and give you a UI for experimenting with them. That makes it feel less like a framework and more like a really practical enhancement to the way many of us already work.</p>]]></content><amg:twitter><![CDATA[slideVars feels like the kind of tiny, practical tool that makes design and front-end iteration a whole lot more fun.]]></amg:twitter><amg:summary><![CDATA[A lovely little utility from Chris Coyier that turns CSS custom properties into a live control panel.]]></amg:summary><summary type="html"><![CDATA[<p>A lovely little utility from Chris Coyier that turns CSS custom properties into a live control panel.</p>]]></summary><category term="CSS" /><category term="developer tools" /><category term="web development" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/links/ai-is-accidently-making-documentation-accessible/</id><title type="html"><![CDATA[🔗 AI is accidently making documentation accessible]]></title><link href="https://www.aaron-gustafson.com/notebook/links/ai-is-accidently-making-documentation-accessible/" rel="alternate" type="text/html" /><link href="https://gerireid.com/blog/ai-is-accidently-making-documentation-accessible/" rel="related" type="text/html" /><published>2026-04-23T12:15:00Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>The real takeaway is that documentation written for clarity is better for everyone — human or machine!</p><p>In other words, AI use is reinforcing what good documentation has always required: clear structure, explicit language, sensible headings, and fewer assumptions about what the reader already knows. That’s good for machines, sure, but it’s even better for people navigating with assistive tech, reading in a second language, or simply trying to get an answer quickly.</p><blockquote><p>AI has not invented new rules for writing, it has made the cost of ignoring the old ones obvious.</p></blockquote>]]></content><amg:twitter><![CDATA[AI didn’t invent better documentation practices; it just exposed how valuable structure, clarity, and explicitness have always been.]]></amg:twitter><amg:summary><![CDATA[Documentation written for clarity is better for everyone — human or machine!]]></amg:summary><summary type="html"><![CDATA[<p>Documentation written for clarity is better for everyone — human or machine!</p>]]></summary><category term="accessibility" /><category term="AI/ML" /><category term="writing" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://gerireid.com/images/open-graph.png" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/links/design-systems-cant-automate-away-all-of-your-accessibility-considerations/</id><title type="html"><![CDATA[🔗 Design systems can’t automate away all of your accessibility considerations]]></title><link href="https://www.aaron-gustafson.com/notebook/links/design-systems-cant-automate-away-all-of-your-accessibility-considerations/" rel="alternate" type="text/html" /><link href="https://zeroheight.com/blog/design-systems-cant-automate-away-all-of-your-accessibility-considerations/" rel="related" type="text/html" /><published>2026-04-23T12:10:00Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>Eric does an excellent job outlining the kinds of accessibility issues design systems and automated tooling can reduce, but never fully eliminate.</p><p>This is the part too many teams miss: accessibility doesn’t end at the component boundary. A well-built component library can give you a stronger foundation, but it can’t guarantee the right labels, the right heading structure, the right focus management, or the right overall experience once those pieces are assembled into an interface. That work still requires judgment, care, and testing with actual people.</p>]]></content><amg:twitter><![CDATA[Passing automated checks is not the same thing as delivering an accessible experience.]]></amg:twitter><amg:summary><![CDATA[Eric does an excellent job outlining the kinds of accessibility issues design systems and automated tooling can reduce, but never fully eliminate.]]></amg:summary><summary type="html"><![CDATA[<p>Eric does an excellent job outlining the kinds of accessibility issues design systems and automated tooling can reduce, but never fully eliminate.</p>]]></summary><category term="accessibility" /><category term="inclusive design" /><category term="pattern libraries" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://zh-marketing-wordpress-uploads.s3.amazonaws.com/wp-content/uploads/2025/11/blog-feature-computer-yellow.png" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/links/the-power-of-no-in-internet-standards/</id><title type="html"><![CDATA[🔗 The Power of ‘No’ in Internet Standards]]></title><link href="https://www.aaron-gustafson.com/notebook/links/the-power-of-no-in-internet-standards/" rel="alternate" type="text/html" /><link href="https://www.mnot.net/blog/2026/02/13/no" rel="related" type="text/html" /><published>2026-04-23T12:05:00Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>This is an important piece about where power actually lives on the web: not in the specification itself, but in whether powerful players choose to participate, implement, and ship.</p><p>I also appreciate Mark’s call for more ambition here. Too often, we talk about the web as if it’s destined to be an OS for web apps rather than a public-interest platform in its own right. That’s not inevitable. But getting somewhere better requires recognizing that refusal, delay, and strategic disinterest can be every bit as consequential as formal opposition.</p>]]></content><amg:twitter><![CDATA[A useful reminder that the real power in standards work is often the power to say no.]]></amg:twitter><amg:summary><![CDATA[This is an important piece about where power actually lives on the web: not in the specification itself, but in whether powerful players choose to participate, implement, and ship.]]></amg:summary><summary type="html"><![CDATA[<p>This is an important piece about where power actually lives on the web: not in the specification itself, but in whether powerful players choose to participate, implement, and ship.</p>]]></summary><category term="web standards" /><category term="the web" /><category term="society" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.mnot.net/blog/image/wait_here.jpeg" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/links/nice-select/</id><title type="html"><![CDATA[🔗 Nice Select]]></title><link href="https://www.aaron-gustafson.com/notebook/links/nice-select/" rel="alternate" type="text/html" /><link href="https://nerdy.dev/nice-select" rel="related" type="text/html" /><published>2026-04-23T12:00:00Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<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><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>]]></content><amg:twitter><![CDATA[This is exactly the sort of UI work I love seeing: ambitious, polished, and still rooted in native controls and progressive enhancement.]]></amg:twitter><amg:summary><![CDATA[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.]]></amg:summary><summary type="html"><![CDATA[<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>]]></summary><category term="CSS" /><category term="HTML" /><category term="progressive enhancement" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://nerdy.dev/media/nice-selects.jpg" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/visual-validation-feedback-for-form-fields/</id><title type="html"><![CDATA[✍🏻 Visual Validation Feedback for Form Fields]]></title><link href="https://www.aaron-gustafson.com/notebook/visual-validation-feedback-for-form-fields/" rel="alternate" type="text/html" /><published>2026-04-22T20:17:47Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<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><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><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><hr><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><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">&gt;</span></span><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">&gt;</span></span>Username:<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">&gt;</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>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">/&gt;</span></span><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">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ul</span><span class="token punctuation">&gt;</span></span><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">&gt;</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">&gt;</span></span><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">&gt;</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">&gt;</span></span><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">&gt;</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">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>form-validation-list</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span><span class="token attr-name">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">&gt;</span></span>Submit<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>form</span><span class="token punctuation">&gt;</span></span></code></pre><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><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><p>The component:</p><ol><li>Associates with an input via the <code>for</code> attribute (just like a <code>label</code> element)</li><li>Finds all elements with <code>data-pattern</code> attributes</li><li>Tests the input value against each pattern when the configured trigger fires</li><li>Adds <code>validation-matched</code> or <code>validation-unmatched</code> classes and visual indicators accordingly</li><li>Inserts localized, visually hidden state text once the field has a value</li><li>Updates a single polite live region while users type</li><li>Uses <code>setCustomValidity()</code> to integrate with native form validation</li><li>Prevents form submission until all rules match</li></ol><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><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><p>Define rules using regular expression patterns in the <code>data-pattern</code> attribute:</p><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">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ul</span><span class="token punctuation">&gt;</span></span><span class="token comment">&lt;!-- Length requirements --&gt;</span><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">&gt;</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">&gt;</span></span><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">&gt;</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">&gt;</span></span><span class="token comment">&lt;!-- Character type requirements --&gt;</span><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">&gt;</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">&gt;</span></span><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">&gt;</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">&gt;</span></span><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">&gt;</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">&gt;</span></span><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;<em>]+<span class="token punctuation">“</span></span><span class="token punctuation">&gt;</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">&gt;</span></span><span class="token comment">&lt;!-- Format requirements --&gt;</span><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">&gt;</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">&gt;</span></span><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><sup class="footnote-ref"><a href="#fn1" id="fnref1">1</a></sup>+$<span class="token punctuation">“</span></span><span class="token punctuation">&gt;</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">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>form-validation-list</span><span class="token punctuation">&gt;</span></span></code></pre><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><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><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><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">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ul</span><span class="token punctuation">&gt;</span></span><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">&gt;</span></span>Contains @ symbol<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">&gt;</span></span><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">&gt;</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">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>form-validation-list</span><span class="token punctuation">&gt;</span></span></code></pre><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><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><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><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">&gt;</span></span><span class="token comment">&lt;!-- rules --&gt;</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>form-validation-list</span><span class="token punctuation">&gt;</span></span></code></pre><p>Set it to “0” to remove the cascade effect entirely and check all rules simultaneously.</p><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><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><p>Here’s a complete example:</p><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">&gt;</span></span><span class="token style"><span class="token language-css"><span class="token selector">.is-valid</span><span class="token punctuation">{</span><span class="token property">border-color</span><span class="token punctuation">:</span> green<span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token selector">.is-invalid</span><span class="token punctuation">{</span><span class="token property">border-color</span><span class="token punctuation">:</span> red<span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token selector">.rule-pass</span><span class="token punctuation">{</span><span class="token property">color</span><span class="token punctuation">:</span> green<span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token selector">.rule-fail</span><span class="token punctuation">{</span><span class="token property">color</span><span class="token punctuation">:</span> red<span class="token punctuation">;</span><span class="token punctuation">}</span></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">&gt;</span></span><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 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><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><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><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><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ul</span><span class="token punctuation">&gt;</span></span><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">&gt;</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">&gt;</span></span><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">&quot;</span></span><span class="token punctuation">&gt;</span></span>Special char (!@#)<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>form-validation-list</span><span class="token punctuation">&gt;</span></span></code></pre><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><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><ul><li><code>–rule-matched-icon</code> - Content for matched state (default: “✓”)</li><li><code>–rule-unmatched-icon</code> - Content for unmatched state (default: “✗”)</li><li><code>–rule-icon-size</code> - Size of icons (default: 1em)</li><li><code>–rule-matched-color</code> - Color for matched rules (default: green)</li><li><code>–rule-unmatched-color</code> - Color for unmatched rules (default: red)</li></ul><p>The older <code>–validation-</em></code> custom property names are still supported as legacy aliases.</p><p>Here’s an example of that:</p><pre class="language-css" tabindex="0"><code class="language-css"><span class="token selector">form-validation-list</span><span class="token punctuation">{</span><span class="token property">–rule-matched-icon</span><span class="token punctuation">:</span><span class="token string">”✅“</span><span class="token punctuation">;</span><span class="token property">–rule-unmatched-icon</span><span class="token punctuation">:</span><span class="token string">”❌“</span><span class="token punctuation">;</span><span class="token property">–rule-icon-size</span><span class="token punctuation">:</span> 1.2em<span class="token punctuation">;</span><span class="token property">–rule-matched-color</span><span class="token punctuation">:</span> #28a745<span class="token punctuation">;</span><span class="token property">–rule-unmatched-color</span><span class="token punctuation">:</span> #dc3545<span class="token punctuation">;</span><span class="token punctuation">}</span></code></pre><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><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><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><p>If you really want to get into the weeds, you can also listen for validation changes in your JavaScript code:</p><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>validationList<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">=&gt;</span><span class="token punctuation">{</span><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>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"><code>&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Matched &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;matchedRules&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; of &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;totalRules&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; rules&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;</code></span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>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"><code>&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Field is &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;isValid &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;valid&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;invalid&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;</code></span></span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p>The event fires after validation completes and gives you the current state. Nice and tidy.</p><p>You can also manually trigger validation and check the element’s current state at any time:</p><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><span class="token comment">// Trigger validation</span><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>console<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><span class="token comment">// Check current state</span>console<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><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><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><pre class="language-html" tabindex="0"><code class="language-html"><span class="token comment">&lt;!-- Spanish --&gt;</span><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>contrasena<span class="token punctuation">“</span></span><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><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><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><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><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ul</span><span class="token punctuation">&gt;</span></span><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">&gt;</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">&gt;</span></span><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">&gt;</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">&gt;</span></span><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">&gt;</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">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>form-validation-list</span><span class="token punctuation">&gt;</span></span><span class="token comment">&lt;!-- French --&gt;</span><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>mot-de-passe<span class="token punctuation">“</span></span><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><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><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><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><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ul</span><span class="token punctuation">&gt;</span></span><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">&gt;</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">&gt;</span></span><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">&gt;</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">&gt;</span></span><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">&gt;</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">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>form-validation-list</span><span class="token punctuation">&gt;</span></span></code></pre><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><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><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><p>The component is built with accessibility in mind:</p><ul><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><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><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><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></ul><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><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><p>The component uses <code>setCustomValidity()</code> to participate in native form validation:</p><ul><li>When all rules match, custom validity is cleared</li><li>When rules don’t match, a custom validity message is set</li><li>Form submission is prevented until all rules pass</li><li>Works with <code>:valid</code> and <code>:invalid</code> CSS pseudo-classes</li><li>Compatible with the Constraint Validation API</li></ul><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><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>form<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">=&gt;</span><span class="token punctuation">{</span><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>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>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><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><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><p>Here’s a complete password validation setup:</p><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">&gt;</span></span><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">&gt;</span></span>Password:<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">&gt;</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>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">/&gt;</span></span><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">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ul</span><span class="token punctuation">&gt;</span></span><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">&gt;</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">&gt;</span></span><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">&gt;</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">&gt;</span></span><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">&gt;</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">&gt;</span></span><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">&gt;</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">&gt;</span></span><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;<em>]+<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>At least one special character (!@#$%^&amp;</em>)<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>form-validation-list</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span><span class="token attr-name">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">&gt;</span></span>Submit<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>form</span><span class="token punctuation">&gt;</span></span></code></pre><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><h2 id="play-with-it" tabindex="-1"><a class="header-anchor" href="#play-with-it" aria-hidden="true">#</a> Play with it</h2><p>Check out <a href="https://aarongustafson.github.io/form-validation-list/demo/">the demo</a> with various examples:</p><figure id="fig-2025-12-06-09" class="media-container"><fullscreen-control class="talk__slides__embed video-embed__video"><iframe src="https://aarongustafson.github.io/form-validation-list/demo/" class="talk__slides__embed video-embed__video" frameborder="0"></iframe></fullscreen-control></figure><h2 id="grab-it" tabindex="-1"><a class="header-anchor" href="#grab-it" aria-hidden="true">#</a> Grab it</h2><p>View the project on <a href="https://github.com/aarongustafson/form-validation-list">GitHub</a>.</p><p>Install via <code>npm</code>:</p><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><p>For most projects, import the guarded auto-definition helper:</p><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><p>If you want to control the tag name yourself, import <code>FormValidationListElement</code> and register it manually.</p><p>Happy validating!</p><hr class="footnotes-sep"><section class="footnotes"><h4 class="hidden">Footnotes</h4><ol class="footnotes-list"><li id="fn1" class="footnote-item"><p>a-zA-Z0-9 <a href="#fnref1" class="footnote-backref">↩︎</a></p></li></ol></section>]]></content><amg:twitter><![CDATA[New #WebComponent: Show users which validation requirements they’ve met — as they type.]]></amg:twitter><amg:summary><![CDATA[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.]]></amg:summary><summary type="html"><![CDATA[<p>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.</p>]]></summary><category term="web components" /><category term="progressive enhancement" /><category term="forms" /><category term="HTML" /><category term="JavaScript" /><category term="web forms" /><category term="user experience" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/never-lose-form-progress-again/</id><title type="html"><![CDATA[✍🏻 Never Lose Form Progress Again]]></title><link href="https://www.aaron-gustafson.com/notebook/never-lose-form-progress-again/" rel="alternate" type="text/html" /><published>2026-04-20T23:59:08Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<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><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><h2 id="basic-usage" tabindex="-1"><a class="header-anchor" href="#basic-usage" aria-hidden="true">#</a> Basic usage</h2><p>All you need to do is wrap your form in the component:</p><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">&gt;</span></span><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">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span><span class="token punctuation">&gt;</span></span>Name<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">/&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span><span class="token punctuation">&gt;</span></span>Email<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">/&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span><span class="token punctuation">&gt;</span></span>Message<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">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>textarea</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span><span class="token attr-name">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">&gt;</span></span>Send<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>form</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>form-saver</span><span class="token punctuation">&gt;</span></span></code></pre><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><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><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><p><code>form-saver</code> supports the controls most of us reach for every day:</p><ul><li>Text-style <code>input</code> fields</li><li><code>textarea</code> elements,</li><li><code>select</code> elements (including multi-selects), and</li><li><code>checkbox</code> and <code>radio</code> controls.</li></ul><p>File inputs are intentionally excluded.</p><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><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><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><p>That is what the <code>retain</code> attribute is for:</p><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">&gt;</span></span><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">&gt;</span></span>…<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>form</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>form-saver</span><span class="token punctuation">&gt;</span></span></code></pre><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><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><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><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><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 attr-name">retain-choice</span><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><span class="token punctuation">&gt;</span></span><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">&gt;</span></span>…<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>form</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>form-saver</span><span class="token punctuation">&gt;</span></span></code></pre><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><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><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 attr-name">retain-choice</span><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><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><span class="token punctuation">&gt;</span></span><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">&gt;</span></span>…<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">“</span>form-footer<span class="token punctuation">”</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>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">&gt;</span></span>Send<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>form</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>form-saver</span><span class="token punctuation">&gt;</span></span></code></pre><p>That gives you a lot more control over the experience without making you build the retention UI yourself.</p><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><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><p>If you need something more explicit, you can provide your own <code>storage-key</code>:</p><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">&gt;</span></span><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">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span><span class="token punctuation">&gt;</span></span>Street Address<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">/&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span><span class="token punctuation">&gt;</span></span>Postal Code<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">/&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span><span class="token attr-name">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">&gt;</span></span>Continue<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>form</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>form-saver</span><span class="token punctuation">&gt;</span></span></code></pre><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><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><p>If you need more direct control, the component exposes a few methods:</p><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><span class="token comment">// Persist the current state</span>saver<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><span class="token comment">// Restore previously saved values</span>saver<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><span class="token comment">// Clear out anything stored for this form</span>saver<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><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><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><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><p>That’s a pretty good trade-off.</p><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><h2 id="demo" tabindex="-1"><a class="header-anchor" href="#demo" aria-hidden="true">#</a> Demo</h2><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><figure id="fig-2026-04-20-01" class="media-container"><fullscreen-control class="talk__slides__embed video-embed__video"><iframe src="https://aarongustafson.github.io/form-saver/demo/" class="talk__slides__embed video-embed__video" frameborder="0"></iframe></fullscreen-control></figure><h2 id="grab-it" tabindex="-1"><a class="header-anchor" href="#grab-it" aria-hidden="true">#</a> Grab it</h2><p>The project is available on <a href="https://github.com/aarongustafson/form-saver">GitHub</a>, and you can install it from npm:</p><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><p>If you want the easiest path, just import it and let the component register itself:</p><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><p>If you would rather define it yourself, you can import the class directly:</p><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>customElements<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><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>]]></content><amg:twitter><![CDATA[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.]]></amg:twitter><amg:summary><![CDATA[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.]]></amg:summary><summary type="html"><![CDATA[<p>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.</p>]]></summary><category term="web components" /><category term="forms" /><category term="HTML" /><category term="JavaScript" /><category term="progressive enhancement" /><category term="web forms" /><category term="user experience" /></entry><entry><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-/</id><title type="html"><![CDATA[🔗 Different contexts, different tools, same person]]></title><link href="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-/" rel="alternate" type="text/html" /><link href="https://www.linkedin.com/posts/derekfeatherstone_accessibility-disability-activity-7434648295420870656-mH3o" rel="related" type="text/html" /><published>2026-03-05T18:38:46Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<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><blockquote><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><p>Different contexts, different tools, same person.</p><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><p>Different energy/capacity, same tools, same person.</p><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><p>Different task, same context, same tools, same person.</p><p>Disability is not black and white… it’s every shade of every colour.</p></blockquote>]]></content><amg:twitter><![CDATA[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.]]></amg:twitter><amg:summary><![CDATA[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.]]></amg:summary><summary type="html"><![CDATA[<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>]]></summary><category term="accessibility" /><category term="progressive enhancement" /><category term="inclusive design" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://static.licdn.com/aero-v1/sc/h/c45fy346jw096z9pbphyyhdz7" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/accessibility-assistant-for-figma-v52/</id><title type="html"><![CDATA[✍🏻 Accessibility Assistant for Figma v52]]></title><link href="https://www.aaron-gustafson.com/notebook/accessibility-assistant-for-figma-v52/" rel="alternate" type="text/html" /><published>2026-02-20T23:27:28Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>I just hit “publish” on <a href="https://www.figma.com/community/plugin/731310036968334777/accessibility-assistant">Accessibility Assistant for Figma</a> v52 and I wanted to share some details on why this is a monumental release for us.</p><p>We’re in the process of a major overhaul to this plugin. There was a lot of infrastructural work to do to modernize the plugin and set the stage for a host of new features to make designers more productive when it comes to making their designs more accessible. This release incorporates a lot of that foundational work, notably:</p><ul><li>Annotations are now presented as Figma-native Dev Mode annotations; this greatly reduces the working overhead of the plugin and reduces visual clutter in the document. We’ve also color-coordinated the icons in the Annotation Set viewer to the labels you see in the Dev Mode annotations, making it easier to scan.</li><li>Legacy annotation tables will automatically be migrated into the new system. The visual readout tables will be hidden when this happens, but are still accessible if you need to copy or reference them. We’ve also included a tool to clean up these old layers when you’re ready.</li><li>Annotations are now managed in a single UI rather than being separated, based on whether they impact focus order. This means you don’t need to jump back &amp; forth between tools to properly annotate your designs.</li><li>We’ve organized and expanded the list of W3C roles available in the role picker. Additionally, the form now adapts to the role, offering you only the relevant fields and reducing distraction. We also added a description field, should you need it.</li></ul><p>We also fixed bugs related to duplicating layers. You can now copy layers and the annotations will go along for the ride, becoming a new Annotation Set. Similarly, you can now duplicate pages and the annotations — which are page-bound — will be re-generated. It’s worth noting that this may take some time on particularly large pages.</p><p>This release has been a long time coming, but I’m incredibly proud of the team that’s been working so diligently on this, particularly Ashish Singh from HCL and Michael Fairchild, Scott O’Hara, and Ben Truelove from Microsoft. Their attention to detail and encyclopedic knowledge of accessibility has been instrumental in getting this project to the place that it is.</p><p>And there’s more to come!</p>]]></content><amg:twitter><![CDATA[I just hit “publish” on Accessibility Assistant for Figma v52 and I wanted to share some details on why this is a monumental release for us.]]></amg:twitter><amg:summary><![CDATA[I just hit “publish” on Accessibility Assistant for Figma v52 and I wanted to share some details on why this is a monumental release for us.]]></amg:summary><summary type="html"><![CDATA[<p>I just hit “publish” on Accessibility Assistant for Figma v52 and I wanted to share some details on why this is a monumental release for us.</p>]]></summary><category term="accessibility" /><category term="design" /><category term="inclusive design" /><category term="Microsoft" /><category term="user experience" /><category term="WAI-ARIA" /></entry></feed>