<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: FrozonFreak</title>
    <description>The latest articles on DEV Community by FrozonFreak (@frozonfreak).</description>
    <link>https://dev.to/frozonfreak</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4119175%2Fb0030d26-db2b-45c8-aaa9-c4ac40bdad48.png</url>
      <title>DEV Community: FrozonFreak</title>
      <link>https://dev.to/frozonfreak</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/frozonfreak"/>
    <language>en</language>
    <item>
      <title>Declare your color schemes before the first paint (Web HIG tip #17)</title>
      <dc:creator>FrozonFreak</dc:creator>
      <pubDate>Fri, 13 Nov 2026 06:11:42 +0000</pubDate>
      <link>https://dev.to/frozonfreak/declare-your-color-schemes-before-the-first-paint-web-hig-tip-17-42bl</link>
      <guid>https://dev.to/frozonfreak/declare-your-color-schemes-before-the-first-paint-web-hig-tip-17-42bl</guid>
      <description>&lt;p&gt;You open example.com on a phone that prefers dark mode. The page was authored as light, with no &lt;code&gt;color-scheme&lt;/code&gt; in the head or the first CSS. In the Safari 27.3 beta, with Adaptive Dark Mode turned on, Safari can repaint that light page as dark: colors and light gradients change, some SVG images and simple logos recolor, and some masks or blend effects can be turned off. Scripts that read the page's colors still get the authored values. The page never told the browser which schemes it supports.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Web HIG tip #17 (Quick #64):&lt;/strong&gt; Dark mode must re-verify contrast for every semantic token combination used. Declare supported color schemes in the document head or the initial CSS, before JavaScript. &lt;code&gt;light dark&lt;/code&gt; is the usual declaration when both schemes are supported. &lt;code&gt;light only&lt;/code&gt; is a narrow opt-out, not the default.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it matters
&lt;/h2&gt;

&lt;p&gt;A missing declaration leaves the browser free to invent an appearance you did not author:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Safari Adaptive Dark Mode may transform a light page that does not declare schemes. Canvas stays as authored; CSS colors, gradients, and some images may not&lt;/li&gt;
&lt;li&gt;Script and computed styles keep the authored values, so the repaint is invisible to ordinary CSS checks and many automated contrast tools&lt;/li&gt;
&lt;li&gt;Text, focus, hover, SVG logos, transparent images, and canvas can look wrong while a lab check still reports the authored contrast&lt;/li&gt;
&lt;li&gt;A declaration added by JavaScript later still takes effect, but the first render may already have used the browser's default light canvas and controls. That is the flash you're trying to avoid. WebKit advises against waiting to inject it with script&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full HIG covers this in &lt;a href="https://github.com/frozonfreak/webhig/blob/1ab8c28fdf2e1cf9a5a4e07e67afbea20a4bab70/HIG.md#supported-color-schemes" rel="noopener noreferrer"&gt;§3.1, Supported color schemes&lt;/a&gt;: pages that support light and dark appearances MUST declare those schemes in the initial document, before any JavaScript runs. In practice, that means the browser knows the schemes before it first renders the page. &lt;a href="https://github.com/frozonfreak/webhig/blob/1ab8c28fdf2e1cf9a5a4e07e67afbea20a4bab70/HIG-QUICK.md" rel="noopener noreferrer"&gt;Quick #64&lt;/a&gt; is Quick-only guidance (no rule ID of its own). The same section states that Safari's contrast aim after a transformation is not a substitute for checking the authored interface in each supported scheme, including focus, hover, and non-text UI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before and after
&lt;/h2&gt;

&lt;p&gt;This page has light and dark styles in &lt;code&gt;/styles.css&lt;/code&gt; but never declares its schemes. The browser keeps its own canvas, form controls and scrollbars on light defaults, and Safari's Adaptive Dark Mode has to analyse the page instead of being told:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Orders — example.com&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/styles.css"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Orders&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Declare both schemes up front, in markup, in first-paint CSS, or both:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"color-scheme"&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"light dark"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Orders — example.com&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nt"&gt;root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;color-scheme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;light&lt;/span&gt; &lt;span class="n"&gt;dark&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/styles.css"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Orders&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;light dark&lt;/code&gt; tells supporting browsers, including Safari Adaptive Dark Mode, that both appearances are intentional. Only declare &lt;code&gt;dark&lt;/code&gt; when you ship an authored dark appearance (&lt;code&gt;prefers-color-scheme&lt;/code&gt;, &lt;code&gt;light-dark()&lt;/code&gt; or an explicit theme). Otherwise the browser's dark canvas, form controls and system colors can clash with colors you authored for light. Then re-check contrast in that appearance. This snippet does not ship a theme switcher, token files, or a full dark palette.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do this instead
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Put &lt;code&gt;&amp;lt;meta name="color-scheme" content="light dark"&amp;gt;&lt;/code&gt; in the document head, and/or &lt;code&gt;color-scheme: light dark&lt;/code&gt; on &lt;code&gt;:root&lt;/code&gt; (WebKit also accepts &lt;code&gt;body&lt;/code&gt;) in CSS that applies on first paint, such as a &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; in the head, so the browser has it before the first render and before any theme script runs. Only the root element's scheme sets the page canvas, so prefer &lt;code&gt;:root&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;light dark&lt;/code&gt; when you support both. Use &lt;code&gt;light only&lt;/code&gt; (and the matching meta value) only when a surface must stay light; it is a narrow opt-out, not the default&lt;/li&gt;
&lt;li&gt;Do not rely on &lt;code&gt;light only&lt;/code&gt; as an opt-out until you've confirmed it works in each browser in your matrix. Browsers have parsed &lt;code&gt;only&lt;/code&gt; for years, and MDN notes Chrome's Auto Dark Theme respects it. But WebKit says Safari 27.3 beta 1's Adaptive Dark Mode did not support it, so it wasn't a working opt-out there&lt;/li&gt;
&lt;li&gt;Re-check contrast for text, focus, hover, and non-text UI in every scheme you declare. Measure authored colors; a browser repaint that computed styles cannot see is not that measurement&lt;/li&gt;
&lt;li&gt;When Safari is in your matrix, keep a visual record (screenshot or equivalent) of Adaptive Dark Mode cases. The HIG requires reviewers to capture these. Automated contrast on computed styles can stay green while the painted page fails&lt;/li&gt;
&lt;li&gt;If the product adds a theme control later, keep the document-level declaration honest about the schemes you actually support&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Quick check for your app
&lt;/h2&gt;

&lt;p&gt;Open example.com and a second key page at a phone width. View source or the Accessibility/Elements panel and confirm a &lt;code&gt;color-scheme&lt;/code&gt; meta tag or first-paint CSS declaration before any theme script. Toggle the OS appearance and, where you support Safari Adaptive Dark Mode, capture light pages with and without the declaration. Compare authored computed colors with what you see painted. Suitable automated checks can catch a missing declaration and authored contrast regressions; they do not prove how Adaptive Dark Mode paints. Pair them with a visual check on the browsers your audience uses.&lt;/p&gt;




&lt;p&gt;The Web HIG is an open set of guidelines for how the web should behave, not a component library. Design systems define how it looks. The Web HIG defines how it behaves.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/frozonfreak/webhig/discussions/35" rel="noopener noreferrer"&gt;https://github.com/frozonfreak/webhig/discussions/35&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Hashnode: &lt;a href="https://thewebhig.hashnode.dev/declare-supported-color-schemes-web-hig-tip-17" rel="noopener noreferrer"&gt;https://thewebhig.hashnode.dev/declare-supported-color-schemes-web-hig-tip-17&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Dev.to: &lt;a href="https://dev.to/frozonfreak/declare-your-color-schemes-before-the-first-paint-web-hig-tip-17-42bl"&gt;https://dev.to/frozonfreak/declare-your-color-schemes-before-the-first-paint-web-hig-tip-17-42bl&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Bluesky: &lt;a href="https://bsky.app/profile/sastha.bsky.social/post/3n26df6xrt22g" rel="noopener noreferrer"&gt;https://bsky.app/profile/sastha.bsky.social/post/3n26df6xrt22g&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>css</category>
      <category>a11y</category>
      <category>html</category>
    </item>
    <item>
      <title>Your AI agent reused the wrong kind of screen. Name the experience purpose.</title>
      <dc:creator>FrozonFreak</dc:creator>
      <pubDate>Fri, 13 Nov 2026 06:07:54 +0000</pubDate>
      <link>https://dev.to/frozonfreak/your-ai-agent-reused-the-wrong-kind-of-screen-name-the-experience-purpose-53jh</link>
      <guid>https://dev.to/frozonfreak/your-ai-agent-reused-the-wrong-kind-of-screen-name-the-experience-purpose-53jh</guid>
      <description>&lt;p&gt;Say you ask your AI agent for a staff check-in screen at example.com. It returns a wide editorial layout, a large heading, and a single "Get started" button. You ask for a member briefing instead, and the next result is a dense table with bulk actions.&lt;/p&gt;

&lt;p&gt;The agent reused a familiar pattern from a different kind of work.&lt;/p&gt;

&lt;p&gt;Design Decision Method's agent procedure asks the agent to identify the experience purpose before it decides how a screen should look and behave. The live docs already use that idea in an illustration: a short request is not operational monitoring. The procedure is more specific. It names six purposes: operational work, reading and storytelling, exploration, evaluation and purchase, creation and editing, and participation and collaboration. Mixed-purpose products can use a different approach per flow.&lt;/p&gt;

&lt;p&gt;What follows are four short, fictional illustrations on example.com. They are not case studies, and they do not report measured results.&lt;/p&gt;

&lt;h2&gt;
  
  
  Operational work
&lt;/h2&gt;

&lt;p&gt;A staff tool for checking people into today's workshop. The job is to find a name, mark attendance, and undo a mistake.&lt;/p&gt;

&lt;p&gt;The procedure's purpose reference treats operational work as a heuristic, not a mandatory template. It points to stable placement, visible state, fast entry, and reversible actions. A magazine-style page that hides the current list is the wrong import. So is a decorative metric row that does not help someone complete the check-in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading and storytelling
&lt;/h2&gt;

&lt;p&gt;A monthly member briefing: a short article with a clear reading order, headings, and a path into a longer report.&lt;/p&gt;

&lt;p&gt;Here the priorities shift. The reference talks about reading order, pacing, and routes into deeper content. The procedure also says that reading or exploration need not end in a transaction or a single primary button. A checkout-shaped page, or a dashboard of tiles, imports another domain's conventions. The briefing can simply be readable on a phone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Evaluation and purchase
&lt;/h2&gt;

&lt;p&gt;A flow to buy a workshop ticket, with a date, a concession option, the total cost, and the cancellation terms.&lt;/p&gt;

&lt;p&gt;For evaluation and purchase, the reference asks for suitability, variations, availability, total cost, terms, and an informed commitment — plus a way to correct the order. A dense admin table that buries the price is not that job. Neither is a long story page that never states what the buyer is committing to. Acceptance in this flow is about an informed choice, not about finishing a staff task.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI as a capability, not a new purpose
&lt;/h2&gt;

&lt;p&gt;example.com also has a small helper that drafts a workshop description. Because that AI feature already exists, the procedure says to apply AI checks only there: say what the helper can do and where it is limited, separate generating a draft from publishing it, and do not show fabricated confidence figures.&lt;/p&gt;

&lt;p&gt;If the product had no AI feature, the rule is simpler: do not add AI controls. The purpose reference is just as plain: treat AI as a capability within a purpose, not a reason to replace every interface with chat. The ticket flow should stay a ticket flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters
&lt;/h2&gt;

&lt;p&gt;Agents are good at completing a pattern they have seen. They are less good at noticing that the pattern belongs to another job. The procedure warns against importing one domain's conventions into another — an operations dashboard for a site that needed a readable page, for example.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://thewebhig.hashnode.dev/your-ai-agent-invents-product-rules-make-it-establish-context-first" rel="noopener noreferrer"&gt;Establish context&lt;/a&gt; is where purpose belongs, alongside audience, frequency, mistake cost, and device. Later, &lt;a href="https://thewebhig.hashnode.dev/done-isn-t-a-test-result-how-your-ai-agent-should-hand-off-ui-work" rel="noopener noreferrer"&gt;deliver and validate&lt;/a&gt; asks for acceptance criteria matched to that purpose: task execution in the check-in tool, comprehension in the briefing, informed purchase in the ticket flow.&lt;/p&gt;

&lt;p&gt;The method does not claim this saves time. That has not been measured. It also does not guarantee a good result. What it gives you is a place to catch a borrowed layout before it hardens.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;Design Decision Method is a lightweight design decision workflow for developers building interfaces with AI agents. It follows the open Agent Skills format and is MIT licensed. Install paths for Claude Code, Cursor, Gemini CLI, and Codex are in the README.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://frozonfreak.github.io/DesignDecesionMethod/" rel="noopener noreferrer"&gt;Read the DDM docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://raw.githubusercontent.com/frozonfreak/DesignDecesionMethod/main/references/modern-design.md" rel="noopener noreferrer"&gt;Purpose-based presentation reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://raw.githubusercontent.com/frozonfreak/DesignDecesionMethod/main/README.md" rel="noopener noreferrer"&gt;Repository README and install notes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>ux</category>
      <category>webdev</category>
      <category>product</category>
    </item>
    <item>
      <title>Six short notes before your AI agent builds a screen</title>
      <dc:creator>FrozonFreak</dc:creator>
      <pubDate>Mon, 09 Nov 2026 16:30:22 +0000</pubDate>
      <link>https://dev.to/frozonfreak/six-short-notes-before-your-ai-agent-builds-a-screen-2c1m</link>
      <guid>https://dev.to/frozonfreak/six-short-notes-before-your-ai-agent-builds-a-screen-2c1m</guid>
      <description>&lt;p&gt;You ask your AI agent for a small change: let someone snooze a reminder on example.com. Without a shared procedure, the reply can grow into controls nobody asked for.&lt;/p&gt;

&lt;p&gt;Design Decision Method is six steps. For a small change, the notes can stay short. This walkthrough takes one fictional feature through all six as light notes — not a case study, not a measured result, and not a claim about every agent.&lt;/p&gt;

&lt;p&gt;The product details below are invented for the illustration.&lt;/p&gt;

&lt;h2&gt;
  
  
  The brief
&lt;/h2&gt;

&lt;p&gt;example.com already has reminders with a title, a due time, and a done action. You want snooze on the existing reminder detail view: one hour, tomorrow morning, or a time the person picks. No new navigation. No new object types.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Establish context
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Audience:&lt;/strong&gt; People who already use reminders on their phone.&lt;br&gt;
&lt;strong&gt;Task / frequency:&lt;/strong&gt; Snooze an upcoming reminder; occasional.&lt;br&gt;
&lt;strong&gt;Cost of a mistake:&lt;/strong&gt; Medium — a bad snooze can make someone miss a follow-up.&lt;br&gt;
&lt;strong&gt;Device:&lt;/strong&gt; Phone first.&lt;br&gt;
&lt;strong&gt;Known:&lt;/strong&gt; Title, due time, and done already exist. There is no calendar product.&lt;br&gt;
&lt;strong&gt;Assumed (marked):&lt;/strong&gt; Quiet hours were not requested, so none are designed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Enough for &lt;a href="https://thewebhig.hashnode.dev/your-ai-agent-invents-product-rules-make-it-establish-context-first" rel="noopener noreferrer"&gt;establish context&lt;/a&gt;: who it is for, how often it happens, what a mistake costs, and what the product already has.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Choose the visual system
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Keep the project's existing components and tokens. This change adds snooze actions; it does not introduce a new visual identity.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One line is enough when a system already exists. &lt;a href="https://thewebhig.hashnode.dev/your-ai-agent-added-a-feature-why-did-it-redesign-the-buttons" rel="noopener noreferrer"&gt;Choose the visual system&lt;/a&gt; starts there.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Object map — run the gate
&lt;/h2&gt;

&lt;p&gt;The docs ask for a map when relationships would change the navigation, the routes, or the structure of the screen. Skip the map when the change does not depend on those relationships, such as a copy edit or a restyle that leaves the structure alone.&lt;/p&gt;

&lt;p&gt;The agent procedure records the gate in one line:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Map: skipped&lt;/strong&gt; — snooze adds actions on an existing reminder detail view. No new object types, no new routes, and navigation is unchanged.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When relationships would reshape the screen, &lt;a href="https://thewebhig.hashnode.dev/when-should-your-ai-agent-map-objects-before-layout" rel="noopener noreferrer"&gt;map objects before layout&lt;/a&gt;. When they would not, do not invent a map for theatre.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Decision record
&lt;/h2&gt;

&lt;p&gt;One consequential choice:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Decision:&lt;/strong&gt; Three snooze presets (1 hour, tomorrow morning, pick a time). Presets first; custom time secondary.&lt;br&gt;
&lt;strong&gt;Frequency / familiarity:&lt;/strong&gt; Occasional; snooze is already familiar elsewhere.&lt;br&gt;
&lt;strong&gt;Error cost:&lt;/strong&gt; Medium if the new time is unclear.&lt;br&gt;
&lt;strong&gt;Device:&lt;/strong&gt; Phone.&lt;br&gt;
&lt;strong&gt;Why:&lt;/strong&gt; Presets cover common cases with less entry effort than opening a full picker every time.&lt;br&gt;
&lt;strong&gt;Assumption:&lt;/strong&gt; "Tomorrow morning" means 09:00 in the person's local timezone — marked, not smuggled in as an unmarked product rule.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A &lt;a href="https://thewebhig.hashnode.dev/your-ai-agent-forgets-why-give-it-a-decision-record" rel="noopener noreferrer"&gt;decision record&lt;/a&gt; is for choices that are costly to undo, not for every spacing tweak.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Structure each view
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Purpose:&lt;/strong&gt; Delay one reminder without opening a separate settings flow.&lt;br&gt;
&lt;strong&gt;Information to decide:&lt;/strong&gt; Reminder title, current due time, and the new time.&lt;br&gt;
&lt;strong&gt;Actions:&lt;/strong&gt; Snooze 1 hour; until tomorrow morning; pick a time; cancel.&lt;br&gt;
&lt;strong&gt;States:&lt;/strong&gt; Loading while saving; error if save fails (keep the reminder identity visible); success shows the new time on the detail view.&lt;br&gt;
&lt;strong&gt;Device:&lt;/strong&gt; Presets and cancel stay available on a narrow screen.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://thewebhig.hashnode.dev/your-ai-agent-draws-a-pretty-layout-make-it-structure-the-view-first" rel="noopener noreferrer"&gt;Structure each view&lt;/a&gt; before the chrome.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Deliver and validate
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Delivered:&lt;/strong&gt; Snooze actions on reminder detail (code), plus the notes above.&lt;br&gt;
&lt;strong&gt;Acceptance / evidence:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Choosing "1 hour" updates the due time. &lt;em&gt;Manual test&lt;/em&gt; on one phone-sized viewport.&lt;/li&gt;
&lt;li&gt;Custom time rejects empty input and keeps the previous due time visible. &lt;em&gt;Heuristic review&lt;/em&gt; of the flow only.&lt;/li&gt;
&lt;li&gt;Contrast on the snooze actions. &lt;em&gt;Automated check&lt;/em&gt; on the colours used — not a claim about the whole product.&lt;/li&gt;
&lt;li&gt;People finish snooze without help. &lt;em&gt;Proposed.&lt;/em&gt; Not tested. No user research.
&lt;strong&gt;Not tested:&lt;/strong&gt; screen reader pass, offline behaviour, other timezones beyond the marked assumption.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://thewebhig.hashnode.dev/done-isn-t-a-test-result-how-your-ai-agent-should-hand-off-ui-work" rel="noopener noreferrer"&gt;Deliver and validate&lt;/a&gt; labels what each result rests on and says what was not tested. Nothing above invents a measurement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep it light
&lt;/h2&gt;

&lt;p&gt;Six steps, still light: short context, a one-line visual keep, a skipped map with a reason, one decision, a compact view note, and an honest handoff. The method is a shared procedure, not mandatory for every small edit. It does not claim this saves time — that has not been measured. What it gives you is a trail of decisions the next prompt can reuse.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;Design Decision Method is a lightweight design decision workflow for developers building interfaces with AI agents. It follows the open Agent Skills format and is MIT licensed. Install paths for Claude Code, Cursor, Gemini CLI, and Codex are in the README.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://frozonfreak.github.io/DesignDecesionMethod/" rel="noopener noreferrer"&gt;Read the DDM docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://raw.githubusercontent.com/frozonfreak/DesignDecesionMethod/main/README.md" rel="noopener noreferrer"&gt;Repository README and install notes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://raw.githubusercontent.com/frozonfreak/DesignDecesionMethod/main/assets/design-record-template.md" rel="noopener noreferrer"&gt;Design record template&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>ux</category>
      <category>ai</category>
      <category>agents</category>
    </item>
    <item>
      <title>Did Place order work? Show the pending state (Web HIG tip #16)</title>
      <dc:creator>FrozonFreak</dc:creator>
      <pubDate>Sun, 08 Nov 2026 09:50:26 +0000</pubDate>
      <link>https://dev.to/frozonfreak/did-place-order-work-show-the-pending-state-web-hig-tip-16-3f4</link>
      <guid>https://dev.to/frozonfreak/did-place-order-work-show-the-pending-state-web-hig-tip-16-3f4</guid>
      <description>&lt;p&gt;Someone taps &lt;strong&gt;Place order&lt;/strong&gt; on a slow connection. Nothing changes on screen. Did the tap register? Should they try again? A missing pending state leaves them guessing. If repeat requests reach an unprotected endpoint, that uncertainty can also become a duplicate order.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Web HIG tip #16 (Quick #45):&lt;/strong&gt; Server mutations must show pending UI immediately — disable submit, show spinner.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it matters
&lt;/h2&gt;

&lt;p&gt;A request in flight is invisible unless the interface makes it visible. Without that signal:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;People can't tell a slow request from a tap that didn't register, so they tap again&lt;/li&gt;
&lt;li&gt;Each extra tap can become another order, payment, invite or post&lt;/li&gt;
&lt;li&gt;Keyboard users can face the same uncertainty; Enter can submit a form, depending on its controls and platform conventions&lt;/li&gt;
&lt;li&gt;If the page looks frozen, some people reload or leave halfway through&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full HIG covers this in &lt;a href="https://github.com/frozonfreak/webhig/blob/1ab8c28fdf2e1cf9a5a4e07e67afbea20a4bab70/HIG.md#42-async-mutation--server-action-state-model" rel="noopener noreferrer"&gt;§4.2, Async Mutation &amp;amp; Server Action State Model&lt;/a&gt;: forms executing server mutations MUST supply immediate visual feedback without waiting for server response round-trips. &lt;a href="https://github.com/frozonfreak/webhig/blob/1ab8c28fdf2e1cf9a5a4e07e67afbea20a4bab70/HIG-QUICK.md" rel="noopener noreferrer"&gt;Quick #47&lt;/a&gt; adds that critical mutations should be idempotent. &lt;a href="https://github.com/frozonfreak/webhig/blob/1ab8c28fdf2e1cf9a5a4e07e67afbea20a4bab70/HIG.md#45-concurrency-conflict--idempotency" rel="noopener noreferrer"&gt;§4.5&lt;/a&gt; says important mutations SHOULD use idempotency keys at the product/API layer. That protects the intended effect of an operation when requests repeat; it does not guarantee a single network request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before and after
&lt;/h2&gt;

&lt;p&gt;This bare native POST form has no application-specific pending feedback. The browser may show navigation progress, but the button does not explain what is happening:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"https://example.com/orders"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"post"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"checkout"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Place order&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is a &lt;strong&gt;client-side pending-state illustration&lt;/strong&gt;, not a complete checkout. It keeps native POST navigation and assumes this is the form's only submission handler. It disables the single submit button and guards against later &lt;code&gt;submit&lt;/code&gt; events while pending:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"https://example.com/orders"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"post"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"checkout"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- Order fields and server-side security protections are omitted. --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"checkout-spinner"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"spinner"&lt;/span&gt; &lt;span class="na"&gt;hidden&lt;/span&gt; &lt;span class="na"&gt;aria-hidden=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"checkout-label"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Place order&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"status"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"checkout-status"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
  &lt;span class="nc"&gt;.spinner&lt;/span&gt;&lt;span class="nd"&gt;:not&lt;/span&gt;&lt;span class="o"&gt;([&lt;/span&gt;&lt;span class="nt"&gt;hidden&lt;/span&gt;&lt;span class="o"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;inline-block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.8em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.8em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="n"&gt;currentColor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;border-inline-end-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;transparent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;spin&lt;/span&gt; &lt;span class="m"&gt;1s&lt;/span&gt; &lt;span class="nb"&gt;linear&lt;/span&gt; &lt;span class="nb"&gt;infinite&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;spin&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nt"&gt;to&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;360deg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefers-reduced-motion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;.spinner&lt;/span&gt;&lt;span class="nd"&gt;:not&lt;/span&gt;&lt;span class="o"&gt;([&lt;/span&gt;&lt;span class="nt"&gt;hidden&lt;/span&gt;&lt;span class="o"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;checkout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button[type="submit"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;checkout-label&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;spinner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;checkout-spinner&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;checkout-status&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;submitting&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;resetPending&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;submitting&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;disabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Place order&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;spinner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hidden&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;resetPending&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;submit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultPrevented&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;submitting&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;submitting&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;disabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Placing order…&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;spinner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hidden&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Placing your order…&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// Let this first submission continue as a native POST navigation.&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pageshow&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;persisted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;resetPending&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The handler updates the label, spinner and status text synchronously, before waiting for a response. A fast navigation may replace the page before that feedback is painted or announced. &lt;code&gt;role="status"&lt;/code&gt; is a polite live region, not a promise that a screen reader will speak before navigation.&lt;/p&gt;

&lt;p&gt;The guard covers only later &lt;code&gt;submit&lt;/code&gt; events on this page while its flag is set. A direct &lt;code&gt;form.submit()&lt;/code&gt; call bypasses that event. Other tabs, reloads and retries remain outside its scope. The &lt;code&gt;pageshow&lt;/code&gt; handler resets the UI when this document returns from the back/forward cache; it does not establish whether an order succeeded.&lt;/p&gt;

&lt;p&gt;This snippet does &lt;strong&gt;not&lt;/strong&gt; handle a blocked or cancelled navigation, an in-page request failure, or a successful order response. A real flow needs outcome and recovery UI, preserved input, and server-side validation and security protections. Do not drop this handler into a framework-managed form without adapting it to that form's lifecycle.&lt;/p&gt;

&lt;p&gt;A hidden input named &lt;code&gt;idempotency_key&lt;/code&gt; would not solve duplication by itself. The server must implement deduplication, including concurrent requests, and bind a key to the same intended operation and authorised caller. Reuse that key for retries of that operation, not unrelated new orders; define payload checks and expiry. If a response is lost, do not assume the order failed. Check its outcome before starting another order. Those server and recovery pieces are outside this example.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do this instead
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Update pending feedback in the submission handler, without waiting for the network: disable the submit trigger, show an inline pending indicator, and keep a meaningful label such as "Saving…" or "Sending…"&lt;/li&gt;
&lt;li&gt;Keep the label meaningful. A spinner alone tells people something is happening, not what&lt;/li&gt;
&lt;li&gt;Show a clear result. For in-page requests, leave pending state when the outcome is known; on a recoverable failure, restore usable controls and preserve the input&lt;/li&gt;
&lt;li&gt;For native navigation, provide a usable result or error page and a safe route back. A connection failure can leave the outcome unknown: offer a way to check it rather than blindly retrying&lt;/li&gt;
&lt;li&gt;Don't rely on the disabled button alone. Important mutations need server-side repeat-request protection; a key only helps when the endpoint actually enforces it&lt;/li&gt;
&lt;li&gt;If your framework exposes submission state, use its documented pending and recovery lifecycle, and test that feedback does not wait for the server response&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Quick check for your app
&lt;/h2&gt;

&lt;p&gt;Use a test environment, not a real payment or production invite. Slow down the response and check that the label and pending indicator update without waiting for it. Try repeated clicks and, where supported, Enter submission. Inspect the network, but also verify the server-side result: deliberately repeated requests for the same operation should not create duplicate orders. Test concurrent requests too.&lt;/p&gt;

&lt;p&gt;Exercise validation failures, lost responses, recoverable errors, Back navigation and a retry of the same operation. Check keyboard use, reduced motion and screen-reader feedback; for native navigation, inspect the destination page as well as the pending state. Suitable automated browser and server tests can catch these problems. Pair them with manual checks on the browsers and assistive technology your audience uses. A green suite is evidence for what it actually exercised, not proof that every failure path works.&lt;/p&gt;




&lt;p&gt;The Web HIG is an open set of guidelines for how the web should behave, not a component library. Design systems define how it looks. The Web HIG defines how it behaves.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/frozonfreak/webhig/discussions/33" rel="noopener noreferrer"&gt;https://github.com/frozonfreak/webhig/discussions/33&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Hashnode: &lt;a href="https://thewebhig.hashnode.dev/show-pending-on-submit-web-hig-tip-16" rel="noopener noreferrer"&gt;https://thewebhig.hashnode.dev/show-pending-on-submit-web-hig-tip-16&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Dev.to: &lt;a href="https://dev.to/frozonfreak/did-place-order-work-show-the-pending-state-web-hig-tip-16-3f4"&gt;https://dev.to/frozonfreak/did-place-order-work-show-the-pending-state-web-hig-tip-16-3f4&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Bluesky: &lt;a href="https://bsky.app/profile/sastha.bsky.social/post/3mzs5erm5m22f" rel="noopener noreferrer"&gt;https://bsky.app/profile/sastha.bsky.social/post/3mzs5erm5m22f&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>ux</category>
      <category>javascript</category>
      <category>forms</category>
    </item>
    <item>
      <title>Your AI agent added a feature. Why did it redesign the buttons?</title>
      <dc:creator>FrozonFreak</dc:creator>
      <pubDate>Sun, 08 Nov 2026 09:50:00 +0000</pubDate>
      <link>https://dev.to/frozonfreak/your-ai-agent-added-a-feature-why-did-it-redesign-the-buttons-5h7o</link>
      <guid>https://dev.to/frozonfreak/your-ai-agent-added-a-feature-why-did-it-redesign-the-buttons-5h7o</guid>
      <description>&lt;p&gt;You ask your AI agent to add an invoice filter to an existing product. The new screen comes back with pill-shaped buttons, a different type scale, and a new accent colour. The rest of the product still uses square buttons and the original tokens.&lt;/p&gt;

&lt;p&gt;You wanted a filter. You got a second visual direction.&lt;/p&gt;

&lt;p&gt;That is the problem this illustration starts with, not a claim about every agent. Design Decision Method's second step, choose the visual system, gives the agent a place to check what already exists before proposing something else.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the system the project has
&lt;/h2&gt;

&lt;p&gt;The live docs put the starting point plainly: keep a system the project already has. When it has none, select from the platform.&lt;/p&gt;

&lt;p&gt;The agent procedure adds an important qualification: retain and extend a &lt;strong&gt;suitable&lt;/strong&gt; existing system, brand, or platform direction, and verify its actual tokens, components, and supported versions. If the brief explicitly requests a different direction, assess that request first, including accessibility and platform constraints, migration scope, and consequential conflicts.&lt;/p&gt;

&lt;p&gt;So this is not a rule against redesign. It is a rule against treating an ordinary feature request as permission to redesign.&lt;/p&gt;

&lt;h2&gt;
  
  
  A worked example: invoice filters on example.com
&lt;/h2&gt;

&lt;p&gt;Imagine a small invoicing product at example.com. It already has an in-house component library, named colour roles, and a documented type scale. You ask for a filter that lets customers show unpaid invoices.&lt;/p&gt;

&lt;p&gt;This is a fictional illustration, not a case study or a measured result. The product details and the following note are invented for the example.&lt;/p&gt;

&lt;p&gt;Before building, the agent's note could read:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Existing direction:&lt;/strong&gt; Keep the product's in-house system. This request adds filtering, not a new visual identity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reuse:&lt;/strong&gt; Use the existing button and select components, the established text styles, and the project's colour roles. Check their definitions and supported states rather than recreating them from a screenshot.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Interaction need:&lt;/strong&gt; Let someone choose an invoice status and clear the filter. If outside guidance helps with that interaction, adapt the behaviour into the existing components rather than importing another visual identity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gap:&lt;/strong&gt; If a needed state is missing from the component library, flag it and propose an extension. Do not silently introduce a second button family.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now the review has something concrete to compare against. Did the implementation use the existing components? Is a new style an intentional extension or an unexplained replacement? A polished screenshot alone cannot answer those questions.&lt;/p&gt;

&lt;h2&gt;
  
  
  An interaction pattern is not a brand
&lt;/h2&gt;

&lt;p&gt;Choosing the visual system and borrowing an interaction pattern are separate decisions in DDM.&lt;/p&gt;

&lt;p&gt;A filtering pattern might help organise choices. That does not mean the page needs the source system's colours, typography, or branding. The procedure says to borrow patterns when they solve the task, not to mix competing visual identities on one surface.&lt;/p&gt;

&lt;p&gt;In the invoice example, an outside pattern would need a specific reason to be used. It is not a reason to replace the product's visual language. The question is what the interaction helps someone do, then how to express it within the system already chosen.&lt;/p&gt;

&lt;h2&gt;
  
  
  What if there is no system?
&lt;/h2&gt;

&lt;p&gt;Then the platform matters. DDM's procedure gives selection rules rather than asking the agent to pick a favourite. For other web or cross-platform work with no system, it names Material 3 as the fallback, with implementation support checked.&lt;/p&gt;

&lt;p&gt;That is a conditional fallback, not a ranking of design systems or an instruction to replace an existing one. The procedure also recognises documented bespoke or editorial direction. Use the applicable row, and check what the project's stack actually supports.&lt;/p&gt;

&lt;p&gt;The accompanying reference asks the agent to inspect tokens, components, screenshots, and installed dependencies. Those are clues to investigate, not substitutes for understanding the project's visual decisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;Choose the visual system is step 2 of DDM's six-step workflow. For your next feature request, ask the agent to identify the existing system, what it will reuse, and any gaps before it proposes new styling.&lt;/p&gt;

&lt;p&gt;Design Decision Method is a lightweight design decision workflow for developers building interfaces with AI agents. It follows the open Agent Skills format and is MIT licensed. The README documents installation for Claude Code, Cursor, Gemini CLI, and Codex.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://frozonfreak.github.io/DesignDecesionMethod/" rel="noopener noreferrer"&gt;Read the DDM docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://raw.githubusercontent.com/frozonfreak/DesignDecesionMethod/main/README.md" rel="noopener noreferrer"&gt;Read the repository README and installation notes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://raw.githubusercontent.com/frozonfreak/DesignDecesionMethod/main/references/visual-systems.md" rel="noopener noreferrer"&gt;Read the visual-system adoption checks&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>ux</category>
      <category>webdev</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Your AI agent draws a pretty layout. Make it structure the view first.</title>
      <dc:creator>FrozonFreak</dc:creator>
      <pubDate>Fri, 06 Nov 2026 05:10:02 +0000</pubDate>
      <link>https://dev.to/frozonfreak/your-ai-agent-draws-a-pretty-layout-make-it-structure-the-view-first-fc0</link>
      <guid>https://dev.to/frozonfreak/your-ai-agent-draws-a-pretty-layout-make-it-structure-the-view-first-fc0</guid>
      <description>&lt;p&gt;You ask your AI agent for an order status page. It returns a polished card grid, a sparkline you never asked for, and a primary button that says "Explore insights." The status sits in the middle. There is no empty state. Error is a toast that vanishes. On a phone, cancel is gone.&lt;/p&gt;

&lt;p&gt;The screen looks finished. The view was never defined.&lt;/p&gt;

&lt;p&gt;Design Decision Method's fifth step is there to stop that. Step 5, structure each view, asks the agent to say what the view is for, what information people need to decide, which actions matter, and which states exist — before it invents a layout.&lt;/p&gt;

&lt;h2&gt;
  
  
  What step 5 asks for
&lt;/h2&gt;

&lt;p&gt;For each important view, the method asks the agent to state:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;its purpose&lt;/li&gt;
&lt;li&gt;the information required to decide&lt;/li&gt;
&lt;li&gt;the actions&lt;/li&gt;
&lt;li&gt;the states that apply, including loading, empty, and error where they exist&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It also asks the agent to keep essential information visible, and to adapt the layout to the device without dropping a capability the task needs.&lt;/p&gt;

&lt;p&gt;That is the whole fifth step. A short definition of the view's job, then a layout that still does that job on a phone.&lt;/p&gt;

&lt;h2&gt;
  
  
  A worked example: order status on example.com
&lt;/h2&gt;

&lt;p&gt;Say you run a small shop at example.com, and you ask your agent for the order status page. This is an illustration of how the procedure applies, not a case study or a measured result.&lt;/p&gt;

&lt;p&gt;Without the method, the agent jumps to a familiar pattern: a dashboard-shaped page with charts, because status pages elsewhere often look like that. The order identity is secondary. Loading is a spinner with no context. Empty and error are afterthoughts.&lt;/p&gt;

&lt;p&gt;With step 5 filled in first, the note might look like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Purpose:&lt;/strong&gt; Show where one order is, what happens next, and what the customer can do about it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Information required to decide:&lt;/strong&gt; Order id, current status, last update, next step, items, and a path to support.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Actions:&lt;/strong&gt; Track shipment (when a carrier link exists). Cancel while still allowed. Contact support. Back to orders.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;States:&lt;/strong&gt; Loading keeps the order context. Empty says this id has no order and offers a path back. Error keeps the order id visible and offers retry and support. The success path makes status and next step readable without a chart.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Device:&lt;/strong&gt; Phone first. Cancel and support stay available; do not hide them behind a hover-only control.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now the sparkline has nowhere to hide. "Explore insights" is out of scope. The layout still has to answer the purpose, surface the decision information, expose the actions, and cover the states.&lt;/p&gt;

&lt;h2&gt;
  
  
  Purpose before chrome
&lt;/h2&gt;

&lt;p&gt;Agents are good at completing a visual pattern. They are less good at noticing that the pattern belongs to a different job. A monitoring dashboard and an order status page can both show numbers. Only one helps someone decide what to do about a single order.&lt;/p&gt;

&lt;p&gt;Write the purpose first and the mismatch is obvious. Decorative metrics need a reason or they stay out. Essential information — status, next step, recovery — stays visible instead of being trimmed for whitespace.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters when you build with agents
&lt;/h2&gt;

&lt;p&gt;A pretty first draft is easy to accept. The missing pieces are usually the ones that hurt later: empty, error, and the action that disappeared on a narrow viewport.&lt;/p&gt;

&lt;p&gt;Structure each view puts those pieces in the first note. You can reject a layout that drops cancel on mobile, or never defines what happens when the order id is wrong.&lt;/p&gt;

&lt;p&gt;The method does not claim this saves time. That has not been measured. What it does give you is a view definition you can check before the chrome hardens.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep it proportional
&lt;/h2&gt;

&lt;p&gt;A one-line copy fix does not need a full view note. The method is a shared procedure, not mandatory for every small edit. When the change adds a screen, changes what people must see to decide, or could hide an action on a smaller device, spend the few lines.&lt;/p&gt;

&lt;p&gt;Earlier steps give you context, a map when relationships matter, and a decision record for costly choices. Step 5 structures the views those decisions imply, instead of starting from a stock layout again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this fits
&lt;/h2&gt;

&lt;p&gt;Structure each view is step 5 of six: establish context, choose the visual system, map objects before layout, write a decision record, structure each view, then deliver and validate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;Design Decision Method is a lightweight design decision workflow for developers building interfaces with AI agents. It follows the open Agent Skills format and is MIT licensed. Install paths for Claude Code, Cursor, Gemini CLI, and Codex are in the README.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Docs: &lt;a href="https://frozonfreak.github.io/DesignDecesionMethod/" rel="noopener noreferrer"&gt;https://frozonfreak.github.io/DesignDecesionMethod/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/frozonfreak/DesignDecesionMethod" rel="noopener noreferrer"&gt;https://github.com/frozonfreak/DesignDecesionMethod&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Design record template: &lt;a href="https://github.com/frozonfreak/DesignDecesionMethod/blob/main/assets/design-record-template.md" rel="noopener noreferrer"&gt;https://github.com/frozonfreak/DesignDecesionMethod/blob/main/assets/design-record-template.md&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>ux</category>
      <category>webdev</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Don't lazy-load the image people see first (Web HIG tip #15)</title>
      <dc:creator>FrozonFreak</dc:creator>
      <pubDate>Fri, 06 Nov 2026 05:09:51 +0000</pubDate>
      <link>https://dev.to/frozonfreak/dont-lazy-load-the-image-people-see-first-web-hig-tip-15-38k3</link>
      <guid>https://dev.to/frozonfreak/dont-lazy-load-the-image-people-see-first-web-hig-tip-15-38k3</guid>
      <description>&lt;p&gt;Open your home page or a product page on a phone, on a slow connection. The headline and buttons show up, but the big hero image stays blank for a beat longer than everything else. Look at its markup and you'll often find &lt;code&gt;loading="lazy"&lt;/code&gt;, added to every &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; on the site by a template, a plugin, or a well-meant "lazy-load all images" change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Web HIG tip #15 (Quick #84):&lt;/strong&gt; Lazy-load below-the-fold media; never lazy-load the LCP candidate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it matters
&lt;/h2&gt;

&lt;p&gt;Lazy loading is a trade: the browser waits to fetch an image until it's close to the viewport. That's a win for images further down the page, and a loss for the one people see first:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Images far below the fold that nobody scrolls to never download, which saves data and leaves bandwidth for what's on screen&lt;/li&gt;
&lt;li&gt;A lazy image's fetch can't start until the browser has laid out the page and worked out where the image sits, so a lazy hero image starts downloading later than it would have otherwise&lt;/li&gt;
&lt;li&gt;The LCP candidate is usually the biggest image or text block in the first view. When it arrives late, the page looks unfinished, and Largest Contentful Paint gets worse&lt;/li&gt;
&lt;li&gt;If the image URL only exists in JavaScript, for example a &lt;code&gt;data-src&lt;/code&gt; that a script swaps in, the browser can't find it in the HTML at all until that script runs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full HIG covers the image side of this in §2.1, Document Fundamentals, under responsive images: lazy-load non-critical images where appropriate, and avoid lazy-loading the LCP candidate and other immediately needed content. The LCP candidate SHOULD be discoverable early and MAY use &lt;code&gt;fetchpriority="high"&lt;/code&gt; when appropriate. Quick #84 is Quick-only guidance, with no rule ID of its own. It pairs with Quick #83, which asks every image to declare its dimensions or aspect ratio, and with Quick #82's LCP target of 2.5 s or less.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule of thumb
&lt;/h2&gt;

&lt;p&gt;Ask: &lt;strong&gt;will this image be on screen before anyone scrolls?&lt;/strong&gt; If yes, don't lazy-load it.&lt;/p&gt;

&lt;p&gt;This page looks fine on a fast laptop, but the hero waits for layout and for a script before it even starts downloading:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- Hero: the LCP candidate, hidden from the browser until JS runs --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"lazy"&lt;/span&gt; &lt;span class="na"&gt;data-src=&lt;/span&gt;&lt;span class="s"&gt;"https://example.com/img/hero.jpg"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Further down the page --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://example.com/img/team.jpg"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"The team at the spring meetup"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Load the hero early and lazy-load what's further down:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- Hero: a real src in the HTML, high priority, not lazy --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://example.com/img/hero.jpg"&lt;/span&gt;
     &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"1200"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"600"&lt;/span&gt;
     &lt;span class="na"&gt;fetchpriority=&lt;/span&gt;&lt;span class="s"&gt;"high"&lt;/span&gt;
     &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"A reader using the app on a phone"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Further down the page --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://example.com/img/team.jpg"&lt;/span&gt;
     &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"800"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"533"&lt;/span&gt;
     &lt;span class="na"&gt;loading=&lt;/span&gt;&lt;span class="s"&gt;"lazy"&lt;/span&gt;
     &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"The team at the spring meetup"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The page looks the same. The difference is when each image starts downloading.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do this instead
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Leave &lt;code&gt;loading&lt;/code&gt; off the LCP image (the default is &lt;code&gt;eager&lt;/code&gt;), give it a real &lt;code&gt;src&lt;/code&gt; or &lt;code&gt;srcset&lt;/code&gt; in the HTML, and consider &lt;code&gt;fetchpriority="high"&lt;/code&gt;. Browsers that don't support &lt;code&gt;fetchpriority&lt;/code&gt; ignore it&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;loading="lazy"&lt;/code&gt; to images and iframes that sit well below the first view: long article bodies, comment sections, footers, embedded maps and video players&lt;/li&gt;
&lt;li&gt;For a &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; further down the page, use &lt;code&gt;preload="none"&lt;/code&gt; (or &lt;code&gt;"metadata"&lt;/code&gt;) and a &lt;code&gt;poster&lt;/code&gt; image. &lt;code&gt;loading="lazy"&lt;/code&gt; on &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; is newly specified but only some browsers support it so far, so treat it as a bonus, not the fix&lt;/li&gt;
&lt;li&gt;In a carousel or gallery, load the first slide eagerly and lazy-load the rest&lt;/li&gt;
&lt;li&gt;Give lazy images &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; (or a CSS &lt;code&gt;aspect-ratio&lt;/code&gt;) so the page doesn't jump when they arrive&lt;/li&gt;
&lt;li&gt;If the hero is a CSS &lt;code&gt;background-image&lt;/code&gt;, the browser only finds it after the CSS has loaded. Use an &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; instead, or add &lt;code&gt;&amp;lt;link rel="preload" as="image"&amp;gt;&lt;/code&gt; for it&lt;/li&gt;
&lt;li&gt;The LCP element can be different on a phone and on a desktop. Check both layouts before you decide which images stay eager&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Quick check for your app
&lt;/h2&gt;

&lt;p&gt;Open your main pages (say, example.com and example.com/products) at a phone width and at a desktop width. In the browser's developer tools, find the LCP element in a performance recording, then check its markup: no &lt;code&gt;loading="lazy"&lt;/code&gt;, and a URL that's in the HTML rather than added by a script. Scroll down and watch the network panel: images further down should only start loading as you get near them. A tool can flag a lazy-loaded LCP image in a single lab run, but only checking the real LCP element on your own pages, at the widths people use, shows which image needs to load first.&lt;/p&gt;




&lt;p&gt;The Web HIG is an open set of guidelines for how the web should behave, not a component library. Design systems define how it looks. The Web HIG defines how it behaves.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/frozonfreak/webhig/discussions/32" rel="noopener noreferrer"&gt;https://github.com/frozonfreak/webhig/discussions/32&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Hashnode: &lt;a href="https://thewebhig.hashnode.dev/dont-lazy-load-the-image-people-see-first-web-hig-tip-15" rel="noopener noreferrer"&gt;https://thewebhig.hashnode.dev/dont-lazy-load-the-image-people-see-first-web-hig-tip-15&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Dev.to: &lt;a href="https://dev.to/frozonfreak/dont-lazy-load-the-image-people-see-first-web-hig-tip-15-38k3"&gt;https://dev.to/frozonfreak/dont-lazy-load-the-image-people-see-first-web-hig-tip-15-38k3&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Bluesky: &lt;a href="https://bsky.app/profile/sastha.bsky.social/post/3mzmmnfubak2p" rel="noopener noreferrer"&gt;https://bsky.app/profile/sastha.bsky.social/post/3mzmmnfubak2p&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>performance</category>
      <category>html</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Your AI agent invents product rules. Make it establish context first.</title>
      <dc:creator>FrozonFreak</dc:creator>
      <pubDate>Thu, 05 Nov 2026 05:44:41 +0000</pubDate>
      <link>https://dev.to/frozonfreak/your-ai-agent-invents-product-rules-make-it-establish-context-first-3c6k</link>
      <guid>https://dev.to/frozonfreak/your-ai-agent-invents-product-rules-make-it-establish-context-first-3c6k</guid>
      <description>&lt;p&gt;You ask your AI agent for an account settings screen. It returns a grid of cards, a dark-mode toggle you never mentioned, and a "delete account" control sitting next to a harmless preference. You push back. The next prompt invents a different layout, and a social-login switch the product does not have.&lt;/p&gt;

&lt;p&gt;The agent filled gaps. It treated guesses as product rules.&lt;/p&gt;

&lt;p&gt;Design Decision Method's first step is there to stop that. Step 1, establish context, asks the agent to read the brief and the product you already have before it decides anything about layout.&lt;/p&gt;

&lt;h2&gt;
  
  
  What step 1 asks for
&lt;/h2&gt;

&lt;p&gt;The method asks the agent to name:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the audience&lt;/li&gt;
&lt;li&gt;how often the task happens&lt;/li&gt;
&lt;li&gt;what a mistake costs&lt;/li&gt;
&lt;li&gt;the device&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It also asks the agent to keep what it knows separate from what it assumes, and not to invent rules or capabilities the product lacks.&lt;/p&gt;

&lt;p&gt;That is the whole first step. No wireframe yet. No colour palette. A short read of the brief and the existing product, then an honest list of facts and assumptions.&lt;/p&gt;

&lt;h2&gt;
  
  
  A worked example: account settings on example.com
&lt;/h2&gt;

&lt;p&gt;Say you run a small product at example.com, and you ask your agent for the account settings screen. This is an illustration of how the procedure applies, not a case study or a measured result.&lt;/p&gt;

&lt;p&gt;Without the method, the agent invents. It adds toggles, cards, and a delete path because those are familiar from other products.&lt;/p&gt;

&lt;p&gt;With step 1 filled in first, the note might look like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Audience:&lt;/strong&gt; Account holders who manage their own profile. Mostly people who signed up themselves.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Task and frequency:&lt;/strong&gt; Change email or password occasionally. Review notification preferences rarely. Delete the account almost never.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost of a mistake:&lt;/strong&gt; Medium for email or password (lockout or missed mail). High for delete or billing changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Device:&lt;/strong&gt; Phone first; desktop second.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Known:&lt;/strong&gt; The product has email, password, and three notification toggles. There is no social login. There is no team account.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Assumptions (marked):&lt;/strong&gt; Offline use was not requested, so none is designed. Billing settings live elsewhere; this screen does not invent them.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now the agent has somewhere to stop inventing. A dark-mode switch that was never in the product stays out. A delete control does not sit next to a low-cost preference just because other apps do that. Optional additions get labelled instead of smuggled in as facts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Known versus assumed
&lt;/h2&gt;

&lt;p&gt;The useful part is the split. "Known" comes from the brief, the code, or a screen you already have. "Assumed" is everything else, and it stays marked.&lt;/p&gt;

&lt;p&gt;If the agent cannot ask about a gap that would change the design, list the assumption in the deliverable instead of quietly filling it. You can confirm, correct, or reject the guess before layout hardens around it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters when you build with agents
&lt;/h2&gt;

&lt;p&gt;Agents are good at completing patterns. They are less good at noticing that your product never offered the pattern. The screen looks finished, and the invented parts are hard to spot until you click them.&lt;/p&gt;

&lt;p&gt;Establish context makes the first deliverable a short statement of who the screen is for, how often people use it, what a mistake costs, and which device they are on. Later steps build on that statement. Without it, they decorate a guess.&lt;/p&gt;

&lt;p&gt;The method does not claim this saves time. That has not been measured. What it does give you is a place to catch invented rules before they become UI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep it proportional
&lt;/h2&gt;

&lt;p&gt;A small copy change does not need a long context note. The method is a shared procedure, not mandatory for every small edit. When the change could invent a capability, bury a costly action, or import another domain's conventions, spend the few lines.&lt;/p&gt;

&lt;p&gt;The design record template has a short Context section for this: audience and roles, primary tasks and frequency, cost of error, device, known facts, and assumptions. Use as much of it as the change needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this fits
&lt;/h2&gt;

&lt;p&gt;Establish context is step 1 of six: establish context, choose the visual system, map objects before layout, write a decision record, structure each view, then deliver and validate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;Design Decision Method is a lightweight design decision workflow for developers building interfaces with AI agents. It follows the open Agent Skills format and is MIT licensed. Install paths for Claude Code, Cursor, Gemini CLI, and Codex are in the README.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Docs: &lt;a href="https://frozonfreak.github.io/DesignDecesionMethod/" rel="noopener noreferrer"&gt;https://frozonfreak.github.io/DesignDecesionMethod/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/frozonfreak/DesignDecesionMethod" rel="noopener noreferrer"&gt;https://github.com/frozonfreak/DesignDecesionMethod&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Design record template: &lt;a href="https://github.com/frozonfreak/DesignDecesionMethod/blob/main/assets/design-record-template.md" rel="noopener noreferrer"&gt;https://github.com/frozonfreak/DesignDecesionMethod/blob/main/assets/design-record-template.md&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>ux</category>
      <category>webdev</category>
      <category>agents</category>
    </item>
    <item>
      <title>"Done" isn't a test result. How your AI agent should hand off UI work</title>
      <dc:creator>FrozonFreak</dc:creator>
      <pubDate>Wed, 04 Nov 2026 05:16:10 +0000</pubDate>
      <link>https://dev.to/frozonfreak/done-isnt-a-test-result-how-your-ai-agent-should-hand-off-ui-work-3d74</link>
      <guid>https://dev.to/frozonfreak/done-isnt-a-test-result-how-your-ai-agent-should-hand-off-ui-work-3d74</guid>
      <description>&lt;p&gt;Your AI agent finishes a change and reports back: "Done. The form is accessible, responsive and works on mobile."&lt;/p&gt;

&lt;p&gt;Did anyone check? Often the agent wrote the code, looked at it, and described what it hoped was true as if it had been tested.&lt;/p&gt;

&lt;p&gt;Design Decision Method's last step is there to stop that. Step 6, deliver and validate, asks the agent to hand over the work, the decisions behind it, and a short validation status that says what each result rests on.&lt;/p&gt;

&lt;h2&gt;
  
  
  What step 6 asks for
&lt;/h2&gt;

&lt;p&gt;The method asks the agent to deliver three things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the requested artifact (spec, mockup, prototype, or code)&lt;/li&gt;
&lt;li&gt;the consequential decisions and assumptions&lt;/li&gt;
&lt;li&gt;a short validation status&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before validating, the agent defines a few observable acceptance criteria matched to the purpose of the experience. Then it labels each result by its evidence level and says what it did not test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Five evidence labels
&lt;/h2&gt;

&lt;p&gt;Each label says what a result can support and what it can't:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Level&lt;/th&gt;
&lt;th&gt;Can support&lt;/th&gt;
&lt;th&gt;Cannot support&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Proposed&lt;/td&gt;
&lt;td&gt;A target to aim for&lt;/td&gt;
&lt;td&gt;Any claim about the result&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Heuristic review&lt;/td&gt;
&lt;td&gt;Likely problems, effort comparisons&lt;/td&gt;
&lt;td&gt;Task-completion rates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Automated check&lt;/td&gt;
&lt;td&gt;Specific rule failures&lt;/td&gt;
&lt;td&gt;Overall accessibility or usability&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Manual test&lt;/td&gt;
&lt;td&gt;Behaviour of a flow, keyboard and focus&lt;/td&gt;
&lt;td&gt;Population-level outcomes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User research&lt;/td&gt;
&lt;td&gt;Real task outcomes for the sampled users&lt;/td&gt;
&lt;td&gt;Claims beyond the sample&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two rules come with it. Never fabricate a measurement. If something wasn't tested, the record says "not tested". And a mockup can show routes and states, but it cannot establish task-completion rates.&lt;/p&gt;

&lt;h2&gt;
  
  
  A worked example: a handoff note
&lt;/h2&gt;

&lt;p&gt;Say you ask your agent to rebuild the sign-up form on example.com. It's one page with name, email and password, and a clear way to fix errors.&lt;/p&gt;

&lt;p&gt;Without the method, the handoff is the sentence at the top of this post. With it, the agent's note might read:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Delivered:&lt;/strong&gt; Sign-up form (code). One page, three fields.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Decisions:&lt;/strong&gt; Errors show next to the field and in a summary at the top. Entered values are kept after an error. The placeholder for email is &lt;a href="mailto:name@example.com"&gt;name@example.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Assumptions:&lt;/strong&gt; No social sign-in was requested, so none was added.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Acceptance criteria and status:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Valid entries survive a validation error. &lt;em&gt;Manual test:&lt;/em&gt; submitted with a bad password, and name and email were still filled in.&lt;/li&gt;
&lt;li&gt;Each error is announced next to its field. &lt;em&gt;Heuristic review&lt;/em&gt; of the markup only. Not tested with a screen reader.&lt;/li&gt;
&lt;li&gt;Text and background contrast. &lt;em&gt;Automated check:&lt;/em&gt; the contrast check found no failures on the colours used. This does not show the whole form is accessible.&lt;/li&gt;
&lt;li&gt;People complete sign-up without help. &lt;em&gt;Proposed.&lt;/em&gt; Not tested. No user research was done.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Not tested:&lt;/strong&gt; screen reader pass, mobile keyboard behaviour, the server's error responses.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Every line says what it rests on. You can see that mobile keyboard behaviour is the next check, and that nobody has tried the form with real users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters when you build with agents
&lt;/h2&gt;

&lt;p&gt;An agent that sounds confident is easy to trust. The evidence labels give you a quick way to read the handoff. "Proposed" means it hasn't been checked. "Automated check" means one rule ran. "Manual test" means somebody actually walked through the flow.&lt;/p&gt;

&lt;p&gt;The list of untested items also gives you the next prompt: ask for exactly those checks.&lt;/p&gt;

&lt;p&gt;The method doesn't claim this saves time. That hasn't been measured. What it does give you is a handoff note that says where its checks stop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep it proportional
&lt;/h2&gt;

&lt;p&gt;Pick a few criteria that fit the design. The method warns against a fixed score or a universal time or click limit. A copy fix might need one line of status. A new checkout flow needs more. The design record template has a short "Validation status" section for this: the evidence levels reached, what was tested and what wasn't, and open questions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this fits
&lt;/h2&gt;

&lt;p&gt;Deliver and validate is step 6 of six: establish context, choose the visual system, map objects before layout, write a decision record, structure each view, then deliver and validate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;Design Decision Method is a lightweight design decision workflow for developers building interfaces with AI agents. It follows the open Agent Skills format and is MIT licensed. Install paths for Claude Code, Cursor, Gemini CLI, and Codex are in the README.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Docs: &lt;a href="https://frozonfreak.github.io/DesignDecesionMethod/" rel="noopener noreferrer"&gt;https://frozonfreak.github.io/DesignDecesionMethod/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/frozonfreak/DesignDecesionMethod" rel="noopener noreferrer"&gt;https://github.com/frozonfreak/DesignDecesionMethod&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Evidence levels and record format: &lt;a href="https://github.com/frozonfreak/DesignDecesionMethod/blob/main/references/acceptance-and-validation.md" rel="noopener noreferrer"&gt;https://github.com/frozonfreak/DesignDecesionMethod/blob/main/references/acceptance-and-validation.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Design record template: &lt;a href="https://github.com/frozonfreak/DesignDecesionMethod/blob/main/assets/design-record-template.md" rel="noopener noreferrer"&gt;https://github.com/frozonfreak/DesignDecesionMethod/blob/main/assets/design-record-template.md&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>ux</category>
      <category>webdev</category>
      <category>testing</category>
    </item>
    <item>
      <title>Your AI agent forgets why. Give it a decision record</title>
      <dc:creator>FrozonFreak</dc:creator>
      <pubDate>Wed, 04 Nov 2026 05:10:36 +0000</pubDate>
      <link>https://dev.to/frozonfreak/your-ai-agent-forgets-why-give-it-a-decision-record-2k4g</link>
      <guid>https://dev.to/frozonfreak/your-ai-agent-forgets-why-give-it-a-decision-record-2k4g</guid>
      <description>&lt;p&gt;You spend a session getting an order form right. One page, not three steps. A review summary before submit. Delivery date where people actually look for it. Then a new prompt regenerates the form, and the agent goes back to a three-step wizard, because nothing told it why you chose otherwise.&lt;/p&gt;

&lt;p&gt;The agent kept the code. It did not keep the reasons.&lt;/p&gt;

&lt;p&gt;Design Decision Method handles this with step 4 of its workflow: the decision record.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a decision record captures
&lt;/h2&gt;

&lt;p&gt;For a choice that would be costly to undo, the method asks you (or your agent) to record:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;task frequency&lt;/li&gt;
&lt;li&gt;user familiarity&lt;/li&gt;
&lt;li&gt;the cost of a mistake&lt;/li&gt;
&lt;li&gt;the device&lt;/li&gt;
&lt;li&gt;the pattern chosen, and why&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It also asks you to judge effort in a specific way: compare the effort to read, remember, enter, navigate, and recover, rather than counting clicks or visible elements. A three-step flow has fewer fields per screen. That does not make it less effort if people have to go back to check what they entered on step one.&lt;/p&gt;

&lt;p&gt;Two more rules keep the record honest:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cite a source only when it resolves an actual design problem.&lt;/strong&gt; No decorative references.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mark departures.&lt;/strong&gt; When the design departs from a cited source's rule, add one adaptation line: source rule / what this design does instead / why the frequency, familiarity, or error cost requires it. Never present an adaptation as upstream guidance.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A worked example
&lt;/h2&gt;

&lt;p&gt;Say you run a small print shop at example.com, and you ask your agent to build the order form. This is an illustration of how the procedure applies, not a case study or a measured result.&lt;/p&gt;

&lt;p&gt;The decision row looks like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Decision&lt;/th&gt;
&lt;th&gt;Frequency&lt;/th&gt;
&lt;th&gt;Familiarity&lt;/th&gt;
&lt;th&gt;Error cost&lt;/th&gt;
&lt;th&gt;Device&lt;/th&gt;
&lt;th&gt;Pattern chosen&lt;/th&gt;
&lt;th&gt;Reason&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Order form layout&lt;/td&gt;
&lt;td&gt;Occasional, once per order&lt;/td&gt;
&lt;td&gt;Mostly first-time customers&lt;/td&gt;
&lt;td&gt;Medium: a wrong size or quantity means a reprint&lt;/td&gt;
&lt;td&gt;Phone first&lt;/td&gt;
&lt;td&gt;One page, grouped sections, review summary before submit&lt;/td&gt;
&lt;td&gt;Customers need to see size, quantity, and price together to catch mistakes. Splitting them across steps adds recall effort at the point of decision.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If a source you cite recommends a different pattern for this form, that is where an adaptation line goes, in the same three-part form.&lt;/p&gt;

&lt;p&gt;The record also carries a short validation status, because step 6 of the method asks for an honest evidence label on each result: proposed, heuristic review, automated check, manual test, or user research. For this form it might read:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Valid entries survive a validation error: not tested (manual test planned).&lt;/li&gt;
&lt;li&gt;Size, quantity, and price visible together before submit: heuristic review.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Untested stays untested. The record says so instead of implying the form works.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the next prompt does with it
&lt;/h2&gt;

&lt;p&gt;Next week you ask the agent to add a "rush delivery" option. The following prompt starts from that record. The agent can see the form is one page for a reason, and that the reason is error cost, not taste. It adds the option inside the existing structure instead of reinventing the flow.&lt;/p&gt;

&lt;p&gt;A person still checks the result. The method does not measure how many prompts that removes, and whether it saves time has not been measured.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep it small
&lt;/h2&gt;

&lt;p&gt;The decision record is proportional. A small change gets a few lines. The repo's design record template is a scaffold, not mandatory overhead. Save the record with the work (as &lt;code&gt;docs/design/&amp;lt;slug&amp;gt;.md&lt;/code&gt;) when your project keeps design records, or deliver a compact record inline with the change when it does not.&lt;/p&gt;

&lt;p&gt;You do not need a record for every edit either. The method is a shared procedure, not mandatory for every small edit. Record the choices you would be annoyed to re-argue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this fits
&lt;/h2&gt;

&lt;p&gt;The decision record is step 4 of six: establish context, choose the visual system, map objects before layout, write a decision record, structure each view, then deliver and validate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;Design Decision Method is a lightweight design decision workflow for developers building interfaces with AI agents. It follows the open Agent Skills format and is MIT licensed. Install paths for Claude Code, Cursor, Gemini CLI, and Codex are in the README.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Docs: &lt;a href="https://frozonfreak.github.io/DesignDecesionMethod/" rel="noopener noreferrer"&gt;https://frozonfreak.github.io/DesignDecesionMethod/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/frozonfreak/DesignDecesionMethod" rel="noopener noreferrer"&gt;https://github.com/frozonfreak/DesignDecesionMethod&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Design record template: &lt;a href="https://github.com/frozonfreak/DesignDecesionMethod/blob/main/assets/design-record-template.md" rel="noopener noreferrer"&gt;https://github.com/frozonfreak/DesignDecesionMethod/blob/main/assets/design-record-template.md&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>ux</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Tell the browser what each field is for (Web HIG tip #14)</title>
      <dc:creator>FrozonFreak</dc:creator>
      <pubDate>Sun, 01 Nov 2026 08:48:20 +0000</pubDate>
      <link>https://dev.to/frozonfreak/tell-the-browser-what-each-field-is-for-web-hig-tip-14-4nlk</link>
      <guid>https://dev.to/frozonfreak/tell-the-browser-what-each-field-is-for-web-hig-tip-14-4nlk</guid>
      <description>&lt;p&gt;Open your checkout or sign-up form on a phone and tap the email field. If the keyboard has no @ key, or the browser offers nothing to fill in, the form isn't telling the browser what it's asking for. Now try the phone number, the postcode, and the password. Every field that makes someone type by hand is one more chance for a typo or for them to give up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Web HIG tip #14 (Quick #52):&lt;/strong&gt; Use native input types and &lt;code&gt;autocomplete&lt;/code&gt; attributes appropriate to the field purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it matters
&lt;/h2&gt;

&lt;p&gt;The browser already knows a lot about the person using it. It just needs to know which field is which:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;type="email"&lt;/code&gt;, &lt;code&gt;type="tel"&lt;/code&gt;, and &lt;code&gt;type="url"&lt;/code&gt; bring up a keyboard suited to the field on most phones, so nobody has to hunt for @, digits, or a slash&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;autocomplete&lt;/code&gt; tokens let the browser offer the saved name, email, address, or card, so one tap can replace a whole block of typing&lt;/li&gt;
&lt;li&gt;Password managers use &lt;code&gt;autocomplete="current-password"&lt;/code&gt; and &lt;code&gt;"new-password"&lt;/code&gt; as the clearest signal of whether to fill a saved password or offer a new one&lt;/li&gt;
&lt;li&gt;People with motor or cognitive disabilities, and anyone on a small screen, gain the most from typing less&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full HIG covers this in §2.11, Forms Contract. Three rows of its table apply here. Autocomplete: use appropriate &lt;code&gt;autocomplete&lt;/code&gt; tokens for common fields (name, email, address, &lt;code&gt;cc-*&lt;/code&gt;). Password managers: do not block autofill, and use standard input types with &lt;code&gt;autocomplete="current-password"&lt;/code&gt; / &lt;code&gt;"new-password"&lt;/code&gt;. Input types: use semantic types (&lt;code&gt;email&lt;/code&gt;, &lt;code&gt;tel&lt;/code&gt;, &lt;code&gt;url&lt;/code&gt;, &lt;code&gt;number&lt;/code&gt;) for mobile keyboard optimization. Quick #52 is Quick-only guidance, with no separate rule ID, and it pairs with Quick #50: every field still needs a visible label.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Author's note, not part of the HIG:&lt;/em&gt; WCAG 2.2 SC 1.3.5 Identify Input Purpose (Level AA) asks that fields collecting information about the user expose their purpose in code, for the purposes listed in WCAG section 7. The matching &lt;code&gt;autocomplete&lt;/code&gt; tokens are the usual way to do that. &lt;code&gt;one-time-code&lt;/code&gt; isn't on that list.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule of thumb
&lt;/h2&gt;

&lt;p&gt;Ask: &lt;strong&gt;if the browser only had this markup, would it know what each field is for?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This form looks fine, but the browser sees three anonymous text boxes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"f1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Email&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"f1"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"f1"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;autocomplete=&lt;/span&gt;&lt;span class="s"&gt;"off"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"f2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Phone&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"f2"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"f2"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"f3"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Password&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"f3"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"f3"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="na"&gt;autocomplete=&lt;/span&gt;&lt;span class="s"&gt;"off"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Name the purpose and the browser does the rest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Email&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;autocomplete=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;
       &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"name@example.com"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"phone"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Phone&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"phone"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"phone"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"tel"&lt;/span&gt; &lt;span class="na"&gt;autocomplete=&lt;/span&gt;&lt;span class="s"&gt;"tel"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Password&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;
       &lt;span class="na"&gt;autocomplete=&lt;/span&gt;&lt;span class="s"&gt;"new-password"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The labels and layout don't change. The keyboard does, and so does what the browser offers to fill.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do this instead
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Pick the &lt;code&gt;type&lt;/code&gt; from what the field holds: &lt;code&gt;email&lt;/code&gt;, &lt;code&gt;tel&lt;/code&gt;, &lt;code&gt;url&lt;/code&gt;, &lt;code&gt;search&lt;/code&gt;, &lt;code&gt;password&lt;/code&gt;. Use &lt;code&gt;type="number"&lt;/code&gt; only for real quantities you might step up or down. For digit strings that aren't numbers, such as card numbers, security codes and one-time codes, use &lt;code&gt;type="text"&lt;/code&gt; with &lt;code&gt;inputmode="numeric"&lt;/code&gt; and the matching &lt;code&gt;autocomplete&lt;/code&gt; token (&lt;code&gt;cc-number&lt;/code&gt;, &lt;code&gt;cc-csc&lt;/code&gt;, &lt;code&gt;one-time-code&lt;/code&gt;). Add &lt;code&gt;pattern&lt;/code&gt; only when you also show a clear error message. Use &lt;code&gt;inputmode="numeric"&lt;/code&gt; for a postcode only if every postcode you accept is digits-only&lt;/li&gt;
&lt;li&gt;Add an &lt;code&gt;autocomplete&lt;/code&gt; token to every field about the user: &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;email&lt;/code&gt;, &lt;code&gt;tel&lt;/code&gt;, &lt;code&gt;address-line1&lt;/code&gt;, &lt;code&gt;postal-code&lt;/code&gt;, &lt;code&gt;country&lt;/code&gt;, &lt;code&gt;cc-number&lt;/code&gt;, &lt;code&gt;cc-exp&lt;/code&gt;, &lt;code&gt;cc-csc&lt;/code&gt;, &lt;code&gt;username&lt;/code&gt;. Sign-in codes get &lt;code&gt;one-time-code&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;current-password&lt;/code&gt; on sign-in and &lt;code&gt;new-password&lt;/code&gt; on sign-up and password change, so password managers fill or generate correctly&lt;/li&gt;
&lt;li&gt;Don't set &lt;code&gt;autocomplete="off"&lt;/code&gt; to stop autofill on these fields. Browsers often ignore it on sign-in fields anyway. Keep &lt;code&gt;off&lt;/code&gt; for values that are never reused or where a saved suggestion would be wrong, such as a CAPTCHA answer, an in-app search or combobox that shows its own suggestions, or a one-off confirmation code. For a one-time sign-in code, use &lt;code&gt;one-time-code&lt;/code&gt; so the phone can fill it from the SMS. On a form where an admin sets someone else's password, use &lt;code&gt;new-password&lt;/code&gt;, not &lt;code&gt;off&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;If a form asks for two addresses, prefix the tokens: &lt;code&gt;shipping address-line1&lt;/code&gt; and &lt;code&gt;billing address-line1&lt;/code&gt;, &lt;code&gt;shipping postal-code&lt;/code&gt; and &lt;code&gt;billing postal-code&lt;/code&gt;. (&lt;code&gt;street-address&lt;/code&gt; is for a single multi-line &lt;code&gt;&amp;lt;textarea&amp;gt;&lt;/code&gt;.) For more than one shipping address, put a &lt;code&gt;section-&lt;/code&gt; token first, e.g. &lt;code&gt;section-gift shipping postal-code&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Quick check for your app
&lt;/h2&gt;

&lt;p&gt;Open your sign-up, sign-in, and checkout forms (say, example.com/checkout) on a real phone. Tap each field: the keyboard should match it, with @ for email and a keypad for phone and codes. Then let the browser fill the form from a saved profile and check that every value lands in the right box. Sign up with a password manager, sign out, and sign back in: it should offer to save, then fill. A tool can list the &lt;code&gt;autocomplete&lt;/code&gt; attributes, but only filling the form shows whether the right values land in the right fields.&lt;/p&gt;




&lt;p&gt;The Web HIG is an open set of guidelines for how the web should behave, not a component library. Design systems define how it looks. The Web HIG defines how it behaves.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/frozonfreak/webhig/discussions/30" rel="noopener noreferrer"&gt;https://github.com/frozonfreak/webhig/discussions/30&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Hashnode: &lt;a href="https://thewebhig.hashnode.dev/tell-the-browser-what-each-field-is-for-web-hig-tip-14" rel="noopener noreferrer"&gt;https://thewebhig.hashnode.dev/tell-the-browser-what-each-field-is-for-web-hig-tip-14&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Dev.to: &lt;a href="https://dev.to/frozonfreak/tell-the-browser-what-each-field-is-for-web-hig-tip-14-4nlk"&gt;https://dev.to/frozonfreak/tell-the-browser-what-each-field-is-for-web-hig-tip-14-4nlk&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Bluesky: &lt;a href="https://bsky.app/profile/sastha.bsky.social/post/3mzagjjaafs2m" rel="noopener noreferrer"&gt;https://bsky.app/profile/sastha.bsky.social/post/3mzagjjaafs2m&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>html</category>
      <category>a11y</category>
      <category>forms</category>
    </item>
    <item>
      <title>Keep focus inside the dialog (Web HIG tip #13)</title>
      <dc:creator>FrozonFreak</dc:creator>
      <pubDate>Fri, 30 Oct 2026 11:22:59 +0000</pubDate>
      <link>https://dev.to/frozonfreak/keep-focus-inside-the-dialog-web-hig-tip-13-44eo</link>
      <guid>https://dev.to/frozonfreak/keep-focus-inside-the-dialog-web-hig-tip-13-44eo</guid>
      <description>&lt;p&gt;Open a modal on your site, the cookie settings, a confirm step, or a sign-in box, and press Tab a few times. If the focus ring disappears behind the dark overlay and lands on a link in the page underneath, a keyboard user is now working in a part of the page they can't see.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Web HIG tip #13 (Quick #73 / HIG-A11Y-008):&lt;/strong&gt; Modals must trap focus inside and restore focus to the trigger on close.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it matters
&lt;/h2&gt;

&lt;p&gt;A modal says "deal with this first". Focus has to agree with what the screen shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If Tab leaves the dialog, keyboard and switch users end up on controls hidden behind the overlay, and pressing Enter acts on something they can't see&lt;/li&gt;
&lt;li&gt;Screen reader users who move past the dialog's content read the page behind it as if the dialog weren't there&lt;/li&gt;
&lt;li&gt;If focus isn't moved in when the dialog opens, the user has to hunt for it, or doesn't know it opened at all&lt;/li&gt;
&lt;li&gt;If focus isn't put back on close, it usually drops to the top of the page, and the user has to find their place again&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full HIG covers this in §5.3, Keyboard Focus &amp;amp; Interaction. HIG-A11Y-008 (Modal focus containment, severity error) asks for three things: focus moves into the dialog when it opens, Tab and Shift+Tab cycle within it while it's active, and focus returns to the control that opened it when it closes. The spec is explicit that &lt;code&gt;aria-modal="true"&lt;/code&gt; tells assistive technology the dialog is modal but does not itself create a focus trap. The keyboard pattern table adds that Escape closes a dialog. On the WCAG side, this supports 2.4.3 Focus Order (Level A). A trap with no way out would fail 2.1.2 No Keyboard Trap (Level A), so the Escape key and a visible close button are part of the rule, not extras.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule of thumb
&lt;/h2&gt;

&lt;p&gt;Ask: &lt;strong&gt;while the dialog is open, can Tab ever reach something behind it? And when it closes, am I back where I started?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The native &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; element, opened with &lt;code&gt;showModal()&lt;/code&gt;, does most of the work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"open-settings"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Cookie settings&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;dialog&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"settings"&lt;/span&gt; &lt;span class="na"&gt;aria-labelledby=&lt;/span&gt;&lt;span class="s"&gt;"settings-title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h2&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"settings-title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Cookie settings&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"dialog"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    …
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"cancel"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Cancel&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"save"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Save&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dialog&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dialog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;settings&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;trigger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;open-settings&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;trigger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;dialog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;showModal&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;showModal()&lt;/code&gt; makes the rest of the page inert, so Tab can't reach it, moves focus into the dialog, and closes it on Escape. Modern browsers also return focus to the element that was focused before the dialog opened. Test that in the browsers you support rather than assuming it. Tab can still move into the browser's own controls, such as the address bar. That's expected: the rule is about the page behind the dialog.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do this instead
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Prefer &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; with &lt;code&gt;showModal()&lt;/code&gt; over a custom &lt;code&gt;&amp;lt;div role="dialog"&amp;gt;&lt;/code&gt;. If you have to keep a custom modal, set &lt;code&gt;inert&lt;/code&gt; on the page content behind it, move focus in yourself, and handle Escape&lt;/li&gt;
&lt;li&gt;Don't rely on &lt;code&gt;aria-modal="true"&lt;/code&gt; alone. It changes what assistive technology is told, not where Tab goes&lt;/li&gt;
&lt;li&gt;Pick the starting point on purpose: the first field in a form, the title for a long read, the safest button for a destructive confirm&lt;/li&gt;
&lt;li&gt;On close, return focus to the trigger. If the trigger no longer exists, for example the row was deleted, move focus to the next logical place, such as the list or the following row&lt;/li&gt;
&lt;li&gt;Give every dialog a visible close or cancel button as well as Escape&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;show()&lt;/code&gt; or a popover for non-modal UI. A dialog that doesn't block the page shouldn't trap focus either&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Quick check for your app
&lt;/h2&gt;

&lt;p&gt;Open a dialog on example.com using only the keyboard. Check that focus lands inside it. Press Tab and Shift+Tab ten times each: focus should stay in the dialog and never land on the page behind the overlay. Press Escape: the dialog should close and focus should be back on the button you opened it with. Repeat with a screen reader and with your confirm, sign-in, and cookie dialogs. A tool can tell you a dialog has &lt;code&gt;aria-modal&lt;/code&gt;, but only tabbing through shows whether focus actually stays put.&lt;/p&gt;




&lt;p&gt;The Web HIG is an open set of guidelines for how the web should behave, not a component library. Design systems define how it looks. The Web HIG defines how it behaves.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/frozonfreak/webhig/discussions/29" rel="noopener noreferrer"&gt;https://github.com/frozonfreak/webhig/discussions/29&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Hashnode: &lt;a href="https://thewebhig.hashnode.dev/keep-focus-inside-the-dialog-web-hig-tip-13" rel="noopener noreferrer"&gt;https://thewebhig.hashnode.dev/keep-focus-inside-the-dialog-web-hig-tip-13&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Dev.to: &lt;a href="https://dev.to/frozonfreak/keep-focus-inside-the-dialog-web-hig-tip-13-44eo"&gt;https://dev.to/frozonfreak/keep-focus-inside-the-dialog-web-hig-tip-13-44eo&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Bluesky: &lt;a href="https://bsky.app/profile/sastha.bsky.social/post/3mz3oafs7zs2l" rel="noopener noreferrer"&gt;https://bsky.app/profile/sastha.bsky.social/post/3mz3oafs7zs2l&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>a11y</category>
      <category>html</category>
    </item>
  </channel>
</rss>
