<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/css" href="https://www.aaron-gustafson.com/c/feed.min.css" ?><feed xmlns="http://www.w3.org/2005/Atom"
      xmlns:amg="https://www.aaron-gustafson.com.com/amg-dtd/"><title>Aaron Gustafson: Content tagged web standards</title><subtitle>The latest 20 posts and links tagged web standards.</subtitle><id>https://www.aaron-gustafson.com</id><link href="https://www.aaron-gustafson.com/feeds/web-standards.xml" rel="self"/><link href="https://www.aaron-gustafson.com"/><author><name>Aaron Gustafson</name><uri>https://www.aaron-gustafson.com</uri></author><updated>2026-10-09T16:44:38Z</updated><entry><id>https://www.aaron-gustafson.com/notebook/webmcp-should-prioritize-people-over-agents/</id><title type="html"><![CDATA[✍🏻 WebMCP Should Prioritize People over Agents]]></title><link href="https://www.aaron-gustafson.com/notebook/webmcp-should-prioritize-people-over-agents/" rel="alternate" type="text/html" /><published>2026-10-09T16:44:38Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>Apple’s WebKit team recently <a href="https://github.com/WebKit/standards-positions/issues/670#issuecomment-4608432694">opposed the WebMCP proposal</a>, arguing that it creates a separate semantic layer for agents when we should instead improve the shared layers people, assistive technology, and agents already use. <a href="https://cloudfour.com/thinks/improvements-to-web-for-ai-should-benefit-all-users/">Jason Grigsby’s post</a> led me to Apple’s comment, and I share the concerns raised in both. In fact, I’d made a similar case over email back in April, when I first learned about <a href="https://developer.chrome.com/docs/ai/webmcp">WebMCP</a>.</p><p>Apple’s WebKit team put the heart of the issue plainly:</p><blockquote cite="https://github.com/WebKit/standards-positions/issues/670#issuecomment-4608432694"><p>Although browser-integrated agents could struggle to act on interfaces built for humans, we do not think a parallel agent-facing tool layer is the right solution. When a site’s actions are hard for an agent to use, that is a gap in the page’s own semantics, and the fix, in our opinion, is to close it in the platform’s shared layers (HTML and ARIA), where the user, assistive technology, and agents all benefit.</p></blockquote><p>That echoes what I proposed in April. Many of the declarative attributes being discussed for WebMCP duplicate info that’s already available in the markup. I suggested letting a developer opt a form in, then having the browser gather its labels, descriptions, and other details from what’s already there.</p><p>Browsers already derive a great deal of useful info from a well-built interface: labels, descriptions, relationships, states, and more. If we’ve done the work to make a form understandable and operable for people—including people using assistive technology—exposing that form to an agent should build on that work, not require us to encode the same info a second time for a “new” audience. What happened to <a href="https://en.wikipedia.org/wiki/Don't_repeat_yourself">the DRY principle</a>?</p><p>What I’d like to see us do is…</p><ol><li>Write the labels, descriptions, and other semantics for the people using the interface.</li><li>Let the browser draw from that existing info by default when exposing the UI to an agent.</li><li>If the agent genuinely needs more specificity, provide a narrowly scoped, agent-specific override.</li></ol><p>That last bit matters because a label or description written for a person might not give an agent enough info to interact reliably. An override should be an option of last resort, but there needs to be a path for supplying agent-specific instructions when the human-facing ones don’t suffice. It should be an <em>enhancement</em>, though, not a requirement.</p><p>Building on existing semantics would place less of a burden on authors and reinforce accessibility best practices. It would also reduce the likelihood that a control ends up with one description for people and another for agents—and that the two fall out of sync. The risk here is that a parallel semantic layer would lead some developers to lavish attention on agents while neglecting the people who also need to use their sites.</p><p>We should push incentives in the other direction: Writing accessible HTML should light up as much agentic functionality as possible. That would reinforce accessible authoring rather than undermine it.</p><p>There are broader opportunities here, too. I’m particularly interested in how agent capabilities could connect to web app technologies such as <a href="https://www.w3.org/TR/appmanifest/#shortcuts-member">shortcuts</a> and <a href="https://w3c.github.io/web-share-target/">share targets</a>. I’m also curious about the role <a href="https://github.com/webmachinelearning/webmcp/blob/main/docs/service-workers.md">Service Workers</a> might play in this new era.</p><p>Those opportunities come with broader risks, of course. WebKit is right to call out the importance of consent, security, and human oversight when agents can invoke tools that act within authenticated sessions—especially in the background, with no open tab and no person present. These web platform technologies may prove useful for agents, but we’ll need to approach them carefully.</p><p>In his post, Jason connects WebKit’s position to the <a href="https://www.w3.org/TR/design-principles/#priority-of-constituencies">W3C’s Priority of Constituencies</a>, which is the right way to frame this. <a href="https://cloudfour.com/thinks/improvements-to-web-for-ai-should-benefit-all-users/">His discussion</a> is worth reading in full. Wherever agents eventually fit into that order, they shouldn’t leapfrog the people on whose behalf they’re meant to act.</p>]]></content><amg:twitter><![CDATA[If we improve the web for AI agents, we should begin with the semantics authors already provide for people—not create a parallel layer that can leave them behind.]]></amg:twitter><amg:summary><![CDATA[<p>Apple’s WebKit team recently <a href="https://github.com/WebKit/standards-positions/issues/670#issuecomment-4608432694">opposed the WebMCP proposal</a>, arguing that it creates a separate semantic layer for agents when we should instead improve the shared layers people, assistive technology, and agents already use. <a href="https://cloudfour.com/thinks/improvements-to-web-for-ai-should-benefit-all-users/">Jason Grigsby’s post</a> led me to Apple’s comment, and I share the concerns raised in both. In fact, I’d made a similar case over email back in April, when I first learned about <a href="https://developer.chrome.com/docs/ai/webmcp">WebMCP</a>.</p>]]></amg:summary><summary type="html"><![CDATA[<p>Apple’s WebKit team recently <a href="https://github.com/WebKit/standards-positions/issues/670#issuecomment-4608432694">opposed the WebMCP proposal</a>, arguing that it creates a separate semantic layer for agents when we should instead improve the shared layers people, assistive technology, and agents already use. <a href="https://cloudfour.com/thinks/improvements-to-web-for-ai-should-benefit-all-users/">Jason Grigsby’s post</a> led me to Apple’s comment, and I share the concerns raised in both. In fact, I’d made a similar case over email back in April, when I first learned about <a href="https://developer.chrome.com/docs/ai/webmcp">WebMCP</a>.</p>]]></summary><category term="accessibility" /><category term="AI/ML" /><category term="web standards" /><category term="ARIA" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.aaron-gustafson.com/i/share-cards/webmcp-should-prioritize-people-over-agents.jpg" /></entry><entry><id>https://www.aaron-gustafson.com/appearances/podcasts/2026-09-16-aaron-gustafson-pokes-the-fire/</id><title type="html"><![CDATA[🎧 Aaron Gustafson pokes the fire]]></title><link href="https://rss.com/podcasts/fireside-chat/3158004/" rel="alternate" type="text/html" /><published>2026-09-16T00:00:00Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>I joined Steve Faulkner to talk about my path from music journalism to Microsoft, progressive enhancement, web standards, and making AI-generated code more accessible.</p>]]></content><category term="progressive enhancement" /><category term="web standards" /><category term="accessibility" /><category term="AI/ML" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/links/making-keyboard-navigation-effortless/</id><title type="html"><![CDATA[🔗 Making keyboard navigation effortless]]></title><link href="https://www.aaron-gustafson.com/notebook/links/making-keyboard-navigation-effortless/" rel="alternate" type="text/html" /><link href="https://blogs.windows.com/msedgedev/2026/03/05/making-keyboard-navigation-effortless/" rel="related" type="text/html" /><published>2026-04-29T12:05:00Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>I’m very excited by <code>focusgroup</code>: a declarative way to handle a bunch of keyboard-navigation patterns that currently require a lot of brittle JavaScript.</p><p>This is exactly the sort of thing I want to see the platform absorb. We’ve spent years rebuilding common interaction patterns with roving <code>tabindex</code>, focus management, and piles of event handling code. If the browser can take more of that on, we get lighter pages, more consistent experiences, and fewer opportunities for developers to get keyboard support wrong.</p>]]></content><amg:twitter><![CDATA[I’m very excited by `focusgroup`: less bespoke roving-`tabindex` code, more consistent keyboard UX, and a healthier platform overall.]]></amg:twitter><amg:summary><![CDATA[<p>I’m very excited by <code>focusgroup</code>: a declarative way to handle a bunch of keyboard-navigation patterns that currently require a lot of brittle JavaScript.</p>]]></amg:summary><summary type="html"><![CDATA[<p>I’m very excited by <code>focusgroup</code>: a declarative way to handle a bunch of keyboard-navigation patterns that currently require a lot of brittle JavaScript.</p>]]></summary><category term="accessibility" /><category term="HTML" /><category term="web standards" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/links/the-power-of-no-in-internet-standards/</id><title type="html"><![CDATA[🔗 The Power of ‘No’ in Internet Standards]]></title><link href="https://www.aaron-gustafson.com/notebook/links/the-power-of-no-in-internet-standards/" rel="alternate" type="text/html" /><link href="https://www.mnot.net/blog/2026/02/13/no" rel="related" type="text/html" /><published>2026-04-23T12:05:00Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>This is an important piece about where power actually lives on the web: not in the specification itself, but in whether powerful players choose to participate, implement, and ship.</p><p>I also appreciate Mark’s call for more ambition here. Too often, we talk about the web as if it’s destined to be an OS for web apps rather than a public-interest platform in its own right. That’s not inevitable. But getting somewhere better requires recognizing that refusal, delay, and strategic disinterest can be every bit as consequential as formal opposition.</p>]]></content><amg:twitter><![CDATA[A useful reminder that the real power in standards work is often the power to say no.]]></amg:twitter><amg:summary><![CDATA[<p>This is an important piece about where power actually lives on the web: not in the specification itself, but in whether powerful players choose to participate, implement, and ship.</p>]]></amg:summary><summary type="html"><![CDATA[<p>This is an important piece about where power actually lives on the web: not in the specification itself, but in whether powerful players choose to participate, implement, and ship.</p>]]></summary><category term="web standards" /><category term="the web" /><category term="society" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/links/default-isn-t-design/</id><title type="html"><![CDATA[🔗 Default Isn’t Design]]></title><link href="https://www.aaron-gustafson.com/notebook/links/default-isn-t-design/" rel="alternate" type="text/html" /><link href="https://eisenbergeffect.medium.com/default-isnt-design-24df33272abb" rel="related" type="text/html" /><published>2025-10-16T21:22:41Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<blockquote><p>When one approach becomes “how things are done,” we unconsciously defend it even when standards would give us a healthier, more interoperable ecosystem. Psychologists call this reflex System Justification. Naming it helps us steer toward a standards-first future without turning the discussion into a framework war.</p></blockquote><p>This whole piece is an excellent discussion about how tools can become an identity and why that’s a bad thing.</p>]]></content><amg:twitter><![CDATA[👍🏻 “When one approach becomes ‘how things are done,’ we unconsciously defend it even when standards would give us a healthier, more interoperable ecosystem.”]]></amg:twitter><category term="web standards" /><category term="JavaScript" /></entry><entry><id>https://www.aaron-gustafson.com/publications/books/learning-web-design-sixth-edition/</id><title type="html"><![CDATA[📗 Learning Web Design]]></title><link href="https://www.oreilly.com/library/view/learning-web-design/9781098137670/" rel="alternate" type="text/html" /><published>2025-05-01T00:00:00Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>Do you want to build web pages but have no prior experience? This friendly guide is the perfect place to start. You’ll begin at square one, learning how the web and web pages work, and then steadily build from there. By the end of the book, you’ll have the skills to create a simple site with multicolumn pages that adapt for mobile devices.</p>]]></content><category term="accessibility" /><category term="CSS" /><category term="HTML" /><category term="JavaScript" /><category term="progressive enhancement" /><category term="responsive web design" /><category term="web design" /><category term="web development" /><category term="web standards" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/links/web-components-are-not-the-future-they-re-the-present/</id><title type="html"><![CDATA[🔗 Web Components Are Not the Future — They’re the Present]]></title><link href="https://www.aaron-gustafson.com/notebook/links/web-components-are-not-the-future-they-re-the-present/" rel="alternate" type="text/html" /><link href="https://www.abeautifulsite.net/posts/web-components-are-not-the-future-they-re-the-present/" rel="related" type="text/html" /><published>2024-09-27T23:46:46Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>I really appreciated Cory LaViska’s take on #WebComponents here. Especially this bit:</p><blockquote><p>You know what framework I want to use? I want a framework that aligns with the platform, not one that replaces it. I want a framework that values incremental innovation over user lock-in. I want a framework that says it’s OK to break things if it means making the Web a better place for everyone. Yes, that comes at a cost, but almost every good investment does, and I would argue that cost will be less expensive than learning a new framework and rebuilding buttons for the umpteenth time.</p></blockquote>]]></content><amg:twitter><![CDATA[I really appreciated Cory LaViska’s take on #WebComponents here.]]></amg:twitter><category term="web components" /><category term="web standards" /><category term="JavaScript" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/widgets/</id><title type="html"><![CDATA[✍🏻 Widgets!]]></title><link href="https://www.aaron-gustafson.com/notebook/widgets/" rel="alternate" type="text/html" /><published>2023-10-09T22:38:54Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>It was a long time coming, but I finally had a chance to put the work I did on <a href="https://github.com/MicrosoftEdge/MSEdgeExplainers/tree/main/PWAWidgets">a widgets proposal for PWAs</a> into practice on my own site. I’m pretty excited about it!</p><h2 id="where-it-all-started" tabindex="-1"><a class="header-anchor" href="#where-it-all-started" aria-hidden="true">#</a> Where it all started</h2><p>I had <a href="https://web.archive.org/web/20200929174844/https://discourse.wicg.io/t/noodling-on-an-idea-projections-for-web-apps/3900">the original idea for “projections”</a> way back in 2019. Inspired by <a href="https://en.wikipedia.org/wiki/Dashboard_(macOS)#Widget_functions_and_capabilities">OS X’s Dashboard Widgets</a> and <a href="https://en.wikipedia.org/wiki/Adobe_AIR">Adobe AIR</a>, I’d begun to wonder if it might be possible to <em>project</em> a component from a website into those kinds of surfaces. Rather than building a bespoke widget that connected to an API, I thought it made sense to leverage an installed PWA to manage those “projections.” I shared the idea at TPAC that year and got some interest from a broad range of folks, but didn’t have much time to work on the details until a few years later.</p><p>In the intervening time, I kept working through the concept in my head. I mean in an ideal world, the widget would just be a responsive web page, right? But if that were the case, what happens when every widget loads the entirety of React to render their stock ticker? That seemed like a performance nightmare.</p><p>In my gut, I felt like the right way to build things would be to have a standard library of widget templates and to enable devs to flow data into them via a Service Worker. Alex Russell suggested I model the APIs on how Notifications are handled (since they serve a similar function) and I was off to the races.</p><p>I drafted <a href="https://github.com/aarongustafson/pwa-widgets">a substantial proposal for my vision of how PWA widgets should work</a>. Key aspects included:</p><ul><li>A declarative way to define and configure a widget from within the Web App Manifest;</li><li>A progressively enhanced pathway for devs to design a widget that adapts to its host environment, from using predefined templates to using custom templates to full-blown web-based widgets (with rendering akin to an <code>iframe</code>);</li><li>A collection of recommended stock templates that implementors should offer to support most widget types;</li><li>Extensibility to support custom templates using any of a variety of templating languages; and</li><li>A complete suite of tools for managing widgets and any associated business logic within a Service Worker.</li></ul><h2 id="widgets-became-a-reality" tabindex="-1"><a class="header-anchor" href="#widgets-became-a-reality" aria-hidden="true">#</a> Widgets became a reality</h2><p>After continuing to gently push on this idea with colleagues across Microsoft (and beyond), I discovered that the Windows 11 team was looking to open up the new Widget Dashboard to third-party applications. I saw this as an opportunity to turn my idea into a reality. After working my way into the conversation, I made a solid case for why PWAs needed to be a part of that story and… it worked! (It no doubt helped that companies including Meta, Twitter, and Hulu were all invested in PWA as a means of delivering apps for Windows.)</p><p>While the timeline for implementation didn’t allow us to tackle the entirety of my proposal, we did carve out the pieces that made for a compelling MVP. This allowed us to show what’s possible, see how folks use it, and plan for future investment in the space.</p><p>Sadly, it meant tabling two features I really loved:</p><ul><li><strong>Stock/predefined templates</strong>. A library of lightly theme-able, consistent, cross-platform templates based on common data structures (e.g., RSS/Atom, iCal) would make it incredibly simple for devs to build a widget. If implemented well, devs might not even need to write a single line of business logic in their Service Worker as the browser could pick up all of the configuration details from the Manifest.</li><li><strong>Configurable widget instances.</strong> Instead of singleton widgets, these would allow you to define a single widget type and replicate it for different use cases. For example, a widget to follow a social media user’s profile could be defined once and the individual instances could be configured with the specific account to be followed.</li></ul><p>I’m sincerely hopeful these two features eventually make their way to us as I think they truly unlock the power of the widget platform. Perhaps, with enough uptake on the current implementation, we can revisit these in the not-too-distant future.</p><hr><p>To test things out, I decided to build two widgets for this site:</p><ol><li>Latest posts</li><li>Latest links</li></ol><p>Both are largely the same in terms of their setup: They display a list of linked titles from this site.</p><h2 id="designing-my-widget-templates" tabindex="-1"><a class="header-anchor" href="#designing-my-widget-templates" aria-hidden="true">#</a> Designing my widget templates</h2><p>Given that they were going to be largely identical, I made <a href="https://github.com/aarongustafson/aaron-gustafson.com/blob/main/src/static/w/feed.ac.json">a single “feed” template for use in both widgets</a>. The templating tech I used is called <a href="https://adaptivecards.io">Adaptive Cards</a>, which is what Windows 11 uses for rendering.</p><p>Adaptive Card templates are relatively straightforward JSON:</p><pre class="language-json" tabindex="0"><code class="language-json">&amp;#<span class="token number">123</span>;<span class="token property">“type”</span><span class="token operator">:</span><span class="token string">“AdaptiveCard”</span><span class="token punctuation">,</span><span class="token property">“$schema”</span><span class="token operator">:</span><span class="token string">“<a href="http://adaptivecards.io/schemas/adaptive-card.json">http://adaptivecards.io/schemas/adaptive-card.json</a>”</span><span class="token punctuation">,</span><span class="token property">“version”</span><span class="token operator">:</span><span class="token string">“1.6”</span><span class="token punctuation">,</span><span class="token property">“body”</span><span class="token operator">:</span> &amp;#<span class="token number">91</span>;&amp;#<span class="token number">123</span>;<span class="token property">“$data”</span><span class="token operator">:</span><span class="token string">“$&amp;#123;take(items,5)&amp;#125;”</span><span class="token punctuation">,</span><span class="token property">“type”</span><span class="token operator">:</span><span class="token string">“Container”</span><span class="token punctuation">,</span><span class="token property">“items”</span><span class="token operator">:</span> &amp;#<span class="token number">91</span>;&amp;#<span class="token number">123</span>;<span class="token property">“type”</span><span class="token operator">:</span><span class="token string">“TextBlock”</span><span class="token punctuation">,</span><span class="token property">“text”</span><span class="token operator">:</span><span class="token string">“&amp;#91;$&amp;#123;title&amp;#125;&amp;#93;($&amp;#123;url&amp;#125;)”</span><span class="token punctuation">,</span><span class="token property">“wrap”</span><span class="token operator">:</span><span class="token boolean">true</span><span class="token punctuation">,</span><span class="token property">“weight”</span><span class="token operator">:</span><span class="token string">“Bolder”</span><span class="token punctuation">,</span><span class="token property">“spacing”</span><span class="token operator">:</span><span class="token string">“Padding”</span><span class="token punctuation">,</span><span class="token property">“height”</span><span class="token operator">:</span><span class="token string">“stretch”</span>&amp;#<span class="token number">125</span>;&amp;#<span class="token number">93</span>;<span class="token punctuation">,</span><span class="token property">“height”</span><span class="token operator">:</span><span class="token string">“stretch”</span>&amp;#<span class="token number">125</span>;&amp;#<span class="token number">93</span>;<span class="token punctuation">,</span><span class="token property">“backgroundImage”</span><span class="token operator">:</span> &amp;#<span class="token number">123</span>;<span class="token property">“url”</span><span class="token operator">:</span><span class="token string">“<a href="https://www.aaron-gustafson.com/i/background-logo.png">https://www.aaron-gustafson.com/i/background-logo.png</a>”</span><span class="token punctuation">,</span><span class="token property">“verticalAlignment”</span><span class="token operator">:</span><span class="token string">“Bottom”</span><span class="token punctuation">,</span><span class="token property">“horizontalAlignment”</span><span class="token operator">:</span><span class="token string">“Center”</span>&amp;#<span class="token number">125</span>;&amp;#<span class="token number">125</span>;</code></pre><p>What this structure does is:</p><ol><li>Create a container into which I will place the content;</li><li>Extract the first five <code>items</code> from the data being fed into the template (more on that in a moment);</li><li>Loop through each <code>item</code> and<ul><li>create a text block,</li><li>populate its content with Markdown to generate a linked title (using the <code>title</code> and <code>url</code> keys from the <code>item</code> object)</li><li>Set some basic styles to make the text bold, separate the titles a little and make them grow to fill the container; then, finally</li></ul></li><li>Set a background on the widget.</li></ol><p>The way Adaptive Cards work is that they flow JSON data into a template and render that. The variable names in the template map directly to the incoming data structure, so are totally up to you to define. As these particular widgets are feed-driven and <a href="https://www.aaron-gustafson.com/feeds/">this site already supports JSONFeed</a>, I set up the widgets to flow the appropriate feed into each and used the keys that were already there. For reference, here’s a sample JSONFeed <code>item</code>:</p><pre class="language-json" tabindex="0"><code class="language-json"><span class="token punctuation">{</span><span class="token property">“id”</span><span class="token operator">:</span><span class="token string">“…”</span><span class="token punctuation">,</span><span class="token property">“title”</span><span class="token operator">:</span><span class="token string">“…”</span><span class="token punctuation">,</span><span class="token property">“summary”</span><span class="token operator">:</span><span class="token string">“…”</span><span class="token punctuation">,</span><span class="token property">“content_html”</span><span class="token operator">:</span><span class="token string">“…”</span><span class="token punctuation">,</span><span class="token property">“url”</span><span class="token operator">:</span><span class="token string">“…”</span><span class="token punctuation">,</span><span class="token property">“tags”</span><span class="token operator">:</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span><span class="token property">“date_published”</span><span class="token operator">:</span><span class="token string">“…”</span><span class="token punctuation">}</span></code></pre><p>If you want to tinker with Adaptive Cards and make your own, you can do so with <a href="https://adaptivecards.io/designer/">their Designer tool</a>.</p><h3 id="defining-the-widgets-in-the-manifest" tabindex="-1"><a class="header-anchor" href="#defining-the-widgets-in-the-manifest" aria-hidden="true">#</a> Defining the widgets in the Manifest</h3><p>With a basic template created, the next step was to set up the two widgets in my Manifest. As they both function largely the same, I’ll just focus on the definition for one of them.</p><p>First off, defining widgets in the Manifest is done via the <code>widgets</code> member, which is an array (much like <code>icons</code> and <code>shortcuts</code>). Each widget is represented as an object in that array. Here is the definition for the “latest posts” widget:</p><pre class="language-json" tabindex="0"><code class="language-json"><span class="token punctuation">{</span><span class="token property">“name”</span><span class="token operator">:</span><span class="token string">“Latest Posts”</span><span class="token punctuation">,</span><span class="token property">“short_name”</span><span class="token operator">:</span><span class="token string">“Posts”</span><span class="token punctuation">,</span><span class="token property">“tag”</span><span class="token operator">:</span><span class="token string">“feed-posts”</span><span class="token punctuation">,</span><span class="token property">“description”</span><span class="token operator">:</span><span class="token string">“The latest posts from Aaron Gustafson’s blog”</span><span class="token punctuation">,</span><span class="token property">“template”</span><span class="token operator">:</span><span class="token string">“feed”</span><span class="token punctuation">,</span><span class="token property">“ms_ac_template”</span><span class="token operator">:</span><span class="token string">“/w/feed.ac.json”</span><span class="token punctuation">,</span><span class="token property">“data”</span><span class="token operator">:</span><span class="token string">“/feeds/latest-posts.json”</span><span class="token punctuation">,</span><span class="token property">“type”</span><span class="token operator">:</span><span class="token string">“application/json”</span><span class="token punctuation">,</span><span class="token property">“auth”</span><span class="token operator">:</span><span class="token boolean">false</span><span class="token punctuation">,</span><span class="token property">“update”</span><span class="token operator">:</span><span class="token number">21600</span><span class="token punctuation">,</span><span class="token property">“icons”</span><span class="token operator">:</span><span class="token punctuation">[</span><span class="token punctuation">{</span><span class="token property">“src”</span><span class="token operator">:</span><span class="token string">“/i/icons/webicon-rss.png”</span><span class="token punctuation">,</span><span class="token property">“type”</span><span class="token operator">:</span><span class="token string">“image/png”</span><span class="token punctuation">,</span><span class="token property">“sizes”</span><span class="token operator">:</span><span class="token string">“120x120”</span><span class="token punctuation">}</span><span class="token punctuation">]</span><span class="token punctuation">,</span><span class="token property">“screenshots”</span><span class="token operator">:</span><span class="token punctuation">[</span><span class="token punctuation">{</span><span class="token property">“src”</span><span class="token operator">:</span><span class="token string">“/i/screenshots/widget-posts.png”</span><span class="token punctuation">,</span><span class="token property">“sizes”</span><span class="token operator">:</span><span class="token string">“387x387”</span><span class="token punctuation">,</span><span class="token property">“label”</span><span class="token operator">:</span><span class="token string">“The latest posts widget”</span><span class="token punctuation">}</span><span class="token punctuation">]</span><span class="token punctuation">}</span></code></pre><p>Breaking this down:</p><ol><li><code>name</code> and <code>short_name</code> act much like these keys in the root of the Manifest as well as in <code>shortcuts</code>: The <code>name</code> value is used as the name for the widget unless there’s not enough room, in which case <code>short_name</code> is used.</li><li>You can think of <code>tag</code> as analogous to <code>class</code> in HTML sense. It’s a way of labeling a widget so you can easily reference it later. Each widget instance will have a unique id created by the widget service, but that instance (or all instances, if the widget supports multiple instances) can be accessed via the <code>tag</code>. But more on that later.</li><li>The <code>description</code> key is used for marketing the widget within a host OS or digital storefront. It should accurately (and briefly) describe what the widget does.</li><li>The <code>template</code> key is not currently used in the Windows 11 implementation but refers to the expected standard library widget template provided by the system. As a template library is not currently available, the <code>ms_ac_template</code> value is used to provide a URL to get the custom Adaptive Card (hence “ac”) template. The “ms_” prefix is there because it’s expected that this would be a Microsoft-proprietary property. It follows <a href="https://www.w3.org/TR/appmanifest/#proprietary-extensions">the guidance for extending the Manifest</a>.</li><li>The <code>data</code> and <code>type</code> keys define the path to the data that should be fed into the template for rendering by the widget host and the MIME of the data format it’s in. The Windows 11 implementation currently only accepts JSON data, but the design of widgets is set up to allow for this to eventually extend to other standardized formats like RSS, iCal, vCard, and such.</li><li><code>update</code> is an optional configuration member allowing you to set how often you’d like the widget to update, in seconds. Developers currently need to add the logic for implementing this into their Service Worker, but this setup allows the configuration to remain independent of the JavaScript code, making it easier to maintain.</li><li>Finally, <code>icons</code> and <code>screenshots</code> allow us to define how the widget shows up in the widget host and how it is promoted for install.</li></ol><p>When someone installs my site as a PWA, the information about the available widgets gets ingested by the browser. The browser then determines, based on the provided values and its knowledge of the available widget service(s) on the device, which widgets should be offered. On Windows 11, this information is <a href="https://learn.microsoft.com/en-us/windows/apps/develop/widgets/implement-widget-provider-cs#update-the-package-manifest">routed into the AppXManifest that governs how apps are represented in Windows</a>. The Windows 11 widget service can then read in the details about the available widgets and offer them for users to install.</p><figure id="2023-10-09-01"><p><img src="https://www.aaron-gustafson.com/i/posts/2023-10-09/widgets-promotion.gif" alt=""></p><figcaption>An animated capture of Windows 11’s widget promotion surface, showing 2 widgets available from this site’s PWA.</figcaption></figure><h2 id="adding-widget-support-to-my-service-worker" tabindex="-1"><a class="header-anchor" href="#adding-widget-support-to-my-service-worker" aria-hidden="true">#</a> Adding widget support to my Service Worker</h2><p>As I mentioned earlier, all of the plumbing for widgets is done within a Service Worker and is modeled on the Notifications API. I’m not going to exhaustively detail how it all works, but I’ll give you enough detail to get you started.</p><p>First off, widgets are exposed via the <code>self.widgets</code> interface. Most importantly, this interface lets you access and update any instances of a widget connected to your PWA.</p><h3 id="installing-a-widget" tabindex="-1"><a class="header-anchor" href="#installing-a-widget" aria-hidden="true">#</a> Installing a widget</h3><p>When a user chooses to install a widget, that emits a “widgetinstall” event in your Service Worker. You use that to kickoff the widget lifecycle by gathering the template and data needed to instantiate the widget:</p><pre class="language-js" tabindex="0"><code class="language-js">self<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">“widgetinstall”</span><span class="token punctuation">,</span><span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span><span class="token operator">=&gt;</span><span class="token punctuation">{</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string"><code>&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Installing &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;widget&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tag&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;</code></span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>event<span class="token punctuation">.</span><span class="token function">waitUntil</span><span class="token punctuation">(</span><span class="token function">initializeWidget</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>widget<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p>The event argument comes in with details of the specific widget being instantiated (as <code>event.widget</code>). In the code above, you can see I’ve logged the widget’s <code>tag</code> value to the console. I pass the widget information over to my <code>initializeWidget()</code> function and it updates the widget with the latest data and, if necessary, sets up a <a href="https://developer.mozilla.org/docs/Web/API/Web_Periodic_Background_Synchronization_API">Periodic Background Sync</a>:</p><pre class="language-js" tabindex="0"><code class="language-js"><span class="token keyword">async</span><span class="token keyword">function</span><span class="token function">initializeWidget</span><span class="token punctuation">(</span><span class="token parameter">widget</span><span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token keyword">await</span><span class="token function">updateWidget</span><span class="token punctuation">(</span>widget<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">await</span><span class="token function">registerPeriodicSync</span><span class="token punctuation">(</span>widget<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">return</span><span class="token punctuation">;</span><span class="token punctuation">}</span></code></pre><p>The code for my <code>updateWidget()</code> function is as follows:</p><pre class="language-js" tabindex="0"><code class="language-js"><span class="token keyword">async</span><span class="token keyword">function</span><span class="token function">updateWidget</span><span class="token punctuation">(</span><span class="token parameter">widget</span><span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token keyword">const</span> template <span class="token operator">=</span><span class="token keyword">await</span><span class="token punctuation">(</span><span class="token keyword">await</span><span class="token function">fetch</span><span class="token punctuation">(</span>widget<span class="token punctuation">.</span>definition<span class="token punctuation">.</span>msAcTemplate<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">text</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">const</span> data <span class="token operator">=</span><span class="token keyword">await</span><span class="token punctuation">(</span><span class="token keyword">await</span><span class="token function">fetch</span><span class="token punctuation">(</span>widget<span class="token punctuation">.</span>definition<span class="token punctuation">.</span>data<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">text</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">try</span><span class="token punctuation">{</span><span class="token keyword">await</span> self<span class="token punctuation">.</span>widgets<span class="token punctuation">.</span><span class="token function">updateByTag</span><span class="token punctuation">(</span>widget<span class="token punctuation">.</span>definition<span class="token punctuation">.</span>tag<span class="token punctuation">,</span><span class="token punctuation">{</span> template<span class="token punctuation">,</span> data <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">catch</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">{</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string"><code>&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Couldn’t update the widget &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;tag&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;</code></span></span><span class="token punctuation">,</span> e<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">return</span><span class="token punctuation">;</span><span class="token punctuation">}</span></code></pre><p>This function does the following:</p><ol><li>Get the template for this widget</li><li>Get the data to flow into the template</li><li>Use the <code>self.widgets.updateByTag()</code> method to push the <var>template</var> and <var>data</var> to the widget service to update any widget instances connected to the widget’s <code>tag</code>.</li></ol><p>As I mentioned, I also have code in place to take advantage of Periodic Background Sync if/when it’s available and the browser allows my site to do it:</p><pre class="language-js" tabindex="0"><code class="language-js"><span class="token keyword">async</span><span class="token keyword">function</span><span class="token function">registerPeriodicSync</span><span class="token punctuation">(</span><span class="token parameter">widget</span><span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token keyword">let</span> tag <span class="token operator">=</span> widget<span class="token punctuation">.</span>definition<span class="token punctuation">.</span>tag<span class="token punctuation">;</span><span class="token keyword">if</span><span class="token punctuation">(</span><span class="token string">“update”</span><span class="token keyword">in</span> widget<span class="token punctuation">.</span>definition<span class="token punctuation">)</span><span class="token punctuation">{</span>registration<span class="token punctuation">.</span>periodicSync<span class="token punctuation">.</span><span class="token function">getTags</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">tags</span><span class="token punctuation">)</span><span class="token operator">=&gt;</span><span class="token punctuation">{</span><span class="token comment">// only one registration per tag</span><span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">!</span>tags<span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span>tag<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">{</span>periodicSync<span class="token punctuation">.</span><span class="token function">register</span><span class="token punctuation">(</span>tag<span class="token punctuation">,</span><span class="token punctuation">{</span><span class="token literal-property property">minInterval</span><span class="token operator">:</span> widget<span class="token punctuation">.</span>definition<span class="token punctuation">.</span>update<span class="token punctuation">,</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">return</span><span class="token punctuation">;</span><span class="token punctuation">}</span></code></pre><p>This function also receives the widget details and:</p><ol><li>Looks to see if the widget <code>definition</code> (from the Manifest) includes an <code>update</code> member. If it has one, it…</li><li>Checks to see if there’s already a Periodic Background Sync that is registered for this tag. If none exists, it…</li><li>Registers a new Periodic Background Sync using the <code>tag</code> value and a minimum interval equal to the <code>update</code> requested.</li></ol><p>The <code>update</code> member, as you may recall, is the frequency (in seconds) you’d ideally like the widget to be updated. In reality, you’re at the mercy of the browser as to when (or even if) your sync will run, but that’s totally cool as there are other ways to update widgets as well.<sup class="footnote-ref"><a href="#fn1" id="fnref1">1</a></sup></p><h3 id="uninstalling-a-widget" tabindex="-1"><a class="header-anchor" href="#uninstalling-a-widget" aria-hidden="true">#</a> Uninstalling a widget</h3><p>When a user uninstalls a widget, your Service Worker will receive a “widgetuninstall” event. Much like the “widgetinstall” event, the argument contains details about that widget which you can use to clean up after yourself:</p><pre class="language-js" tabindex="0"><code class="language-js">self<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">“widgetuninstall”</span><span class="token punctuation">,</span><span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span><span class="token operator">=&gt;</span><span class="token punctuation">{</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string"><code>&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Uninstalling &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;widget&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tag&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;</code></span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>event<span class="token punctuation">.</span><span class="token function">waitUntil</span><span class="token punctuation">(</span><span class="token function">uninstallWidget</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>widget<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p>Your application may have different cleanup needs, but this is a great time to clean up any unneeded Periodic Sync registrations. Just be sure to check the length of the widget’s <code>instances</code> array (<code>widget.instances</code>) to make sure you’re dealing with the last instance of a given widget <em>before</em> you unregister the sync:</p><pre class="language-js" tabindex="0"><code class="language-js"><span class="token keyword">async</span><span class="token keyword">function</span><span class="token function">uninstallWidget</span><span class="token punctuation">(</span><span class="token parameter">widget</span><span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token keyword">if</span><span class="token punctuation">(</span>widget<span class="token punctuation">.</span>instances<span class="token punctuation">.</span>length <span class="token operator">===</span><span class="token number">1</span><span class="token operator">&amp;&amp;</span><span class="token string">“update”</span><span class="token keyword">in</span> widget<span class="token punctuation">.</span>definition<span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token keyword">await</span> self<span class="token punctuation">.</span>registration<span class="token punctuation">.</span>periodicSync<span class="token punctuation">.</span><span class="token function">unregister</span><span class="token punctuation">(</span>widget<span class="token punctuation">.</span>definition<span class="token punctuation">.</span>tag<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">return</span><span class="token punctuation">;</span><span class="token punctuation">}</span></code></pre><h3 id="refreshing-your-widgets" tabindex="-1"><a class="header-anchor" href="#refreshing-your-widgets" aria-hidden="true">#</a> Refreshing your widgets</h3><p>Widget platforms may periodically freeze your widget(s) to save resources. For example, they may do this when widgets are not visible. To keep your widgets up to date, they will periodically issue a “widgetresume” event. If you’ve modeled your approach on the one I’ve outlined above, you can route this event right through to your <code>updateWidget()</code> function:</p><pre class="language-js" tabindex="0"><code class="language-js">self<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">“widgetresume”</span><span class="token punctuation">,</span><span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span><span class="token operator">=&gt;</span><span class="token punctuation">{</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string"><code>&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Resuming &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;widget&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tag&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;</code></span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>event<span class="token punctuation">.</span><span class="token function">waitUntil</span><span class="token punctuation">(</span><span class="token function">updateWidget</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>widget<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><h3 id="actions" tabindex="-1"><a class="header-anchor" href="#actions" aria-hidden="true">#</a> Actions</h3><p>While I don’t want to get too into the weeds here, I do want to mention that widgets can have predefined user actions as well. These actions result in “widget click” events being sent back to the Service Worker so you can respond to them:</p><pre class="language-js" tabindex="0"><code class="language-js">self<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">“widgetclick”</span><span class="token punctuation">,</span><span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span><span class="token operator">=&gt;</span><span class="token punctuation">{</span><span class="token keyword">const</span> widget <span class="token operator">=</span> event<span class="token punctuation">.</span>widget<span class="token punctuation">;</span><span class="token keyword">const</span> action <span class="token operator">=</span> event<span class="token punctuation">.</span>action<span class="token punctuation">;</span><span class="token keyword">switch</span><span class="token punctuation">(</span>action<span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token comment">// Custom Actions</span><span class="token keyword">case</span><span class="token string">“refresh”</span><span class="token operator">:</span>event<span class="token punctuation">.</span><span class="token function">waitUntil</span><span class="token punctuation">(</span><span class="token function">updateWidget</span><span class="token punctuation">(</span>widget<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">break</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p>For a great example of how a widget can integrate actions, you should check out <a href="https://microsoftedge.github.io/Demos/pwamp/">the demo PWAmp project</a>. <a href="https://github.com/MicrosoftEdge/Demos/blob/main/pwamp/sw-widgets.js">Their Service Worker widget code</a> is worth a read.</p><h2 id="result!" tabindex="-1"><a class="header-anchor" href="#result!" aria-hidden="true">#</a> Result!</h2><p>With all of these pieces in place, I was excited to see my site showing up in the Widget Dashboard in Windows 11.</p><figure id="2023-10-09-02"><p><img src="https://www.aaron-gustafson.com/i/posts/2023-10-09/widgets-in-windows.jpg" alt=""></p><figcaption>A screenshot of Windows 11 showing the Widget Dashboard overlaying the desktop with this site installed as a PWA to the right. The “latest posts” and “latest links” widgets are shown.</figcaption></figure><p>You can view the full source code on GitHub:</p><ul><li><a href="https://github.com/aarongustafson/aaron-gustafson.com/blob/main/src/static/w/feed.ac.json">“Feed” Adaptive Card Template</a></li><li><a href="https://github.com/aarongustafson/aaron-gustafson.com/blob/main/src/static/manifest.json#L158-L237">Widget definitions in the Manifest</a></li><li><a href="https://github.com/aarongustafson/aaron-gustafson.com/blob/main/src/_javascript/serviceworker/widgets.js">Widgets code in my Service Worker</a></li></ul><hr><p>I’m quite hopeful this will be the first of many places PWA-driven widgets will appear. If you’s like to see them supported elsewhere, be sure to tell your browser and OS vendor(s) of choice. The more they hear from their user base that this feature is needed, the more likely we are to see it get implemented in more places.</p><h2 id="addendum%3A-gotchas" tabindex="-1"><a class="header-anchor" href="#addendum%3A-gotchas" aria-hidden="true">#</a> Addendum: Gotchas</h2><p>In wiring this all up, I ran into a few current bugs I wanted to flag so you can avoid them:</p><ul><li>The <code>icons</code> member won’t accept SVG images. This should eventually be fixed, but it was keeping my widgets from appearing as installable.</li><li>The <code>screenshots</code> members can’t be incredibly large. I’m told you should provide square screenshots no larger than 500px ×500px.</li></ul><hr class="footnotes-sep"><section class="footnotes"><h4 class="hidden">Footnotes</h4><ol class="footnotes-list"><li id="fn1" class="footnote-item"><p>Have you checked out <a href="https://developer.mozilla.org/docs/Web/API/Server-sent_events/Using_server-sent_events">Server Events</a>? <a href="#fnref1" class="footnote-backref">↩︎</a></p></li></ol></section>]]></content><amg:twitter><![CDATA[I finally had a chance to put the work I did on a widgets proposal for PWAs into practice on my own site. It’s pretty exciting!]]></amg:twitter><amg:summary><![CDATA[<p>It was a long time coming, but I finally had a chance to put the work I did on <a href="https://github.com/MicrosoftEdge/MSEdgeExplainers/tree/main/PWAWidgets">a widgets proposal for PWAs</a> into practice on my own site. I’m pretty excited about it!</p>]]></amg:summary><summary type="html"><![CDATA[<p>It was a long time coming, but I finally had a chance to put the work I did on <a href="https://github.com/MicrosoftEdge/MSEdgeExplainers/tree/main/PWAWidgets">a widgets proposal for PWAs</a> into practice on my own site. I’m pretty excited about it!</p>]]></summary><category term="progressive web apps" /><category term="experiments" /><category term="JavaScript" /><category term="Microsoft" /><category term="this site" /><category term="user experience" /><category term="web development" /><category term="web standards" /><category term="Windows" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.aaron-gustafson.com/i/share-cards/widgets.jpg" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/enhancing-the-manifest/</id><title type="html"><![CDATA[✍🏻 Enhancing the Manifest]]></title><link href="https://www.aaron-gustafson.com/notebook/enhancing-the-manifest/" rel="alternate" type="text/html" /><published>2021-10-26T18:57:19Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>Since joining the esteemed group of editors maintaining the <a href="https://www.w3.org/TR/appmanifest/">Web App Manifest spec</a> for the <a href="https://www.w3.org/">W3C</a>, I’ve been on the lookout for ways to enhance both web apps themselves—in terms of functionality—and how web apps are represented in app catalogs and digital storefronts. Some of that work is finally gaining traction and I’d love to get your input.</p><p>I briefly presented on a bunch of the different efforts I’m involved with at the <a href="https://www.w3.org/2021/10/TPAC/">W3C’s TPAC</a> yesterday. Here’s a rundown of what I discussed as well as links you can follow if you want to get involved.</p><h2 id="indicating-an-app%E2%80%99s-policies-%26-other-legalese" tabindex="-1"><a class="header-anchor" href="#indicating-an-app%E2%80%99s-policies-%26-other-legalese" aria-hidden="true">#</a> Indicating an app’s policies &amp; other legalese</h2><p>Many app catalogs enable developers to provide links to things like their app’s Privacy Policy and Terms of Use. There is no way, however, to semantically indicate this information in your markup; the closest you can get is <code>link[rel=&quot;license&quot;]</code>, but that’s limited in scope. Rather than propose including more <code>link</code> or <code>meta</code> elements (which would need to be included on <em>every</em> page of your site), we think it makes more sense to roll this information into the Manifest as part of a <code>policies</code> object.</p><p><a href="https://github.com/w3c/manifest-app-info/pull/46">My proposal</a> enables developers to provide URLs for an app’s accessibility statement, content license, and policies governing privacy, security, support, and usage. Developers would be free to include whichever policies their app has and they can be URLs within the app itself or link off to other websites (such as the publisher’s). Here’s what that could look like:</p><pre class="language-json" tabindex="0"><code class="language-json"><span class="token property">“policies”</span><span class="token operator">:</span><span class="token punctuation">{</span><span class="token property">“accessibility”</span><span class="token operator">:</span><span class="token string">“/accessibility”</span><span class="token punctuation">,</span><span class="token property">“usage”</span><span class="token operator">:</span><span class="token string">“/terms”</span><span class="token punctuation">,</span><span class="token property">“privacy”</span><span class="token operator">:</span><span class="token string">“<a href="http://publisher.tld/privacy">http://publisher.tld/privacy</a>”</span><span class="token punctuation">}</span></code></pre><p>So far, this seems to meet the needs of several of the app catalogs I’ve spoken with. I don’t know that browsers will make use of this information, but I could see it being useful in the context of an installed PWA as well as in app listings within the browser UI too.</p><p>In a related effort, I’m pushing for the Microsoft Store to support accessibility statements, which are helpful for explaining application functionality and providing details of any known accessibility limitations within an app.</p><h2 id="identifying-an-app%E2%80%99s-publisher" tabindex="-1"><a class="header-anchor" href="#identifying-an-app%E2%80%99s-publisher" aria-hidden="true">#</a> Identifying an app’s publisher</h2><p>When you’re considering installing any app, you want to know you can trust who’s behind it. Phishing sites are everywhere though, so we have to believe that phishing apps are here too (or not too far off). You want to be able to trust that an app purporting to be from your bank actually is from your bank. With such high stakes, opening up the possibility for an app to identify its publisher is quite dangerous, which is why this proposal has taken a while to germinate.</p><p>From the developer side of things, <a href="https://github.com/w3c/manifest-app-info/pull/47">my proposed implementation</a> is relatively straightforward. First, the app identifies its <code>publisher</code> in the Manifest:</p><pre class="language-json" tabindex="0"><code class="language-json"><span class="token property">“publisher”</span><span class="token operator">:</span><span class="token punctuation">{</span><span class="token property">“name”</span><span class="token operator">:</span><span class="token string">“Organization Name”</span><span class="token punctuation">,</span><span class="token property">“url”</span><span class="token operator">:</span><span class="token string">“<a href="https://organization.tld/">https://organization.tld/</a>”</span><span class="token punctuation">}</span></code></pre><p>The problem is that I could put anything in there, declaring my publisher to be any company or individual. In order to substantiate my app’s claim, the listed publisher needs to claim the app too. The publisher could do this by enumerating the apps it owns in a text file located at <code><a href="https://organization.tld/.well-known/published-web-apps">https://organization.tld/.well-known/published-web-apps</a></code>:</p><pre class="language-txt" tabindex="0"><code class="language-txt"><a href="https://origin1.tld/manifest.json">https://origin1.tld/manifest.json</a><a href="https://origin2.tld/static/manifest.json">https://origin2.tld/static/manifest.json</a></code></pre><p>Instead of using a text file, it may be possible to piggyback on the proposed <a href="https://web.dev/pwa-url-handler/#the-web-app-origin-association-file"><code>web-app-origin-association</code> file</a>, but I’m concerned with <a href="https://github.com/w3c/manifest-app-info/pull/47#issuecomment-952169632">overloading <code>web-app-origin-association</code> and confusing developers</a> who are using it for URL handling.</p><p>Regardless of the mechanism, even with this bi-directional attestation, however, the system still has a gaping security hole: the <code>publisher[&quot;name&quot;]</code> string can’t be validated this way. It would be relatively easy for an attacker to host 2 websites, one of which looks like your bank’s app and the other which merely hosts the <code>published-web-apps</code> file in order to support the bogus app’s claim of being published by your bank.</p><p>This is where <a href="https://github.com/w3c/manifest-app-info/pull/47/files#diff-0eb547304658805aad788d320f10bf1f292797b5e6d745a3bf617584da017051R504-R512">the proposed processing algorithm adds an opaque validation step</a>. Implementors are encouraged to verify that the publisher’s domain actually matches the claimed name. There are a number of services that offer name-to-url (or <em>vice versa</em>) lookup, but an implementor could even have a manual process in place to verify the provided information (which app catalogs do as a regular part of their business).</p><p>In the end, the publisher value will either be a validated name and URL pair or, if validation fails, the web app’s URL host string (e.g., “<a href="http://twitter.com">twitter.com</a>”), which many implementors already use as the publisher name.</p><h2 id="enabling-server-side-content-negotiation" tabindex="-1"><a class="header-anchor" href="#enabling-server-side-content-negotiation" aria-hidden="true">#</a> Enabling server-side content negotiation</h2><p>Though many folks these days focus their efforts on running as much code as possible on the client, there is a lot of value in making significant changes to your app on the server, before responding to a request. We’ve heard a number of use cases from folks building PWAs and have put together three proposals to help address them:</p><ol><li><strong><a href="https://github.com/w3c/manifest-app-info/pull/32">A Client Hint to indicate whether the app is installed</a>.</strong> The <code>Sec-CH-App-Installed</code> header would give a server information about whether or not a user agent is representing the app in an “installed” state. The value would be a boolean <code>true</code> or <code>false</code>. We are discussing <a href="https://github.com/w3c/manifest-app-info/pull/32#issuecomment-825288114">exposing this information via a DOM API as well</a>.</li><li><strong><a href="https://github.com/w3c/manifest/pull/977">A Client Hint to indicate the <code>display</code> mode</a>.</strong> We already have a way of detecting the <code>display</code> mode of a web app using <code>matchMedia()</code> and <a href="https://w3c.github.io/manifest/#the-display-mode-media-feature">CSS’s <code>display-mode</code> media feature</a>, but that is all client side code. The <code>Sec-CH-Display-Mode</code> header would provide the <code>display</code> value (taking into account <a href="https://github.com/w3c/manifest/pull/932"><code>display_override</code></a>) on the server side as well.</li><li><strong><a href="https://github.com/w3c/manifest-app-info/issues/19#issuecomment-858137411">Using the <code>Referer</code> header to indicate installation source</a></strong>. I was working with a partner that wanted to beta their PWA to only people who’d installed their PWA from a specific app catalog. Even if they had supplied a separate Manifest with a unique <code>start_url</code> to that catalog, that would not have allowed them to limit their app’s distribution if the URL was shared. And so we proposed that the browser provide a catalog-associated <code>Referer</code> header when the PWA is first launched (and in the absence of a true referral). This feature has shipped in Edge for PWAs installed via the Microsoft Store and we are encouraging other app catalogs and browsers to consider the same approach.</li></ol><p>It’s worth noting that all of these features can be used for analytics and (potentially) to violate someone’s privacy. That is why we’ve suggested they sit under the umbrella of the <a href="https://developer.chrome.com/docs/privacy-sandbox/overview/">Privacy Sandbox</a>.</p><h2 id="adapting-app-ui-to-user-preferences" tabindex="-1"><a class="header-anchor" href="#adapting-app-ui-to-user-preferences" aria-hidden="true">#</a> Adapting app UI to user preferences</h2><p>Shortly after OSes began rolling out support for color schemes (e.g., “light” and “dark” modes), I saw a need for web apps to be able to adapt to these user preferences. In CSS we have the <a href="https://drafts.csswg.org/mediaqueries-5/#descdef-media-prefers-color-scheme"><code>prefers-color-scheme</code> Media Query</a>, but the Manifest had no way of accounting for these sorts of changes. My initial proposal centered around <a href="https://github.com/w3c/image-resource/issues/26">adding support for Media Queries in the <code>ImageResource</code> spec</a>. Short of having CSS-driven, adaptive SVG web app icons (which I’m also working on), being able to associate an <code>ImageResource</code> bitmap with a Media Query seems like the way to go.</p><p>Here’s what that might look like in the context of the <code>icons</code> member:</p><pre class="language-json" tabindex="0"><code class="language-json"><span class="token property">“icons”</span><span class="token operator">:</span><span class="token punctuation">[</span><span class="token punctuation">{</span><span class="token property">“src”</span><span class="token operator">:</span><span class="token string">“/icons/play-later.png”</span><span class="token punctuation">,</span><span class="token property">“type”</span><span class="token operator">:</span><span class="token string">“image/png”</span><span class="token punctuation">,</span><span class="token property">“size”</span><span class="token operator">:</span><span class="token string">“512x512”</span><span class="token punctuation">,</span><span class="token property">“media”</span><span class="token operator">:</span><span class="token string">“(prefers-color-scheme: light)”</span><span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token punctuation">{</span><span class="token property">“src”</span><span class="token operator">:</span><span class="token string">“/icons/play-later-reverse.png”</span><span class="token punctuation">,</span><span class="token property">“type”</span><span class="token operator">:</span><span class="token string">“image/png”</span><span class="token punctuation">,</span><span class="token property">“size”</span><span class="token operator">:</span><span class="token string">“512x512”</span><span class="token punctuation">,</span><span class="token property">“media”</span><span class="token operator">:</span><span class="token string">“(prefers-color-scheme: dark)”</span><span class="token punctuation">}</span><span class="token punctuation">]</span></code></pre><p>This approach would be applicable wherever <code>ImageResource</code> gets used, which includes app icons, shortcut icons, and screenshots within the context of the Manifest as well as the Notifications and Payment Request APIs.</p><p>Beyond images, however, <a href="https://github.com/w3c/manifest/issues/975">as Jen Simmons pointed out, we need the ability to adapt colors within the Manifest as well</a>. It’s early days in discussing this right now, but we’re thinking about adding a generic means of redefining Manifest keys, based on a particular context. It is going to be a challenge to figure this out because we don’t want to force Manifest authors to redefine an entire nested structure (such as a shortcut) just to swap out a single part of that object. But more on that in a moment.</p><p>Here is a simple example of how this might look:</p><pre class="language-json" tabindex="0"><code class="language-json"><span class="token property">“user_preferences”</span><span class="token operator">:</span><span class="token punctuation">[</span><span class="token punctuation">{</span><span class="token property">“context”</span><span class="token operator">:</span><span class="token string">“(prefers-color-scheme: dark)”</span><span class="token punctuation">,</span><span class="token property">“redefine”</span><span class="token operator">:</span><span class="token punctuation">{</span><span class="token property">“theme_color”</span><span class="token operator">:</span><span class="token string">“#bdbdbd”</span><span class="token punctuation">,</span><span class="token property">“background_color”</span><span class="token operator">:</span><span class="token string">“#000000”</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">]</span><span class="token punctuation">,</span></code></pre><p>Ideally developers would not use this mechanism for redefining their icons, using the <code>ImageResource</code> option instead, but it does open the door to complexity.</p><h2 id="web-app-translations" tabindex="-1"><a class="header-anchor" href="#web-app-translations" aria-hidden="true">#</a> Web app translations</h2><p>Speaking of complexity, have you ever tried to build a web app that is translated into multiple languages? It’s an incredibly challenging task and the Manifest does not make it any easier. At present, the guidance for creating multi-lingual Manifests is to use a separate file for each language or make it a dynamic file that can be adjusted on the server side based on path or query string adjustments.</p><p>I’m currently working on <a href="https://github.com/w3c/manifest/issues/676">a proposal to address this within a single document by introducing a <code>translations</code> member</a>. My proposal offers two approaches, one favoring simpler Manifests, the other more useful for complex Manifests.</p><p>For simple Manifests, translations could be embedded directly within the Manifest file as members of the translations object, keyed by language code:</p><pre class="language-json" tabindex="0"><code class="language-json"><span class="token punctuation">{</span><span class="token property">“name”</span><span class="token operator">:</span><span class="token string">“Good dog”</span><span class="token punctuation">,</span><span class="token property">“description”</span><span class="token operator">:</span><span class="token string">“An app for dogs”</span><span class="token punctuation">,</span><span class="token property">“lang”</span><span class="token operator">:</span><span class="token string">“en”</span><span class="token punctuation">,</span><span class="token property">“translations”</span><span class="token operator">:</span><span class="token punctuation">{</span><span class="token property">“fr”</span><span class="token operator">:</span><span class="token punctuation">{</span><span class="token property">“name”</span><span class="token operator">:</span><span class="token string">“Bon chien”</span><span class="token punctuation">,</span><span class="token property">“description”</span><span class="token operator">:</span><span class="token string">“Une application pour chiens”</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre><p>For complex Manifests that may need to redefine text in shortcuts, screenshot labels, and the like, I proposed enabling translations to be managed in a separate file, offering alternative strings only for the items that need it. Using this approach, the above example could become:</p><pre class="language-json" tabindex="0"><code class="language-json"><span class="token comment">// manifest.json</span><span class="token punctuation">{</span><span class="token property">“name”</span><span class="token operator">:</span><span class="token string">“Good dog”</span><span class="token punctuation">,</span><span class="token property">“description”</span><span class="token operator">:</span><span class="token string">“An app for dogs”</span><span class="token punctuation">,</span><span class="token property">“lang”</span><span class="token operator">:</span><span class="token string">“en”</span><span class="token punctuation">,</span><span class="token property">“translations”</span><span class="token operator">:</span><span class="token punctuation">{</span><span class="token property">“fr”</span><span class="token operator">:</span><span class="token string">“manifest.fr.json”</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment">// manifest.fr.json:</span><span class="token punctuation">{</span><span class="token property">“name”</span><span class="token operator">:</span><span class="token string">“Bon chien”</span><span class="token punctuation">,</span><span class="token property">“description”</span><span class="token operator">:</span><span class="token string">“Une application pour chiens”</span><span class="token punctuation">}</span></code></pre><p>One serious challenge (as with the <code>user_preferences</code> block, discussed above) is how to manage changing individual parts of a complex Manifest component. For example, you would likely need to change the text label of a shortcut, but may not need to change its <code>url</code> or icon. <a href="https://github.com/w3c/manifest/issues/975#issuecomment-870430956">Thomas Steiner suggested</a> that <a href="https://github.com/json-path/JsonPath#getting-started">JSON Path</a> may be a good solution here as it could enable you to do something like</p><pre class="language-json" tabindex="0"><code class="language-json"><span class="token punctuation">{</span><span class="token property">“name”</span><span class="token operator">:</span><span class="token string">“Good dog”</span><span class="token punctuation">,</span><span class="token property">“description”</span><span class="token operator">:</span><span class="token string">“An app for dogs”</span><span class="token punctuation">,</span><span class="token property">“lang”</span><span class="token operator">:</span><span class="token string">“en”</span><span class="token punctuation">,</span><span class="token property">“shortcuts”</span><span class="token operator">:</span><span class="token punctuation">[</span><span class="token punctuation">{</span><span class="token property">“name”</span><span class="token operator">:</span><span class="token string">“Pet Me”</span><span class="token punctuation">,</span><span class="token property">“url”</span><span class="token operator">:</span><span class="token string">“/pet-me/”</span><span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token punctuation">{</span><span class="token property">“name”</span><span class="token operator">:</span><span class="token string">“Feed Me”</span><span class="token punctuation">,</span><span class="token property">“url”</span><span class="token operator">:</span><span class="token string">“/feed-me/”</span><span class="token punctuation">}</span><span class="token punctuation">]</span><span class="token punctuation">,</span><span class="token property">“translations”</span><span class="token operator">:</span><span class="token punctuation">{</span><span class="token property">“fr”</span><span class="token operator">:</span><span class="token punctuation">{</span><span class="token property">“name”</span><span class="token operator">:</span><span class="token string">“Bon chien”</span><span class="token punctuation">,</span><span class="token property">“description”</span><span class="token operator">:</span><span class="token string">“Une application pour chiens”</span><span class="token punctuation">,</span><span class="token property">“$[‘shortcuts’][0][‘name’]”</span><span class="token operator">:</span><span class="token string">“Nourrissez-moi”</span><span class="token punctuation">,</span><span class="token property">“$[‘shortcuts’][1][‘name’]”</span><span class="token operator">:</span><span class="token string">“Caressez-moi”</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre><p>This is still something we are actively working through, but it’s promising for sure.</p><h2 id="consider-getting-involved" tabindex="-1"><a class="header-anchor" href="#consider-getting-involved" aria-hidden="true">#</a> Consider getting involved</h2><p>There is a lot of really interesting work happening in Manifest-land these days. If you’d like to get involved, please follow the links and chime in on the discussions. We’d love to have you!</p>]]></content><amg:summary><![CDATA[<p>Since joining the esteemed group of editors maintaining the <a href="https://www.w3.org/TR/appmanifest/">Web App Manifest spec</a> for the <a href="https://www.w3.org/">W3C</a>, I’ve been on the lookout for ways to enhance both web apps themselves—in terms of functionality—and how web apps are represented in app catalogs and digital storefronts. Some of that work is finally gaining traction and I’d love to get your input.</p>]]></amg:summary><summary type="html"><![CDATA[<p>Since joining the esteemed group of editors maintaining the <a href="https://www.w3.org/TR/appmanifest/">Web App Manifest spec</a> for the <a href="https://www.w3.org/">W3C</a>, I’ve been on the lookout for ways to enhance both web apps themselves—in terms of functionality—and how web apps are represented in app catalogs and digital storefronts. Some of that work is finally gaining traction and I’d love to get your input.</p>]]></summary><category term="progressive web apps" /><category term="web standards" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.aaron-gustafson.com/i/share-cards/enhancing-the-manifest.jpg" /></entry><entry><id>https://www.aaron-gustafson.com/appearances/podcasts/2021-10-15-perspectives-with-aaron-gustafson/</id><title type="html"><![CDATA[🎧 Perspectives with Aaron Gustafson]]></title><link href="https://crossword.fm/podcast/perspectives-with-aaron-gustafson/" rel="alternate" type="text/html" /><published>2021-10-15T00:00:00Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>I joined Luke and Jonathan to talk about the Open Web and share his perspective (and some guidance) with the WordPress community.</p>]]></content><category term="progressive enhancement" /><category term="web standards" /><category term="industry" /></entry><entry><id>https://www.aaron-gustafson.com/speaking-engagements/getting-started-with-pwas/</id><title type="html"><![CDATA[📢 Getting Started with PWAs]]></title><link href="" rel="alternate" type="text/html" /><published>2019-11-14T00:09:00Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>In this workshop, Aaron Gustafson will help you get your web projects super-charged as Progressive Web Apps (PWAs). The workshop will start off with an explanation of what PWAs are (and what they’re not), the use cases for and benefits of building them, and solid approaches to creating them. Then Aaron will walk you, step-by-step, through the process of turning a website into a PWA. Following along, you’ll author Web App Manifests and Service Workers and gain exposure to some next-gen APIs that tie them more deeply into the underlying operating system. By the end of the workshop, you’ll walk away with a functional PWA you can use right away.</p>]]></content><amg:twitter><![CDATA[In this workshop, Aaron Gustafson will help you get your web projects super-charged as Progressive Web Apps (PWAs).]]></amg:twitter><amg:summary><![CDATA[<p>In this workshop, Aaron Gustafson will help you get your web projects super-charged as Progressive Web Apps (PWAs).</p>]]></amg:summary><summary type="html"><![CDATA[<p>In this workshop, Aaron Gustafson will help you get your web projects super-charged as Progressive Web Apps (PWAs).</p>]]></summary><category term="progressive web apps" /><category term="web design" /><category term="web development" /><category term="web standards" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.aaron-gustafson.com/i/talks/getting-started-with-pwas-workshop.png" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/im-running-for-the-w3c-advisory-board/</id><title type="html"><![CDATA[✍🏻 I’m Running for the W3C Advisory Board]]></title><link href="https://www.aaron-gustafson.com/notebook/im-running-for-the-w3c-advisory-board/" rel="alternate" type="text/html" /><published>2019-05-06T23:15:52Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>As many of you know, I’ve been involved in the push for web standards for the better part of two decades. I caught the bug early and have been advocating for their use in pretty much every <a href="/publications/#articles">article</a>, <a href="/publications/#books">book</a>, <a href="/speaking-engagements/">talk, and workshop</a> I’ve created. I’ve also had the great pleasure of helping run the <a href="https://www.webstandards.org/">Web Standards Project (WaSP)</a>, <a href="https://wikipedia.org/wiki/Web_Standards_Project">a group whose impact on the web cannot be understated</a>.<sup class="footnote-ref"><a href="#fn1" id="fnref1">1</a></sup> And so, when a handful of my colleagues reached out to see if I’d consider running for the <a href="https://www.w3.org/2005/10/Process-20051014/organization.html#AB">W3C Advisory Board</a>, I was… well… speechless. What an honor it is to be nominated, especially out of the blue like that!</p><p>You can read <a href="https://www.w3.org/2019/05/02-ab-nominations#ag">my nomination statement on the W3C site</a>, so I won’t spend a lot of time rehashing that. What I will do is make a brief case for why I think I would be a valuable member of this particular board.</p><p><strong>I’m a web developer whose heart belongs to standards.</strong> I may work for Microsoft in Developer Relations, but I started building stuff for the web in 1996 and never stopped. I’ve worked on sites for every kind of business you can imagine—from small mom and pop shops to huge international conglomerates and everything in between. I’ve also held just about every role you can in web projects, from strategist right through to front and back end dev, where the rubber meets the road.</p><p>I think this experience, especially when coupled with my current position at Microsoft—which affords me a lot of time to listen to the challenges faced by the web design and development community—will enable me to bring an “in the trenches” perspective to the W3C. <a href="https://rachelandrew.co.uk/">Rachel Andrew</a> provides similar guidance as <a href="https://fronteers.nl/about">Fronteers</a>’ representative to the W3C and I relish the opportunity to work with her again<sup class="footnote-ref"><a href="#fn2" id="fnref2">2</a></sup> in this capacity. I honestly wish there were more web designers and developers working within the W3C and my goal is to give voice to their concerns and champion their ideas.</p><p><strong>I’m a diplomat <em>and</em> a pragmatist</strong>. Over the years, I’ve participated in varying capacities for a handful of boards and committees. I’ve chaired small town committees (e.g., the Energy Use Task Force in Hamden, Connecticut), been the co-president of a state political party (Green Party of Connecticut), run homeowners associations, and, of course, led the Web Standards Project, to name but a few. In all of these roles—and in my consulting work—I’ve learned how to manage personalities (and politics), set expectations, and get folks to rally together to achieve common goals.</p><p>Anyone who knows me will tell you I am incredibly diplomatic. Perhaps more soo than is warranted sometimes. I believe everyone should be heard, but I’m also unwilling to allow individuals to dominate conversations and drown out other viewpoints. I value diverse opinions and appreciate people who challenge convention. In all interactions, I look for common ground and shared goals. I don’t shy away from uncomfortable conversations and have no problem disagreeing with someone, but I will always do it in a civil and respectful way.</p><p>While idealistic—especially when it comes to the web and standards—I’m also a pragmatist. I want to understand problems from multiple angles and use that knowledge to know which battles are worth fighting and when compromise is necessary. And I always seek to build consensus, which is the W3C way.</p><p><strong>I’ve got experience in non-profit work.</strong> You may not realize it, but the W3C does not actually exist as a legal entity. It’s currently in the process of changing that and becoming a non-profit corporation. The Advisory Board is overseeing that process. When I lived in Connecticut, I helped form a non-profit corporation. I’ve also got experience in grant writing and other non-profit related work. I think I could be a real asset in that regard.</p><hr><p>If you can vote in this election and think I’d make a good member of the Advisory Board, please vote for me. If you can’t vote, but know someone who can, please encourage them to read this and consider voting for me. I’d be ever so grateful for your help.</p><p>Thank you!</p><hr class="footnotes-sep"><section class="footnotes"><h4 class="hidden">Footnotes</h4><ol class="footnotes-list"><li id="fn1" class="footnote-item"><p>Most of the truly impressive and important work was done by the folks who founded the Web Standards Project. I can’t take credit for more than a handful of our activities, but I was honored to have played a bit role in its history. <a href="#fnref1" class="footnote-backref">↩︎</a></p></li><li id="fn2" class="footnote-item"><p>We worked together in the Web Standard Project. <a href="#fnref2" class="footnote-backref">↩︎</a></p></li></ol></section>]]></content><amg:summary><![CDATA[<p>As many of you know, I’ve been involved in the push for web standards for the better part of two decades. I caught the bug early and have been advocating for their use in pretty much every <a href="/publications/#articles">article</a>, <a href="/publications/#books">book</a>, <a href="/speaking-engagements/">talk, and workshop</a> I’ve created. I’ve also had the great pleasure of helping run the <a href="https://www.webstandards.org/">Web Standards Project (WaSP)</a>, <a href="https://wikipedia.org/wiki/Web_Standards_Project">a group whose impact on the web cannot be understated</a>.<sup class="footnote-ref"><a href="#fn1" id="fnref1">1</a></sup> And so, when a handful of my colleagues reached out to see if I’d consider running for the <a href="https://www.w3.org/2005/10/Process-20051014/organization.html#AB">W3C Advisory Board</a>, I was… well… speechless. What an honor it is to be nominated, especially out of the blue like that!</p><hr class="footnotes-sep"><section class="footnotes"><h4 class="hidden">Footnotes</h4><ol class="footnotes-list"><li id="fn1" class="footnote-item"><p>Most of the truly impressive and important work was done by the folks who founded the Web Standards Project. I can’t take credit for more than a handful of our activities, but I was honored to have played a bit role in its history. <a href="#fnref1" class="footnote-backref">↩︎</a></p></li></ol></section>]]></amg:summary><summary type="html"><![CDATA[<p>As many of you know, I’ve been involved in the push for web standards for the better part of two decades. I caught the bug early and have been advocating for their use in pretty much every <a href="/publications/#articles">article</a>, <a href="/publications/#books">book</a>, <a href="/speaking-engagements/">talk, and workshop</a> I’ve created. I’ve also had the great pleasure of helping run the <a href="https://www.webstandards.org/">Web Standards Project (WaSP)</a>, <a href="https://wikipedia.org/wiki/Web_Standards_Project">a group whose impact on the web cannot be understated</a>.<sup class="footnote-ref"><a href="#fn1" id="fnref1">1</a></sup> And so, when a handful of my colleagues reached out to see if I’d consider running for the <a href="https://www.w3.org/2005/10/Process-20051014/organization.html#AB">W3C Advisory Board</a>, I was… well… speechless. What an honor it is to be nominated, especially out of the blue like that!</p><hr class="footnotes-sep"><section class="footnotes"><h4 class="hidden">Footnotes</h4><ol class="footnotes-list"><li id="fn1" class="footnote-item"><p>Most of the truly impressive and important work was done by the folks who founded the Web Standards Project. I can’t take credit for more than a handful of our activities, but I was honored to have played a bit role in its history. <a href="#fnref1" class="footnote-backref">↩︎</a></p></li></ol></section>]]></summary><category term="web standards" /><category term="career" /><category term="W3C" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.aaron-gustafson.com/i/share-cards/im-running-for-the-w3c-advisory-board.jpg" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/links/web-developer-representation-in-w3c/</id><title type="html"><![CDATA[🔗 Web Developer Representation in W3C]]></title><link href="https://www.aaron-gustafson.com/notebook/links/web-developer-representation-in-w3c/" rel="alternate" type="text/html" /><link href="https://alistapart.com/article/web-developer-representation-in-w3c" rel="related" type="text/html" /><published>2018-09-27T18:46:41Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>One thing I’ve always felt was missing from the W3C process was input from real web designers and developers. Sure, a handful of us have been tapped to join specific workign groups as “invited experts,” but they have been few and far between. And few designers and developers have the piles of cash laying about that are required to join the W3C.</p><p>Over the years, I’d hoped to see an organization like the World Organization of Webmasters or the Web Standards Project—both of which I helped steer in some capacity­ at varying times—would step up an fill this suprising gap, but alas that never happened. And so I am so thankful to see the Fronteers folks (a web design community in the Netherlands) considering formally joining the W3C to fill this role. And I’m even more excited that Rachel Andrew is their first choice to act on our behalf.</p><p>I’m hopeful the Fronteers community will vote in favor of this so we can get a few of our own advocates on key committees.</p>]]></content><amg:twitter><![CDATA[Fronteers may join the W3C to advocate for web developers everywhere]]></amg:twitter><category term="web standards" /></entry><entry><id>https://www.aaron-gustafson.com/appearances/podcasts/2018-09-24-wasp-and-pwas/</id><title type="html"><![CDATA[🎧 WaSP and PWAs]]></title><link href="https://shoptalkshow.com/episodes/330-aaron-gustafson-wasp-pwas/" rel="alternate" type="text/html" /><published>2018-09-24T00:00:00Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>Dave, Chris, and I talked at length about the Web Standards Project (WaSP), the web, and progressive web apps (PWAs).</p>]]></content><category term="progressive web apps" /><category term="web standards" /><category term="industry" /></entry><entry><id>https://www.aaron-gustafson.com/publications/articles/the-story-of-css-grid-from-its-creators/</id><title type="html"><![CDATA[📄 The Story of CSS Grid, from Its Creators]]></title><link href="https://alistapart.com/article/the-story-of-css-grid-from-its-creators" rel="alternate" type="text/html" /><published>2017-10-19T00:00:00Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>Designers have used grids for centuries. And after more than 20 years of waiting, they are finally here for the browser. This is the story of CSS Grid. It took a lot of people in the right place and at the right time to make it happen.</p>]]></content><category term="CSS" /><category term="web standards" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/pondering-fallback-content/</id><title type="html"><![CDATA[✍🏻 Pondering fallback content]]></title><link href="https://www.aaron-gustafson.com/notebook/pondering-fallback-content/" rel="alternate" type="text/html" /><published>2017-03-31T00:00:50Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>I don’t remember what got it stuck in my craw, but I’ve been thinking a bit about HTML fallbacks of late. If you’re unfamiliar, consider the following <code>picture</code> element:</p><pre class="language-html" tabindex="0"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>picture</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>source</span><span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">“</span>image/webp<span class="token punctuation">”</span></span><span class="token attr-name">srcset</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">“</span>/i/j/r.webp<span class="token punctuation">”</span></span><span class="token punctuation">/&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span><span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">“</span>/i/j/r.jpg<span class="token punctuation">”</span></span><span class="token attr-name">alt</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">“</span><span class="token punctuation">”</span></span><span class="token punctuation">/&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>picture</span><span class="token punctuation">&gt;</span></span></code></pre><p>This <code>picture</code> element has two children: one <code>source</code> referencing a WebP image and an <code>img</code> element referencing a JPG. This pattern demonstrates how <code>picture</code> elements must be built in order to validate, but it also reinforces a best practice that uses the fault tolerant nature of HTML parsers to guarantee every user gets something.</p><p>In very simplistic terms, here’s what happens when a browser encounters this markup:</p><ol><li>The browser recognizes the <code>picture</code> element and begins parsing its content to determine how to render it, or</li><li>The browser doesn’t recognize the <code>picture</code> and ignores it, moving inside to look for any elements it might recognize.</li></ol><p>In practical terms, this markup delivers two potential experiences. Older browsers that haven’t implemented <code>picture</code> get the JPG image. Newer browsers that have implemented <code>picture</code> get either the WebP (if they support that format) or the JPG (if they don’t). In this scenario, the <code>img</code> element can be (and often is) referred to as “fallback content”.</p><p>So why have I been thinking about fallback content? Well, I’ve been thinking a lot about media formats and what happens when a user’s browser encounters a <code>video</code>, <code>audio</code>, or <code>picture</code> element that only includes <a href="https://developer.mozilla.org/docs/Web/HTML/Supported_media_formats">formats it doesn’t support</a>. For instance: A <code>video</code> element that only offers an AVI <code>source</code> is highly unlikely to be playable in any browser.<sup class="footnote-ref"><a href="#fn1" id="fnref1">1</a></sup></p><pre class="language-html" tabindex="0"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>video</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>source</span><span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">“</span>my.avi<span class="token punctuation">”</span></span><span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">“</span>video/avi<span class="token punctuation">”</span></span><span class="token punctuation">/&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>video</span><span class="token punctuation">&gt;</span></span></code></pre><p>So what happens when most browsers encounter this markup? Nothing. Blank space. If you added a poster, that would be shown, but the user gets no video controls and receives no indication that it should even be video content.</p><p>This is the correct behavior, <a href="https://html.spec.whatwg.org/#the-video-element">according to the spec</a>:</p><blockquote><p>When no video data is available (the element’s <code>readyState</code> attribute is either <code>HAVE_NOTHING</code>, or <code>HAVE_METADATA</code> but no video data has yet been obtained at all, or the element’s <code>readyState</code> attribute is any subsequent value but the media resource does not have a video channel) … The <code>video</code> element represents its poster frame, if any, or else transparent black with no intrinsic dimensions.</p></blockquote><p>It’s also a pretty crappy user experience if you ask me. Now it’s worth noting that the spec does allow for a better experience, but it doesn’t require it:</p><blockquote><p>User agents that cannot render the video may instead make the element represent a link to an external video playback utility or to the video data itself.</p></blockquote><p>That would offer a much better experience. Of course, since it’s not a requirement for standards-compliance, it’s not guaranteed. In fact, I have yet to encounter a browser that provides that kind of affordance. So if we want our users to receive that kind of a fallback, we need to it using a tool we do control: HTML. Here’s a stab at what that might look like:</p><pre class="language-html" tabindex="0"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>video</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>source</span><span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">“</span>my.avi<span class="token punctuation">”</span></span><span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">“</span>video/avi<span class="token punctuation">”</span></span><span class="token punctuation">/&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span>Your browser doesn’t support AVI, but you can<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span><span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">“</span>my.avi<span class="token punctuation">”</span></span><span class="token attr-name">download</span><span class="token punctuation">&gt;</span></span>download this movie<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>video</span><span class="token punctuation">&gt;</span></span></code></pre><p>Given how fallbacks work, you might expect a browser to offer up the inner paragraph and download link when it realizes there is no video data it can play. Sadly that’s not the case. They all still display either an empty space or the <code>poster</code> image.<sup class="footnote-ref"><a href="#fn2" id="fnref2">2</a></sup></p><h2 id="a-better-way%3F" tabindex="-1"><a class="header-anchor" href="#a-better-way%3F" aria-hidden="true">#</a> A better way?</h2><p>Obviously the paragraph fallback would be best, but I have no concept of how difficult that would be to do. So what if browsers did expose <em>our</em> fallback content when the only media types on offer are ones they don’t support?</p><p>It seems like this would offer a number of benefits:</p><ol><li>Authors without the requisite skill or knowledge necessary to transcode media into the various formats necessary for cross-browser support would not be penalized (nor would their users);</li><li>CMS developers could provide a standardized, expected fallback pattern without worrying that content contributors might only upload one format that isn’t universally supported; and</li><li>It would allow these elements to be more forward <em>and</em> backward compatible—newer media formats could be rolled out easily without sacrificing a page’s usability in non-supporting browsers.</li></ol><p>That last one is the biggie for me. We want to support as many users as possible, now and well into the future. This might be a way to reliably do that.</p><p>I <a href="https://twitter.com/aarongustafson/status/847475329065041921">took a Twitter Poll</a> to gauge your thoughts, but that was just a yes/no. I’d love to know your detailed thoughts on this. Is there be any downside to this approach?</p><hr class="footnotes-sep"><section class="footnotes"><h4 class="hidden">Footnotes</h4><ol class="footnotes-list"><li id="fn1" class="footnote-item"><p>Safari is the only possible exception here, and that’s only if QuickTime supports the particular CODEC that was used to create the AVI in the first place. Pretty slim odds. <a href="#fnref1" class="footnote-backref">↩︎</a></p></li><li id="fn2" class="footnote-item"><p>In case you’re interested, <a href="http://codepen.io/aarongustafson/pen/BWVbLG">I put together a CodePen walking through some of these scenarios</a>. <a href="#fnref2" class="footnote-backref">↩︎</a></p></li></ol></section>]]></content><amg:summary><![CDATA[<p>I don’t remember what got it stuck in my craw, but I’ve been thinking a bit about HTML fallbacks of late.</p>]]></amg:summary><summary type="html"><![CDATA[<p>I don’t remember what got it stuck in my craw, but I’ve been thinking a bit about HTML fallbacks of late.</p>]]></summary><category term="HTML" /><category term="web standards" /><category term="progressive enhancement" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.aaron-gustafson.com/i/share-cards/pondering-fallback-content.jpg" /></entry><entry><id>https://www.aaron-gustafson.com/publications/articles/developing-dependency-awareness/</id><title type="html"><![CDATA[📄 Developing Dependency Awareness]]></title><link href="https://www.smashingmagazine.com/2016/05/developing-dependency-awareness/" rel="alternate" type="text/html" /><published>2016-05-23T00:00:00Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>As web designers &amp; developers, we take a lot for granted and it can get us into trouble.</p>]]></content><category term="HTML" /><category term="web standards" /><category term="progressive enhancement" /></entry><entry><id>https://www.aaron-gustafson.com/speaking-engagements/planning-adaptive-interfaces/</id><title type="html"><![CDATA[📢 Planning Adaptive Interfaces]]></title><link href="" rel="alternate" type="text/html" /><published>2016-03-03T13:00:00Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>How do you plan for the unknown? The answer is obvious—you can’t—but that’s not a bad thing. Unknowns can be scary, but they also create opportunity.</p><p>On the web, it’s tempting to focus our effort around what we know (or think we know) about our customers based on analytics data we’re collecting and our own experience of the web. Similarly, we often get hung up on trying to give every customer the exact same experience of our product. What we need to realize, however, is that analytics and anecdotal knowledge only get you so far. Our customers’ access and experience of the web is highly variable, deeply personal, and, more often than not, completely out of our control.</p><p>But take heart, all is not lost. By being flexible in our approach and embracing the unknown, we can create user experiences that are intended to vary from device to device, browser to browser, and network to network.</p><p>In this workshop, I explain the ins and outs of crafting rich web experiences that adapt to the capabilities and peculiarities of our customers and their devices, while maintaining your sanity in the process. You’ll leave with:</p><ul><li>an understanding of the challenges (and possibilities) presented by the wide range of browsers and devices being used to access the web;</li><li>a fresh perspective on interface design, grounded in the progressive enhancement philosophy;</li><li>ideas around how to tailor experiences based on device capabilities;</li><li>solid strategies for determining how common UI components can be re-imagined in an adaptive fashion; and</li><li>a practical knowledge of how HTML, CSS, and JavaScript can be deployed in the service of adaptive user interfaces.</li></ul>]]></content><amg:twitter><![CDATA[How do you plan for the unknown? The answer is obvious—you can’t—but that’s not a bad thing. Unknowns can be scary, but they also create opportunity.]]></amg:twitter><amg:summary><![CDATA[<p>How do you plan for the unknown? The answer is obvious—you can’t—but that’s not a bad thing. Unknowns can be scary, but they also create opportunity.</p>]]></amg:summary><summary type="html"><![CDATA[<p>How do you plan for the unknown? The answer is obvious—you can’t—but that’s not a bad thing. Unknowns can be scary, but they also create opportunity.</p>]]></summary><category term="progressive enhancement" /><category term="HTML" /><category term="CSS" /><category term="JavaScript" /><category term="web standards" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.aaron-gustafson.com/i/talks/planning-adaptive-interfaces.png" /></entry><entry><id>https://www.aaron-gustafson.com/appearances/podcasts/2015-10-05-nighthacking-from-oredev/</id><title type="html"><![CDATA[🎧 NightHacking from Øredev]]></title><link href="https://www.youtube.com/watch?v=XCq0wzwQF-A" rel="alternate" type="text/html" /><published>2015-10-05T00:00:00Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>Fredrik and I talk about web standards, progressive enhancement, and more, live at Øredev.</p>]]></content><category term="web standards" /><category term="progressive enhancement" /></entry><entry><id>https://www.aaron-gustafson.com/publications/articles/establishing-web-standards/</id><title type="html"><![CDATA[📄 Establishing Web Standards]]></title><link href="https://medium.com/net-magazine/establishing-web-standards-12f7f4308982#.c6mvi7ijn" rel="alternate" type="text/html" /><published>2015-08-01T00:00:00Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>How an idea becomes a specification at the W3C.</p>]]></content><category term="web standards" /></entry></feed>