﻿<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Modern Web Weekly]]></title><description><![CDATA[A weekly update on Progressive Web Apps (PWA), Web Components and new features of the modern web platform, tested and explained in plain English]]></description><link>https://modernwebweekly.substack.com</link><image><url>https://substackcdn.com/image/fetch/$s_!zlvL!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4eca4d82-112d-48ce-99ab-dcf5b2822ac0_256x256.png</url><title>Modern Web Weekly</title><link>https://modernwebweekly.substack.com</link></image><generator>Substack</generator><lastBuildDate>Fri, 19 Jun 2026 02:14:19 GMT</lastBuildDate><atom:link href="https://modernwebweekly.substack.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Danny Moerkerke]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[modernwebweekly@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[modernwebweekly@substack.com]]></itunes:email><itunes:name><![CDATA[Danny Moerkerke]]></itunes:name></itunes:owner><itunes:author><![CDATA[Danny Moerkerke]]></itunes:author><googleplay:owner><![CDATA[modernwebweekly@substack.com]]></googleplay:owner><googleplay:email><![CDATA[modernwebweekly@substack.com]]></googleplay:email><googleplay:author><![CDATA[Danny Moerkerke]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Out-of-Order Streaming HTML Is Coming To A Browser Near You]]></title><description><![CDATA[The modern web, tested and explain in plain English]]></description><link>https://modernwebweekly.substack.com/p/out-of-order-streaming-html-is-coming</link><guid isPermaLink="false">https://modernwebweekly.substack.com/p/out-of-order-streaming-html-is-coming</guid><dc:creator><![CDATA[Danny Moerkerke]]></dc:creator><pubDate>Fri, 05 Jun 2026 12:55:33 GMT</pubDate><enclosure url="https://images.unsplash.com/photo-1683322499436-f4383dd59f5a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMnx8bmV0d29ya3xlbnwwfHx8fDE3ODAzOTgxNjl8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1683322499436-f4383dd59f5a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMnx8bmV0d29ya3xlbnwwfHx8fDE3ODAzOTgxNjl8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1683322499436-f4383dd59f5a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMnx8bmV0d29ya3xlbnwwfHx8fDE3ODAzOTgxNjl8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1683322499436-f4383dd59f5a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMnx8bmV0d29ya3xlbnwwfHx8fDE3ODAzOTgxNjl8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1683322499436-f4383dd59f5a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMnx8bmV0d29ya3xlbnwwfHx8fDE3ODAzOTgxNjl8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1683322499436-f4383dd59f5a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMnx8bmV0d29ya3xlbnwwfHx8fDE3ODAzOTgxNjl8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1683322499436-f4383dd59f5a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMnx8bmV0d29ya3xlbnwwfHx8fDE3ODAzOTgxNjl8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" width="4507" height="3004" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1683322499436-f4383dd59f5a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMnx8bmV0d29ya3xlbnwwfHx8fDE3ODAzOTgxNjl8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:3004,&quot;width&quot;:4507,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;a bunch of blue wires connected to each other&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="a bunch of blue wires connected to each other" title="a bunch of blue wires connected to each other" srcset="https://images.unsplash.com/photo-1683322499436-f4383dd59f5a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMnx8bmV0d29ya3xlbnwwfHx8fDE3ODAzOTgxNjl8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1683322499436-f4383dd59f5a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMnx8bmV0d29ya3xlbnwwfHx8fDE3ODAzOTgxNjl8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1683322499436-f4383dd59f5a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMnx8bmV0d29ya3xlbnwwfHx8fDE3ODAzOTgxNjl8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1683322499436-f4383dd59f5a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMnx8bmV0d29ya3xlbnwwfHx8fDE3ODAzOTgxNjl8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@scottrodgerson">Scott Rodgerson</a> on <a href="https://unsplash.com">Unsplash</a></figcaption></figure></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://modernwebweekly.substack.com/p/out-of-order-streaming-html-is-coming?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://modernwebweekly.substack.com/p/out-of-order-streaming-html-is-coming?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><p><strong>Modern Web Weekly #74</strong></p><p>Did you ever wish you could serve some parts of your HTML earlier or later than others? </p><p>Imagine your web app has a large dropdown menu at the top of the page. Since HTML is delivered in a top-to-bottom fashion, the HTML after this menu won&#8217;t be available until the HTML for the menu is downloaded and parsed, but the user won&#8217;t be able to use the menu until the page becomes interactive. The menu HTML might as well be delivered later so the rest of the page is available sooner.</p><p>Or how about a single-page app with a page shell. This shell is delivered in the first request, and then JavaScript is needed to plug in all other HTML.</p><p>Wouldn&#8217;t it be nice if you could do this in one request and choose the order in which the HTML parts are delivered, regardless of their order in the source document?</p><p>Well, now you can! &#128170;</p><h4>Out-of-order streaming HTML</h4><p>There have already been <a href="https://lamplightdev.com/blog/2024/01/10/streaming-html-out-of-order-without-javascript/">some experiments</a> with out-of-order streaming HTML that use Declarative Shadow DOM and <code>&lt;slot&gt;</code> elements to stream HTML out of order:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;html&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-html">&lt;div&gt;
  &lt;template shadowrootmode="open"&gt;
    &lt;header&gt;Header&lt;/header&gt;
    &lt;main&gt;
      &lt;slot name="content"&gt;&lt;/slot&gt;
    &lt;/main&gt;
    &lt;footer&gt;Footer&lt;/footer&gt;
  &lt;/template&gt;

  &lt;div slot="content"&gt;
    This div will be rendered inside the slot above without JavaScript.
  &lt;/div&gt;
&lt;/div&gt;</code></pre></div><p>In the above example, <code>&lt;slot name=&#8221;content&#8221;&gt;&lt;/slot&gt;</code> will be replaced with  <code>&lt;div slot=&#8221;content&#8221;&gt;</code> as soon as it&#8217;s available in the browser, without any JavaScript.</p><p>This is a very neat feature, but it only works with Shadow DOM.</p><p>In Chrome 148, with experimental web features enabled (visit chrome://flags/#enable-experimental-web-platform-features), you can now use <a href="https://github.com/WICG/declarative-partial-updates">Declarative Partial Updates</a> that enable web apps to stream HTML into parts of the document, regardless of the order in which it&#8217;s delivered by the server. </p><p>For example, web apps can now download the most important HTML first, even when this is not at the top of the HTML document.</p><h4>How it works</h4><p>Streaming out-of-order HTML is called &#8220;patching&#8221;. It means you can interleave content for multiple locations in an HTML document in one HTML stream, and the content doesn&#8217;t have to be streamed in the order in which it&#8217;s located in the document, hence &#8220;out-of-order streaming&#8221;.</p><p>It works by putting <strong>processing instructions</strong> in an HTML document that function as the insertion points where the content will be streamed into:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;html&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-html">&lt;div&gt;
  &lt;?marker name="content"&gt;
&lt;/div&gt;

&lt;template for="content"&gt;
  &lt;p&gt;Here is the content that will be inserted.&lt;/p&gt;
&lt;/template&gt;</code></pre></div><p>In the above example, the processing instruction <code>&lt;?marker name=&#8221;content&#8221;&gt;</code> will be replaced with the contents of the <code>&lt;template&gt;</code>.</p><p>The template will look up the processing instruction with a <code>name</code> attribute that corresponds to its <code>for</code> attribute and replace it with its contents.</p><p>The above example will end up like this:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;html&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-html">&lt;div&gt;
  &lt;p&gt;Here is the content that will be inserted.&lt;/p&gt;
&lt;/div&gt;</code></pre></div><p>In addition to <code>&lt;?marker&gt;</code>, you can also use the <code>&lt;?start&gt;</code> and <code>&lt;?end&gt;</code> markers that enable you to add temporary content that will be displayed until it&#8217;s replaced with the contents of the corresponding <code>&lt;template&gt;</code> element:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;html&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-html">&lt;div&gt;
  &lt;?start name="list"&gt;
  Loading&#8230;
  &lt;?end&gt;
&lt;/div&gt;

...

&lt;template for="list"&gt;
  &lt;ul&gt;
    &lt;li&gt;Item 1&lt;/li&gt;
    &lt;li&gt;Item 2&lt;/li&gt;
    &lt;li&gt;Item 3&lt;/li&gt;
  &lt;/ul&gt;
&lt;/template&gt;</code></pre></div><p>In this example, &#8220;Loading&#8230;&#8221; is displayed inside the <code>&lt;div&gt;</code> until it&#8217;s replaced with the list inside the <code>&lt;template&gt;</code> element.</p><p>This would enable you to first stream a skeleton HTML page with a loading screen, and after that you could append the list HTML, which is maybe fetched from an external API that takes longer to load.</p><p>Normally, you would first serve the app shell, then fetch the list with the API data in a separate request, and then use JavaScript to insert the list HTML. With patching, you can do everything in a single HTML response without having to use JavaScript to insert the list.</p><p>You can even add processing instructions in the templates to enable updates in multiple steps. Imagine that the list in the previous example is loaded in chunks instead of fully in one request. By including processing instructions in the templates, you can now continuously update the list:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;html&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-html">&lt;div&gt;
  &lt;?start name="list"&gt;
  Loading&#8230;
  &lt;?end&gt;
&lt;/div&gt;

...

&lt;template for="list"&gt;
  &lt;ul&gt;
    &lt;li&gt;Item 1&lt;/li&gt;
    &lt;?marker name="items"&gt;
  &lt;/ul&gt;
&lt;/template&gt;

&lt;template for="items"&gt;
  &lt;li&gt;Item 2&lt;/li&gt;
  &lt;li&gt;Item 3&lt;/li&gt;
  &lt;?marker name="items"&gt;
&lt;/template&gt;

&lt;template for="items"&gt;
  &lt;li&gt;Item 4&lt;/li&gt;
  &lt;?marker name="items"&gt;
&lt;/template&gt;</code></pre></div><p>This results in the following HTML:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;html&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-html">&lt;div&gt;
  &lt;ul&gt;
    &lt;li&gt;Item 1&lt;/li&gt;
    &lt;li&gt;Item 2&lt;/li&gt;
    &lt;li&gt;Item 3&lt;/li&gt;
    &lt;li&gt;Item 4&lt;/li&gt;
    &lt;?marker name="items"&gt;
  &lt;/ul&gt;
&lt;/div&gt;</code></pre></div><p>This is huuuuge&#8230;</p><p>&#8230;and there&#8217;s even more&#8230;</p><h4>Streaming HTML in single-page apps</h4><p>Single-page apps (SPA) have long been the solution to make web apps feel like real apps, because they only update the changed content when the user navigates to another page, without a full page reload.</p><p>The downside of this is that SPAs can&#8217;t use <a href="https://instantmultipageapp.com">the browser&#8217;s streaming parser</a>. When a browser loads a web page, it can do so in a streaming fashion. This means that it can already start rendering the page while it&#8217;s still downloading; it doesn&#8217;t have to wait for the page to be fully loaded before it can start rendering content.</p><p>SPAs typically load a skeleton HTML page and then update the content with DOM manipulation using JavaScript. Any content that is inserted into the DOM after the page has been served by the server cannot use the browser&#8217;s streaming parser. It has to wait for all content to be ready before it can start to render it when it&#8217;s inserted using JavaScript (for example, with <code>innerHTML</code>).</p><p>Until now&#8230;</p><h4>New streaming methods</h4><p>In addition to existing methods like <code>setHTML</code>, <code>setHTMLUnsafe</code> and the <code>innerHTML</code> and <code>outerHTML</code> setters, this proposal adds streaming methods like <code>streamHTML</code>, <code>streamHTMLUnsafe</code>, <code>streamAppendHTML</code> etc.</p><p>This enables SPAs to <em>stream</em> HTML into the insertion points marked by processing instructions <em>using JavaScript</em>. This means that instead of using <code>innerHTML</code> to update content, your SPA can now do this:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;javascript&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-javascript">const response = await fetch('/path/to/content.html');
const html = await response.text();

const writer = document.body.streamAppendHTMLUnsafe().getWriter();

await writer.write(html);
await writer.close();</code></pre></div><p>Note that in this example, the content is added to the <code>&lt;body&gt;</code> element.</p><p>When you use <code>&lt;template&gt;</code> elements and markers, you don&#8217;t even need to get a reference to the insertion point with JavaScript anymore. You can just define the insertion point of the SPA content with a marker:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;html&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-html">&lt;main id="app"&gt;
  &lt;section class="page"&gt;
    &lt;?marker name="content"&gt;
  &lt;/section&gt;
&lt;/main&gt;</code></pre></div><p>And then you can simply append the <code>&lt;template&gt;</code> to the bottom of the page:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;html&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-html">&lt;template for="content"&gt;
&lt;?start name="content"&gt;
  &lt;p&gt;Homepage content&lt;/p&gt;
&lt;?end&gt;
&lt;/template&gt;</code></pre></div><p>Since you defined a marker and added this template to the bottom of the page, the browser will now take care of inserting the content in the right place. Also note that this template contains <code>&lt;?start&gt;</code> and <code>&lt;?end&gt;</code> markers, and the resulting HTML will be this:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;html&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-html">&lt;main id="app"&gt;
  &lt;section class="page"&gt;
    &lt;?start name="content"&gt;
      &lt;p&gt;Homepage content&lt;/p&gt;
    &lt;?end&gt;
  &lt;/section&gt;
&lt;/main&gt;</code></pre></div><p>This ensures that the processing instructions are ready for when the user navigates to another page. If each partial contains these processing instructions, you can always just add the page HTML to the bottom of the page, and the browser will then insert it in the right location while replacing the contents of the previous page.</p><p>And there&#8217;s even more good news&#8230;</p><h4>Cross-browser support is coming soon</h4><p>Not only can you already use this in Chromium-based browsers from version 148 with experimental web features enabled, but the WebKit team (Safari) has <a href="https://github.com/WebKit/standards-positions/issues/628">announced</a> they will support this and the Firefox team has a <a href="https://github.com/mozilla/standards-positions/issues/1369">positive position</a> on this feature.</p><p>This means this feature could be supported in all major browsers soon! &#128170;</p><h4>Demo</h4><p>I created a demo SPA that uses the technique described above to update the page content. It defines markers in the HTML where the page content is to be inserted, and each page partial HTML template contains new markers where the content of the next page is to be inserted. All page templates are simply added to the bottom of the page, and the browser takes care of the rest.</p><p>Check out the <a href="https://streaming-spa.vercel.app/">demo</a> and the <a href="https://github.com/DannyMoerkerke/streaming-spa">source code</a>.</p><p>Notice that the demo uses the <code>streamAppendHTMLUnsafe()</code> method to stream the content. The Unsafe variant is required to preserve the &lt;template&gt; element in the HTML that is inserted. Using <code>streamAppendHTML()</code>results in empty HTML and doesn&#8217;t work.</p><p>Also notice that the streaming methods are required to use templates and markers. When you use non-streaming methods, like <code>appendHTMLUnsafe()</code> for example, the parent of the template when it is parsed is an intermediate document fragment, and the patching happens "in place" inside the fragment instead of in the insertion point indicated by the markers.</p><p>The /list route of the demo demonstrates a list that is updated in chunks. The demo just uses <code>setTimeout()</code> to simulate a response that delivers the chunks, but you can probably imagine how this would work in a production scenario where the content is delivered in real chunks.</p><p>Also make sure to check out the <a href="https://developer.chrome.com/blog/declarative-partial-updates">Chrome developers article</a> and the <a href="https://github.com/GoogleChromeLabs/web-perf-demos/blob/main/patching-demos/photo-album-server.js">accompanying demo</a>, which demonstrates the out-of-order streaming very well. </p><p>This is a very promising feature that will truly revolutionize how HTML is delivered. When more browsers support this, you will hear it here first!</p><div><hr></div><p style="text-align: center;">Do you need help with your web app?</p><p style="text-align: center;">Book a 1-on-1 call with me and I will do my absolute best to answer all your questions and solve your problems.</p><p style="text-align: center;">&#8364;100 for one hour, money-back guarantee if you&#8217;re not satisfied.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://cal.com/dannymoerkerke/1-on-1-consult-with-danny-moerkerke&quot;,&quot;text&quot;:&quot;Book a call now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://cal.com/dannymoerkerke/1-on-1-consult-with-danny-moerkerke"><span>Book a call now</span></a></p><div><hr></div><p style="text-align: center;"></p>]]></content:encoded></item><item><title><![CDATA[File System Access And Persistent Storage For Web Apps]]></title><description><![CDATA[The modern web, tested and explained in plain English]]></description><link>https://modernwebweekly.substack.com/p/file-system-access-and-persistent</link><guid isPermaLink="false">https://modernwebweekly.substack.com/p/file-system-access-and-persistent</guid><dc:creator><![CDATA[Danny Moerkerke]]></dc:creator><pubDate>Thu, 28 May 2026 13:46:36 GMT</pubDate><enclosure url="https://images.unsplash.com/photo-1652195019227-4f3d3f599959?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxOHx8ZGlza3xlbnwwfHx8fDE3Nzk5NzQ5NDR8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1652195019227-4f3d3f599959?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxOHx8ZGlza3xlbnwwfHx8fDE3Nzk5NzQ5NDR8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1652195019227-4f3d3f599959?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxOHx8ZGlza3xlbnwwfHx8fDE3Nzk5NzQ5NDR8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1652195019227-4f3d3f599959?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxOHx8ZGlza3xlbnwwfHx8fDE3Nzk5NzQ5NDR8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1652195019227-4f3d3f599959?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxOHx8ZGlza3xlbnwwfHx8fDE3Nzk5NzQ5NDR8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1652195019227-4f3d3f599959?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxOHx8ZGlza3xlbnwwfHx8fDE3Nzk5NzQ5NDR8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1652195019227-4f3d3f599959?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxOHx8ZGlza3xlbnwwfHx8fDE3Nzk5NzQ5NDR8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" width="6016" height="4000" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1652195019227-4f3d3f599959?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxOHx8ZGlza3xlbnwwfHx8fDE3Nzk5NzQ5NDR8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:4000,&quot;width&quot;:6016,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;a group of electronic devices&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="a group of electronic devices" title="a group of electronic devices" srcset="https://images.unsplash.com/photo-1652195019227-4f3d3f599959?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxOHx8ZGlza3xlbnwwfHx8fDE3Nzk5NzQ5NDR8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1652195019227-4f3d3f599959?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxOHx8ZGlza3xlbnwwfHx8fDE3Nzk5NzQ5NDR8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1652195019227-4f3d3f599959?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxOHx8ZGlza3xlbnwwfHx8fDE3Nzk5NzQ5NDR8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1652195019227-4f3d3f599959?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxOHx8ZGlza3xlbnwwfHx8fDE3Nzk5NzQ5NDR8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@sjjillan">s j</a> on <a href="https://unsplash.com">Unsplash</a></figcaption></figure></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://modernwebweekly.substack.com/p/file-system-access-and-persistent?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://modernwebweekly.substack.com/p/file-system-access-and-persistent?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><h4>Modern Web Weekly #73</h4><p>The File System Access API brings access to the native file system to web apps. The API is currently only supported in Chromium-based browsers on desktop, but the Origin Private File System is now supported across all browsers on desktop, Android, and iOS.</p><p>The Origin Private File System (OPFS) is a high-performance virtual file system that&#8217;s accessible only to the origin the web app (PWA) runs on. You can work with it like any file system, which means that you can create and delete directories and files, and also edit files. It uses the same interfaces that the File System Access API uses, like <code>FileSystemDirectoryHandle</code> and <code>FileSystemFileHandle</code>. These are the interfaces that give access to a directory and fil,e respectively.</p><p>OPFS is a virtual file system, which means that the directories and files that are created do not correspond to actual directories and files on the user's device. They can be saved inside a database or a single file; this is up to the browser to implement.</p><p>An implication of this is that the user doesn&#8217;t have to give permission repeatedly to the web app to get access to the directories and files, as is the case with the File System Access API, which does have access to the actual file system of the user's device.</p><p>Just like IndexedDB, localStorage, and sessionStorage, the OPFS is subject to storage quota restrictions. This means that there is a maximum to the amount of data that can be stored (although this will usually be enough for most use-cases) and that the OPFS will be cleared when the user clears all browsing data.</p><p>Your web app gets access to OPFS through the <code>StorageManager,</code> which is located in the <code>navigator.storage</code> property. <code>StorageManager</code> is also responsible for managing storage permissions and estimating available storage through its <code>persist()</code> and <code>estimate()</code> methods.</p><p>Access to OPFS is obtained through the <code>getDirectory()</code> method:</p><pre><code><code>const opfsRoot = await navigator.storage.getDirectory();</code></code></pre><p>The returned object <code>opfsRoot</code> is a <code>FileSystemDirectoryHandle</code>. In the root, you can create a directory with the <code>getDirectoryHandle()</code> method:</p><pre><code><code>const directoryHandle = await opfsRoot
    .getDirectoryHandle('test_dir', {create: true});</code></code></pre><p><code>getDirectoryHandle()</code> is used to get a reference to a directory inside OPFS, but by passing <code>{create: true}</code> as the second argument, the directory is created. If the directory already exists<em>,</em> an error is thrown.</p><p>To create a file, use the <code>getFileHandle()</code> method:</p><pre><code><code>const fileHandle = await opfsRoot.getfileHandle('test.txt', {create: true});</code></code></pre><p>Just like <code>getDirectoryHandle()</code>, <code>getFileHandle()</code> gets a reference to a file or creates it when <code>{create: true}</code> is passed as the second argument. The above example creates a file inside the OPFS root. To create a file inside a specific folder, you need to call <code>getFileHandle()</code> on the <code>FileSystemDirectoryHandle</code> that references the directory you want to create it in:</p><pre><code><code>// create a directory in OPFS
const directoryHandle = await opfsRoot
    .getDirectoryHandle('test_dir', {create: true});

// create a file in the directory that was just created
// "getFileHandle()" is called on "directoryHandle"
const fileHandle = await directoryHandle
    .getfileHandle('test.txt', {create: true});</code></code></pre><p>If you need to get a handle to an existing file, simply leave out <code>{create: true}</code>. To get the actual file the handle points to, use the <code>getFile()</code> method:</p><pre><code><code>// handle to existing file
const fileHandle = await directoryHandle.getfileHandle('test.txt');

// the actual File object
const file = await fileHandle.getFile();

// get the contents of the file as text
const contents = await.file.text();
</code></code></pre><p>To save data to the file, call the <code>createWritable()</code> method of the file handle, which returns a <code>FileSystemWritableFileStream</code>:</p><pre><code><code>const contents = 'This is a test file';
const writable = await fileHandle.createWritable();

// write the contents of the file to the stream.
await writable.write(contents);

// close the stream, the contents are now persisted to the file
await writable.close();</code></code></pre><p>For older browsers that don&#8217;t support the <code>FileSystemWritableFileStream</code>, we need to use the <em>synchronous</em> file handle called <code>FileSystemSyncAccessHandle, </code>which has a synchronous <code>write()</code> method. But these synchronous methods are only available inside Web Workers. The reason for this is that synchronous methods can block the main thread, and Web Workers can&#8217;t, so synchronous methods are only allowed inside Web Workers.</p><p>To get a <code>FileSystemSyncAccessHandle</code>, call the <code>createSyncAccessHandle</code> on the file handle:</p><pre><code><code>const fileHandle = await directoryHandle.getfileHandle('test.txt');

// get synchronous file handle
const syncAccessHandle = await fileHandle.createSyncAccessHandle();</code></code></pre><p>Here&#8217;s how you would then save contents to the file:</p><pre><code><code>const syncAccessHandle = await fileHandle.createSyncAccessHandle();
const encoder = new TextEncoder();
const writeBuffer = encoder.encode(contents);
const writeSize = syncAccessHandle.write(writeBuffer, { "at" : 0 });

// truncate the file to the size of the data, otherwise if the new data
// is smaller than any old data, parts of the old data will 
// stay in the file
syncAccessHandle.truncate(writeSize);

// save changes to disk
syncAccessHandle.flush();

// close FileSystemSyncAccessHandle when done
syncAccessHandle.close();</code></code></pre><p>From this example, you can see that to save data to a file synchronously, we always need to have the file handle that points to the file we want to save data to. Since this needs to be done inside a Web Worker, the file handle needs to be sent to the Worker using the <code>postMessage()</code> method. When this file handle is sent, it needs to be serialized, but an additional issue is that some older browsers don&#8217;t support serializing <code>FileSystemFileHandle</code>.</p><p>A possible workaround for this is to not send the actual file handle to the Web Worker, but the path to the file we want to save data to. We can do this by calling the <code>resolve()</code> method of <code>FileSystemDirectoryHandle</code>.</p><p>For example, if a file inside OPFS is located at <code>test_dir/nested_dir/text.txt</code>, calling <code>resolve()</code> on the root directory with the file handle as its argument will return an array with all directory names in the path and the file name:</p><pre><code><code>// "handle" is the file handle of the file located at 
// test_dir/nested_dir/test.txt
const path = opfsRoot.resolve(handle);

console.log(path); // ['test_dir', 'nested_dir', 'test.txt']</code></code></pre><p>You can then use this array to first get the handle to <code>test_dir</code> from the OPFS root, then use that directory handle to get the handle to <code>nested_dir</code> and then use that handle to get the file handle to <code>test.txt</code>.</p><p>Inside the Web Worker, you would then use something like this to save the data to the file:</p><pre><code><code>self.addEventListener('message', async ({data}) =&gt; {
  // get the path to the file and the contents to save to it
  const {path, contents} = data;

  // get the OPFS root directory
  const root = await navigator.storage.getDirectory();

  // get the file name (last element in the "path" array)
  const fileName = path.pop();
    
  let nestedDir = root;

  // recursively get the handle to the directory the file is in
  for(const dirPath of path) {
    nestedDir = await nestedDir.getDirectoryHandle(dirPath);
  }

  // get the handle to the file we want to save to
  const file = await nestedDir.getFileHandle(fileName);

  // get the sync file handle
  const syncAccessHandle = await file.createSyncAccessHandle();
  const encoder = new TextEncoder();
  const writeBuffer = encoder.encode(contents);
  const writeSize = accessHandle.write(writeBuffer, { "at" : 0 });
    
  // truncate the file to the size of the data, otherwise if the new data
  // is smaller than any old data, parts of the old data will 
  // stay in the file
  syncAccessHandle.truncate(writeSize);

  // save changes to disk
  syncAccessHandle.flush();

  // close FileSystemSyncAccessHandle when done
  syncAccessHandle.close();
});</code></code></pre><p>Both directories and files can be removed with the <code>remove()</code> method of the directory or file handle. To remove a folder and all its subfolders pass <code>{recursive: true}</code>:</p><p>The <code>remove()</code> method is currently implemented in Chrome. In Safari, you have to use <code>removeEntry</code> with the name of the directory or file.</p><pre><code><code>directoryHandle.removeEntry('test.txt');</code></code></pre><p><a href="https://whatpwacando.today/file-system">Check What PWA Can Do Today for a demo</a> of both the File System Access API and the Origin Private File System.</p><div><hr></div><h3>Persistent storage for PWAs</h3><p>In addition to OPFS, web apps can store data in the user&#8217;s browser in various ways. You&#8217;re probably familiar with cookies that can store simple data, but web apps can also use <code>IndexedDB</code> and <code>Cache Storage</code> to store larger, structured data. These two mechanisms are part of the <code>Storage API</code>. In addition to these, there are also <code>localStorage</code> and <code>sessionStorage</code> that are part of the <code>Web Storage API</code>.</p><p>The problem is that the browser <em>may</em> delete this data when a user hasn&#8217;t used the app for a certain time. This is especially important when the user&#8217;s device runs low on disk space. In that case, data from web apps that the user hasn&#8217;t used for the longest time will be removed first.</p><p>Safari is the only browser that proactively removes data when Intelligent Tracking Prevention (ITP) is turned on. When a web app hasn&#8217;t been interacted with for seven days, the data it saved will be removed; however, installed PWAs are excluded from this.</p><p>Web apps can now also request persistent storage in all browsers. This means that the data saved by the app will not be removed unless the data is manually removed by the user or when the device runs low on disk space. When disk space runs low, the browser will notify the user and offer to delete specific data.</p><h3>How to request persistent storage</h3><p>In supporting browsers, web apps can request persistent storage through <code>navigator.storage.persist()</code>. This returns a <code>Promise</code> that resolves to <code>true</code> or <code>false</code>. Permission is granted by the browser based on how much the user has interacted with the app and if the app is installed as a PWA.</p><p>To check if an app already has persistent storage, use <code>await navigator.storage.persisted()</code> which returns a <code>Promise</code> that resolves to <code>true</code> or <code>false</code>.</p><p>If persistent storage is not granted, try interacting with the app more or install it as a PWA. Browsers don&#8217;t show any dialogs to ask for permission, so you could consider requesting persistent storage regularly.</p><h3>How much storage does my web app use?</h3><p>The available and used storage space can be obtained through <code>navigator.storage.estimate()</code> which returns a <code>Promise</code> that resolves to the <code>quota</code> and <code>usage</code>properties that return the available and used storage space, respectively, in bytes.</p><p>In Chrome and Edge, an additional property <code>usageDetails</code> is returned that specifies the used space by storage type, which may vary per browser.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!1g8K!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa21113f6-ae6d-4b84-853c-2ccff6b4d196_399x159.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!1g8K!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa21113f6-ae6d-4b84-853c-2ccff6b4d196_399x159.jpeg 424w, https://substackcdn.com/image/fetch/$s_!1g8K!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa21113f6-ae6d-4b84-853c-2ccff6b4d196_399x159.jpeg 848w, https://substackcdn.com/image/fetch/$s_!1g8K!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa21113f6-ae6d-4b84-853c-2ccff6b4d196_399x159.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!1g8K!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa21113f6-ae6d-4b84-853c-2ccff6b4d196_399x159.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!1g8K!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa21113f6-ae6d-4b84-853c-2ccff6b4d196_399x159.jpeg" width="399" height="159" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a21113f6-ae6d-4b84-853c-2ccff6b4d196_399x159.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:159,&quot;width&quot;:399,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Modern Web Weekly #13&quot;,&quot;title&quot;:&quot;Modern Web Weekly #13&quot;,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Modern Web Weekly #13" title="Modern Web Weekly #13" srcset="https://substackcdn.com/image/fetch/$s_!1g8K!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa21113f6-ae6d-4b84-853c-2ccff6b4d196_399x159.jpeg 424w, https://substackcdn.com/image/fetch/$s_!1g8K!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa21113f6-ae6d-4b84-853c-2ccff6b4d196_399x159.jpeg 848w, https://substackcdn.com/image/fetch/$s_!1g8K!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa21113f6-ae6d-4b84-853c-2ccff6b4d196_399x159.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!1g8K!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa21113f6-ae6d-4b84-853c-2ccff6b4d196_399x159.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption">Available and used storage in Safari on iOS</figcaption></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!bJBJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F344dfcce-be2b-4cdb-903d-426acf49af2e_400x281.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!bJBJ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F344dfcce-be2b-4cdb-903d-426acf49af2e_400x281.jpeg 424w, https://substackcdn.com/image/fetch/$s_!bJBJ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F344dfcce-be2b-4cdb-903d-426acf49af2e_400x281.jpeg 848w, https://substackcdn.com/image/fetch/$s_!bJBJ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F344dfcce-be2b-4cdb-903d-426acf49af2e_400x281.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!bJBJ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F344dfcce-be2b-4cdb-903d-426acf49af2e_400x281.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!bJBJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F344dfcce-be2b-4cdb-903d-426acf49af2e_400x281.jpeg" width="400" height="281" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/344dfcce-be2b-4cdb-903d-426acf49af2e_400x281.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:281,&quot;width&quot;:400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Modern Web Weekly #13&quot;,&quot;title&quot;:&quot;Modern Web Weekly #13&quot;,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Modern Web Weekly #13" title="Modern Web Weekly #13" srcset="https://substackcdn.com/image/fetch/$s_!bJBJ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F344dfcce-be2b-4cdb-903d-426acf49af2e_400x281.jpeg 424w, https://substackcdn.com/image/fetch/$s_!bJBJ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F344dfcce-be2b-4cdb-903d-426acf49af2e_400x281.jpeg 848w, https://substackcdn.com/image/fetch/$s_!bJBJ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F344dfcce-be2b-4cdb-903d-426acf49af2e_400x281.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!bJBJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F344dfcce-be2b-4cdb-903d-426acf49af2e_400x281.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Available and used storage in Chrome on Android</figcaption></figure></div><p>Here&#8217;s a <a href="https://whatpwacando.today/storage">live demo</a> on What PWA Can Do Today.</p><div><hr></div><p style="text-align: center;">Do you need help with your web app?</p><p style="text-align: center;">Book a 1-on-1 call with me, and I will do my absolute best to answer all your questions and solve your problems.</p><p style="text-align: center;">&#8364;100 for one hour, money-back guarantee if you&#8217;re not satisfied.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://cal.com/dannymoerkerke/1-on-1-consult-with-danny-moerkerke&quot;,&quot;text&quot;:&quot;Book a call now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://cal.com/dannymoerkerke/1-on-1-consult-with-danny-moerkerke"><span>Book a call now</span></a></p><div><hr></div><p style="text-align: center;"></p>]]></content:encoded></item><item><title><![CDATA[A Picture-in-Picture window with arbitrary HTML]]></title><description><![CDATA[The modern web, tested and explained in plain English]]></description><link>https://modernwebweekly.substack.com/p/a-picture-in-picture-window-with</link><guid isPermaLink="false">https://modernwebweekly.substack.com/p/a-picture-in-picture-window-with</guid><dc:creator><![CDATA[Danny Moerkerke]]></dc:creator><pubDate>Thu, 21 May 2026 13:47:20 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Gmdx!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70c3fe1a-1962-47fe-a0a2-78ade62092a2_1024x608.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Gmdx!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70c3fe1a-1962-47fe-a0a2-78ade62092a2_1024x608.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Gmdx!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70c3fe1a-1962-47fe-a0a2-78ade62092a2_1024x608.png 424w, https://substackcdn.com/image/fetch/$s_!Gmdx!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70c3fe1a-1962-47fe-a0a2-78ade62092a2_1024x608.png 848w, https://substackcdn.com/image/fetch/$s_!Gmdx!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70c3fe1a-1962-47fe-a0a2-78ade62092a2_1024x608.png 1272w, https://substackcdn.com/image/fetch/$s_!Gmdx!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70c3fe1a-1962-47fe-a0a2-78ade62092a2_1024x608.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Gmdx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70c3fe1a-1962-47fe-a0a2-78ade62092a2_1024x608.png" width="1024" height="608" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/70c3fe1a-1962-47fe-a0a2-78ade62092a2_1024x608.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:&quot;normal&quot;,&quot;height&quot;:608,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Gmdx!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70c3fe1a-1962-47fe-a0a2-78ade62092a2_1024x608.png 424w, https://substackcdn.com/image/fetch/$s_!Gmdx!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70c3fe1a-1962-47fe-a0a2-78ade62092a2_1024x608.png 848w, https://substackcdn.com/image/fetch/$s_!Gmdx!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70c3fe1a-1962-47fe-a0a2-78ade62092a2_1024x608.png 1272w, https://substackcdn.com/image/fetch/$s_!Gmdx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70c3fe1a-1962-47fe-a0a2-78ade62092a2_1024x608.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption"></figcaption></figure></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://modernwebweekly.substack.com/p/a-picture-in-picture-window-with?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://modernwebweekly.substack.com/p/a-picture-in-picture-window-with?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><p><strong>Modern Web Weekly #72</strong></p><p>Firefox 151 now supports the Document Picture-in-Picture API after support already landed in Chromium-based browsers last year.</p><p>The Document Picture-in-Picture API enables web apps to populate a picture-in-picture window with arbitrary HTML content. This means that you can place custom controls to control a video in a picture-in-picture window, for example</p><p>Another use case is that you can use this API to place multiple video streams in a single picture-in-picture window without having to use a <code>&lt;canvas&gt;</code> to place the individual <code>&lt;video&gt;</code> elements. Since you can place basically any HTML in this picture-in-picture window, the possibilities are endless.</p><p>Contrary to automatic picture-in-picture, the picture-in-picture window with arbitrary HTML is always on top. This means that when you switch from the browser or PWA to another application or even to another desktop, the picture-in-picture window will still be visible.</p><p>To open a picture-in-picture window and save a reference to it:</p><pre><code><code>// open a picture-in-picture window and optionally
// specify the width and height
const pipWindow = await documentPictureInPicture.requestWindow({
  width: 800,
  height: 600
});</code></code></pre><p>The picture-in-picture window now contains a <code>document</code> like any other browser window that you can append HTML elements to. In the demo, I use a <code>&lt;web-cam&gt;</code> web component that I can select and move to the picture-in-picture window:</p><pre><code><code>// get a reference to the web cam Web Component
const webCam = document.querySelector('web-cam');

// add the web cam to the picture-in-picture window
pipWindow.document.body.appendChild(webCam);</code></code></pre><p>The whole web component is now moved to the picture-in-picture window, and it can be controlled from there. When the picture-in-picture window is closed, we listen for the <code>unload</code> or <code>pagehide</code> event and move the web component back to the main <code>document</code>:</p><pre><code><code>// listen for the 'unload' or 'pagehide' event
pipWindow.addEventListener('unload', (e) =&gt; {

// get a reference to the parent element of the web component
// in the main document
const webCamContainer = document.querySelector('.web-cam-container');

// get a reference to &lt;web-cam&gt; in the picture-in-picture window
const webCam =  e.target.querySelector('web-cam');

// move &lt;web-cam&gt; back to the main document
  webCamContainer.appendChild(webCam);
});</code></code></pre><p>By default, the picture-in-picture window will contain a &#8220;back to tab&#8221; button in the title bar that can be hidden by setting <code>disallowReturnToOpener</code> to <code>true</code> when opening the window:</p><pre><code><code>const pipWindow = await documentPictureInPicture.requestWindow({
  width: 800,
  height: 600,
  disallowReturnToOpener: true // will hide the "back to tab" button
});</code></code></pre><p>Here&#8217;s a screen recording of the <a href="https://whatpwacando.today/pip?ref=modern-web-weekly.ghost.io">demo</a> running in Firefox, where I swipe between desktops while the PiP window stays visible:</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;49b77a9f-ab2d-4f3a-8887-41b2d644f042&quot;,&quot;duration&quot;:null}"></div><p><strong>CSS picture-in-picture display mode</strong><br><br>When HTML elements are part of the picture-in-picture window, you can use <code>(display-mode: picture-in-picture)</code> to target these elements in CSS with a media-query. I use this in the <code>&lt;web-cam&gt;</code> web component to hide the picture-in-picture button that&#8217;s normally shown, as the component is already in picture-in-picture mode, and clicking it again would throw an error:</p><pre><code><code>@media all and (display-mode: picture-in-picture) {
  :is(#pip, #pip-label) {
    display: none !important;
  }
}</code></code></pre><div><hr></div><h2>Install element is now available as an origin trial</h2><p>In <a href="https://modernwebweekly.substack.com/p/modern-web-weekly-67">Modern Web Weekly #67</a>, I wrote about the new declarative <code>&lt;install&gt;</code> element that is used to install a PWA. At the time of writing that, it was only available behind a flag in Chrome Canary, but now it&#8217;s also available as an <a href="https://developer.chrome.com/origintrials/#/view_trial/506092008125759489">origin trial</a>, which means you can register to receive a key that enables it in your web app.</p><p>You add the key in a special meta tag in your web app&#8217;s HTML, and it works. You can check it out in <a href="https://whatpwacando.today/installation/">the demo on What PWA Can Do Today</a>.</p><p>The declarative <code>&lt;install&gt;</code> renders a button with an install icon and the text &#8220;Install&#8221;. </p><p>When used without attributes, it will install the current web app if it has a <code>manifest.json</code> file:</p><pre><code><code>&lt;install&gt;
  [optional fallback content]
&lt;/install&gt;</code></code></pre><p>With the attributes <code>installurl</code> and <code>manifestid</code> it can be used to install web apps from other domains:</p><pre><code><code>&lt;install installurl="https://whatpwacando.today/"
         manifestid="https://whatpwacando.today/pwa-today"&gt;
  [optional fallback content]
&lt;/install&gt;</code></code></pre><p><code>installurl</code> specifies the URL of the web app to install. If unspecified, the current web app will be installed.</p><p><code>manifestid</code> specifies the computed ID of the web app to install. If unspecified, the <code>manifest.json</code> file of the web app at <code>installurl</code> must have a custom ID defined. If specified, it must match the computed ID of the web app to be installed. You can find the computed app ID of your web app in Chrome DevTools in the Application tab &gt; Manifest &gt; Identity.</p><p>When the web app to be installed, either the current one or another one specified by <code>installurl</code>, is not yet installed, the button looks like this:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!hQ2u!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F365a0ab3-1892-4b0c-bc87-91e19af2bad2.heic" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!hQ2u!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F365a0ab3-1892-4b0c-bc87-91e19af2bad2.heic 424w, https://substackcdn.com/image/fetch/$s_!hQ2u!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F365a0ab3-1892-4b0c-bc87-91e19af2bad2.heic 848w, https://substackcdn.com/image/fetch/$s_!hQ2u!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F365a0ab3-1892-4b0c-bc87-91e19af2bad2.heic 1272w, https://substackcdn.com/image/fetch/$s_!hQ2u!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F365a0ab3-1892-4b0c-bc87-91e19af2bad2.heic 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!hQ2u!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F365a0ab3-1892-4b0c-bc87-91e19af2bad2.heic" width="331" height="289" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/365a0ab3-1892-4b0c-bc87-91e19af2bad2.heic&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:289,&quot;width&quot;:331,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1961,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/heic&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://modernwebweekly.substack.com/i/186978872?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F365a0ab3-1892-4b0c-bc87-91e19af2bad2.heic&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!hQ2u!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F365a0ab3-1892-4b0c-bc87-91e19af2bad2.heic 424w, https://substackcdn.com/image/fetch/$s_!hQ2u!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F365a0ab3-1892-4b0c-bc87-91e19af2bad2.heic 848w, https://substackcdn.com/image/fetch/$s_!hQ2u!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F365a0ab3-1892-4b0c-bc87-91e19af2bad2.heic 1272w, https://substackcdn.com/image/fetch/$s_!hQ2u!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F365a0ab3-1892-4b0c-bc87-91e19af2bad2.heic 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">&lt;install&gt; element when the app is not yet installed</figcaption></figure></div><p>Before, after the app was installed, it used to look like this, but for some reason, this has been removed:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!jyfB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68e0cdce-3c51-44b9-9561-342e4eb89344.heic" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!jyfB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68e0cdce-3c51-44b9-9561-342e4eb89344.heic 424w, https://substackcdn.com/image/fetch/$s_!jyfB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68e0cdce-3c51-44b9-9561-342e4eb89344.heic 848w, https://substackcdn.com/image/fetch/$s_!jyfB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68e0cdce-3c51-44b9-9561-342e4eb89344.heic 1272w, https://substackcdn.com/image/fetch/$s_!jyfB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68e0cdce-3c51-44b9-9561-342e4eb89344.heic 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!jyfB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68e0cdce-3c51-44b9-9561-342e4eb89344.heic" width="331" height="289" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/68e0cdce-3c51-44b9-9561-342e4eb89344.heic&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:289,&quot;width&quot;:331,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2010,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/heic&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://modernwebweekly.substack.com/i/186978872?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68e0cdce-3c51-44b9-9561-342e4eb89344.heic&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!jyfB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68e0cdce-3c51-44b9-9561-342e4eb89344.heic 424w, https://substackcdn.com/image/fetch/$s_!jyfB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68e0cdce-3c51-44b9-9561-342e4eb89344.heic 848w, https://substackcdn.com/image/fetch/$s_!jyfB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68e0cdce-3c51-44b9-9561-342e4eb89344.heic 1272w, https://substackcdn.com/image/fetch/$s_!jyfB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68e0cdce-3c51-44b9-9561-342e4eb89344.heic 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">&lt;install&gt; element after the app was installed, unfortunately this is now removed</figcaption></figure></div><p>I hope this behaviour will be restored before the origin trial finishes. It makes it very easy for installed PWAs that are opened in a browser to indicate that it was already installed. This is especially nice for PWA app stores that offer functionality to install web apps on other domains. These could automatically show a &#8220;Launch&#8221; button instead of an &#8220;Install&#8221; button.</p><p>When clicking the button, the behaviour fortunately hasn&#8217;t been changed. When the app is not installed, it will show the installation dialog, and when it&#8217;s clicked after the app was installed, it will just offer to open the installed app.</p><p>The install element fires three events to indicate successful installation, dismissal of the prompt, and validation errors:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;javascript&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-javascript">const installElement = document.querySelector('install');

installElement.addEventListener('promptaction', () =&gt; {
  console.log('Installation succeeded');
});

installElement.addEventListener('promptdismiss', () =&gt; {
  console.log('User dismissed the install prompt');
});

installElement.addEventListener('validationstatuschanged', (e) =&gt; {
  console.error('Invalid install data:', e.target.invalidReason);
});</code></pre></div><p>I haven&#8217;t been able to fire the <code>validationstatuschanged</code> event by providing invalid installation data with the <code>installurl</code> and <code>manifestid</code> attributes. In that case, an issue is shown in Chrome DevTools:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!w_vu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a27c684-e998-4e97-b9f2-e9808c11804c_1772x516.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!w_vu!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a27c684-e998-4e97-b9f2-e9808c11804c_1772x516.png 424w, https://substackcdn.com/image/fetch/$s_!w_vu!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a27c684-e998-4e97-b9f2-e9808c11804c_1772x516.png 848w, https://substackcdn.com/image/fetch/$s_!w_vu!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a27c684-e998-4e97-b9f2-e9808c11804c_1772x516.png 1272w, https://substackcdn.com/image/fetch/$s_!w_vu!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a27c684-e998-4e97-b9f2-e9808c11804c_1772x516.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!w_vu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a27c684-e998-4e97-b9f2-e9808c11804c_1772x516.png" width="1456" height="424" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4a27c684-e998-4e97-b9f2-e9808c11804c_1772x516.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:424,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:109200,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://modernwebweekly.substack.com/i/198392378?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a27c684-e998-4e97-b9f2-e9808c11804c_1772x516.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!w_vu!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a27c684-e998-4e97-b9f2-e9808c11804c_1772x516.png 424w, https://substackcdn.com/image/fetch/$s_!w_vu!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a27c684-e998-4e97-b9f2-e9808c11804c_1772x516.png 848w, https://substackcdn.com/image/fetch/$s_!w_vu!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a27c684-e998-4e97-b9f2-e9808c11804c_1772x516.png 1272w, https://substackcdn.com/image/fetch/$s_!w_vu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a27c684-e998-4e97-b9f2-e9808c11804c_1772x516.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Chrome dev tools issue when an &lt;install&gt; element has invalid install data</figcaption></figure></div><p>Just like with the Web Install API, when the current web app is installed, and it has screenshots defined in <code>manifest.json</code>, the install dialog will show the <a href="https://modernwebweekly.substack.com/i/140564211/a-better-install-ui-for-pwas">enhanced installation dialog</a>. But when a web app at another domain is installed, it will show the simple installation dialog, even if that web app has screenshots defined in its <code>manifest.json</code>.</p><p>For the Web Install API, this will soon be fixed, so I expect this to be the case for the install element as well.</p><p>Check the demo at <a href="https://whatpwacando.today/installation/">https://whatpwacando.today/installation/</a></p><div><hr></div><h2>AirPlay support on iOS, iPadOS, and macOS</h2><p>I added a simple demo to What PWA Can Do Today to show how you can stream video from one Apple device (iPhone, iPad, MacBook) to another (AppleTV, MacBook, compatible smart TV) using AirPlay.</p><p>When your web app uses a <code>&lt;video&gt;</code> element with the <code>controls</code> attribute and runs on an Apple device, it will automatically display an AirPlay button in the controls. If you provide your own controls, you can programmatically start AirPlay with JavaScript:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;javascript&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-javascript">const button = document.querySelector("#airplay-button")
let isAirPlayAvailable = false

button.disabled = !isAirPlayAvailable;

player.addEventListener('webkitplaybacktargetavailabilitychanged', e =&gt; {
  if (e.availability === 'available') {
    isAirPlayAvailable = true
  }
});

button.addEventListener('click', () =&gt; {
  if (isAirPlayAvailable) {
    player.webkitShowPlaybackTargetPicker()
  }
});</code></pre></div><p>To use AirPlay, the video source needs to be a real URL. A stream or object URL will not work.</p><p>Check out the <a href="https://whatpwacando.today/airplay">demo</a>.</p><div><hr></div><p style="text-align: center;">Do you need help with your web app?</p><p style="text-align: center;">Book a 1-on-1 call with me, and I will do my absolute best to answer all your questions and solve your problems.</p><p style="text-align: center;">&#8364;100 for one hour, money-back guarantee if you&#8217;re not satisfied.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://cal.com/dannymoerkerke/1-on-1-consult-with-danny-moerkerke&quot;,&quot;text&quot;:&quot;Book a call now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://cal.com/dannymoerkerke/1-on-1-consult-with-danny-moerkerke"><span>Book a call now</span></a></p><div><hr></div><p style="text-align: center;"></p><p></p>]]></content:encoded></item><item><title><![CDATA[Bullet-proof encryption for web apps]]></title><description><![CDATA[The modern web, tested and explained in plain English]]></description><link>https://modernwebweekly.substack.com/p/bullet-proof-encryption-for-web-apps</link><guid isPermaLink="false">https://modernwebweekly.substack.com/p/bullet-proof-encryption-for-web-apps</guid><dc:creator><![CDATA[Danny Moerkerke]]></dc:creator><pubDate>Thu, 07 May 2026 12:32:31 GMT</pubDate><enclosure url="https://images.unsplash.com/photo-1654588831193-0285dab84d5a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw1Nnx8ZW5jcnlwdGlvbnxlbnwwfHx8fDE3Nzc5ODgxMjB8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1654588831193-0285dab84d5a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw1Nnx8ZW5jcnlwdGlvbnxlbnwwfHx8fDE3Nzc5ODgxMjB8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1654588831193-0285dab84d5a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw1Nnx8ZW5jcnlwdGlvbnxlbnwwfHx8fDE3Nzc5ODgxMjB8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1654588831193-0285dab84d5a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw1Nnx8ZW5jcnlwdGlvbnxlbnwwfHx8fDE3Nzc5ODgxMjB8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1654588831193-0285dab84d5a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw1Nnx8ZW5jcnlwdGlvbnxlbnwwfHx8fDE3Nzc5ODgxMjB8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1654588831193-0285dab84d5a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw1Nnx8ZW5jcnlwdGlvbnxlbnwwfHx8fDE3Nzc5ODgxMjB8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1654588831193-0285dab84d5a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw1Nnx8ZW5jcnlwdGlvbnxlbnwwfHx8fDE3Nzc5ODgxMjB8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" width="5616" height="2940" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1654588831193-0285dab84d5a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw1Nnx8ZW5jcnlwdGlvbnxlbnwwfHx8fDE3Nzc5ODgxMjB8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2940,&quot;width&quot;:5616,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;a computer keyboard with a padlock on top of it&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="a computer keyboard with a padlock on top of it" title="a computer keyboard with a padlock on top of it" srcset="https://images.unsplash.com/photo-1654588831193-0285dab84d5a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw1Nnx8ZW5jcnlwdGlvbnxlbnwwfHx8fDE3Nzc5ODgxMjB8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1654588831193-0285dab84d5a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw1Nnx8ZW5jcnlwdGlvbnxlbnwwfHx8fDE3Nzc5ODgxMjB8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1654588831193-0285dab84d5a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw1Nnx8ZW5jcnlwdGlvbnxlbnwwfHx8fDE3Nzc5ODgxMjB8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1654588831193-0285dab84d5a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw1Nnx8ZW5jcnlwdGlvbnxlbnwwfHx8fDE3Nzc5ODgxMjB8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@sasun1990">Sasun Bughdaryan</a> on <a href="https://unsplash.com">Unsplash</a></figcaption></figure></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://modernwebweekly.substack.com/p/bullet-proof-encryption-for-web-apps?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://modernwebweekly.substack.com/p/bullet-proof-encryption-for-web-apps?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><h4>Modern Web Weekly #71</h4><p>If you want to use encryption in your web app to encrypt and decrypt sensitive data, you can use the Web Crypto API to generate an public and private key:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;javascript&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-javascript">const { publicKey, privateKey } = await window.crypto.subtle.generateKey(
  {
    name: "RSA-OAEP",
    modulusLength: 4096,
    publicExponent: new Uint8Array([1, 0, 1]),
    hash: "SHA-256",
  },
  true,
  ["encrypt", "decrypt"],
);</code></pre></div><p>You can now send the public key to anyone you want to share your encrypted data with and keep your private key safely stored so you can decrypt data. But where exactly would you store your private key?</p><p>The only options you have are localStorage, sessionStorage, IndexedDB, and OPFS. While these are <em>relatively</em> safe, there is always the possibility that your web app is compromised and your private key is stolen. For very sensitive data, that can be un unacceptable risk.</p><p>Luckily, there&#8217;s now a solution.</p><p>The WebAuthn PRF extension (Pseudo-Random Function) enables web apps to derive encryption keys using the user&#8217;s passkey, <em>without ever touching the private key itself</em>.</p><p>Whenever you register a passkey or authenticate with it, you provide a text label for the key and you get a secret back that you can use to derive the encryption key. If you specify the same label, you get the same encryption key back. </p><p>So your web app only needs to store the label that you can use to get the key. No need to store the key anywhere else so it can never be stolen.</p><p>Here&#8217;s how it works.</p><h4>When registering a passkey</h4><p>Whenever you register a passkey, you add a <code>prf</code> key to the <code>extensions</code> key of the credential creation options like this:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;javascript&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-javascript">extensions: {
  prf: {
    eval: {
      first: new TextEncoder().encode("prf-key-v1") // the text label to derive the key
    }
  }
}</code></pre></div><p>In this example, &#8220;prf-key-1&#8221; is the text label we use for the key. The complete example to register the passkey would look something like this:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;javascript&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-javascript">const credential = await navigator.credentials.create({
  publicKey: {
    challenge: new Uint8Array([
      21, 31, 105 /* 29 more random bytes generated by the server */,
    ]),
    rp: { name: "Secure Notes" },
    user: {
      id: new Uint8Array(16),
      name: "pwa@whatpwacando.today",
      displayName: "PWA"
    },
    pubKeyCredParams: [{ alg: -7, type: "public-key" }],
    authenticatorSelection: {
      userVerification: "required"
    },
    extensions: {
      prf: {
        eval: {
          first: new TextEncoder().encode("prf-key-v1") // the text label to derive the key
        }
      }
    }
  }
}); </code></pre></div><p>We can now access the secret that we can use to derive the encryption key:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;javascript&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-javascript">const prfResult = credential.getClientExtensionResults().prf.results.first;</code></pre></div><p><code>prfResult</code> is an <code>ArrayBuffer</code> that we can use to create a <code>CryptoKey</code> object using <code>importKey()</code>:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;javascript&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-javascript">const keyMaterial = await crypto.subtle.importKey(
  "raw",
  prfResult,
  "HKDF",
  false,
  ["deriveKey"]
);
</code></pre></div><p>And then we can use <code>deriveKey()</code> to get the actual encryption key:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;javascript&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-javascript">const encryptionKey = await crypto.subtle.deriveKey(
  {
    name: "HKDF",
    hash: "SHA-256",
    salt: new Uint8Array([]),
    info: new TextEncoder().encode("prf-demo"),
  },
  keyMaterial,
  { name: "AES-GCM", length: 256 },
  false,
  ["encrypt", "decrypt"]
);</code></pre></div><p>Don&#8217;t worry if you don&#8217;t fully understand these last two steps. The important part is that you know how to get the actual encryption key and use it to encrypt and decrypt your data.</p><p>Here&#8217;s how you can encrypt text:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;javascript&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-javascript">const textToEncrypt = 'My super secret data';
const iv = crypto.getRandomValues(new Uint8Array(12));

const encryptedText = await crypto.subtle.encrypt(
  { name: "AES-GCM", iv },
  encryptionKey,
  new TextEncoder().encode(textToEncrypt)
);</code></pre></div><p>And here&#8217;s how you can decrypt it:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;javascript&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-javascript">const decrypted = await crypto.subtle.decrypt(
  { name: "AES-GCM", iv },
  encryptionKey,
  textToDecrypt
);

const decryptedText = new TextDecoder().decode(decryptedText);</code></pre></div><p>Notice that the variable <code>iv</code> (Initialisation Vector) needs to be the same for encryption and decryption, so if you want to store the decrypted text on a server, you need to store <code>iv</code> along with it.</p><p>If you want to decrypt the text, you simply get the decrypted text and <code>iv</code> from the server, authenticate with your passkey and pass in the correct text label when you get the passkey using <code>navigator.credentials.get()</code>:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;javascript&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-javascript">const assertion = await navigator.credentials.get({
  publicKey: {
    challenge: new Uint8Array([
      21, 31, 105 /* 29 more random bytes generated by the server */,
    ]),
    extensions: {
      prf: {
        eval: {
          first: new TextEncoder().encode("prf-key-v1") // same text label to derive the key
        }
      }
    }
  }
});</code></pre></div><p>Because you use the same text label for the key (&#8220;pro-key-1&#8220;) you get the exact same key back so you can decrypt your data. </p><p>The beauty of this is that you can only get the key after you are authenticated using your passkey. There&#8217;s no key stored in the browser storage (localStorage, cookies, IndexedDB etc.) so even when your app is hacked, your private key can&#8217;t be stolen.</p><p>The WebAuthn PRF extension is supported in all major browsers.</p><p>Check out the demo on <a href="https://whatpwacando.today/encryption/">https://whatpwacando.today/encryption</a> </p><div><hr></div><p style="text-align: center;">Do you need help with your web app?</p><p style="text-align: center;">Book a 1-on-1 call with me and I will do my absolute best to answer all your questions and solve your problems.</p><p style="text-align: center;">&#8364;100 for one hour, money-back guarantee if you&#8217;re not satisfied.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://cal.com/dannymoerkerke/1-on-1-consult-with-danny-moerkerke&quot;,&quot;text&quot;:&quot;Book a call now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://cal.com/dannymoerkerke/1-on-1-consult-with-danny-moerkerke"><span>Book a call now</span></a></p><div><hr></div><p style="text-align: center;"></p><p></p><p></p>]]></content:encoded></item><item><title><![CDATA[Modern Web Weekly #70]]></title><description><![CDATA[The modern web, tested and explained in plain English]]></description><link>https://modernwebweekly.substack.com/p/modern-web-weekly-70</link><guid isPermaLink="false">https://modernwebweekly.substack.com/p/modern-web-weekly-70</guid><dc:creator><![CDATA[Danny Moerkerke]]></dc:creator><pubDate>Thu, 30 Apr 2026 11:04:47 GMT</pubDate><enclosure url="https://images.unsplash.com/photo-1665470909939-959569b20021?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyMTF8fHBvcHVwfGVufDB8fHx8MTc3NzU0Njg5Nnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1665470909939-959569b20021?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyMTF8fHBvcHVwfGVufDB8fHx8MTc3NzU0Njg5Nnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1665470909939-959569b20021?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyMTF8fHBvcHVwfGVufDB8fHx8MTc3NzU0Njg5Nnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1665470909939-959569b20021?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyMTF8fHBvcHVwfGVufDB8fHx8MTc3NzU0Njg5Nnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1665470909939-959569b20021?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyMTF8fHBvcHVwfGVufDB8fHx8MTc3NzU0Njg5Nnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1665470909939-959569b20021?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyMTF8fHBvcHVwfGVufDB8fHx8MTc3NzU0Njg5Nnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1665470909939-959569b20021?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyMTF8fHBvcHVwfGVufDB8fHx8MTc3NzU0Njg5Nnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" width="3000" height="2143" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1665470909939-959569b20021?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyMTF8fHBvcHVwfGVufDB8fHx8MTc3NzU0Njg5Nnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2143,&quot;width&quot;:3000,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;text&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="text" title="text" srcset="https://images.unsplash.com/photo-1665470909939-959569b20021?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyMTF8fHBvcHVwfGVufDB8fHx8MTc3NzU0Njg5Nnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1665470909939-959569b20021?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyMTF8fHBvcHVwfGVufDB8fHx8MTc3NzU0Njg5Nnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1665470909939-959569b20021?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyMTF8fHBvcHVwfGVufDB8fHx8MTc3NzU0Njg5Nnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1665470909939-959569b20021?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyMTF8fHBvcHVwfGVufDB8fHx8MTc3NzU0Njg5Nnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@mediamodifier">Mediamodifier</a> on <a href="https://unsplash.com">Unsplash</a></figcaption></figure></div><h2>Safari Tech Preview now supports the improved attr() function</h2><p>After Chrome, Safari Tech Preview 241 now also supports the improved <code>attr()</code> function. This function can read any attribute from the element it&#8217;s applied to. Using its output however, was limited to the CSS <code>content</code> attribute, but now you can use it in conjunction with any CSS property.</p><p>For example, you can now define the background color of a <code>&lt;div&gt;</code> with a <code>data </code>attribute and then use that in its CSS like this:</p><pre><code><code>&lt;div
  data-bg="#00ff00"&gt;&lt;/div&gt;

&lt;style&gt;
  div {
    background-color: attr(data-bg type(&lt;color&gt;));
  }
&lt;/style&gt;  </code></code></pre><p>The <code>attr()</code> function reads the <code>data-bg</code> attribute and declares that its value is a color value with <code>type(&lt;color&gt;)</code>. There are many other value types, for example <code>&lt;string&gt;</code>and <code>&lt;number&gt;</code>. You can find the complete list in the <a href="https://www.w3.org/TR/css-values-4/?ref=modern-web-weekly.ghost.io">specs</a>.</p><p>You can also define a CSS unit if the value you read from an attribute needs to include a unit value, like <code>px</code> for example:</p><pre><code><code>div {
  font-size: attr(data-font-size px);
}</code></code></pre><p>or use this as the value of a CSS custom property:</p><pre><code><code>/* set the value of the "--font-size" custom property */
div {
  --font-size: attr(data-font-size px);
}</code></code></pre><p>If the attribute that <code>attr()</code> reads is not present, you can also define a fallback value:</p><pre><code><code>div {
  background-color: attr(data-bg type(&lt;color&gt;), #00ff00);
}
</code></code></pre><p><em>(note the comma before the fallback value)</em></p><p>This way, you can expose a couple of (<code>data-</code>) attributes that can be used to style an element solely by changing its attribute values. The benefits that this approach has over utility classes and inline styles are that utility classes can only have fixed values and inline styles can be blocked by content security policies</p><p>Checkout the codepen where you can change the data-attributes of the <code>&lt;div&gt;</code> to change its styling and the <code>min</code> and <code>max</code> attributes of the slider to change the background color.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://codepen.io/dannymoerkerke/pen/pvzLgez" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!JIjU!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1978144-dd6b-4eee-a5d2-4362711ec206_738x300.heic 424w, https://substackcdn.com/image/fetch/$s_!JIjU!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1978144-dd6b-4eee-a5d2-4362711ec206_738x300.heic 848w, https://substackcdn.com/image/fetch/$s_!JIjU!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1978144-dd6b-4eee-a5d2-4362711ec206_738x300.heic 1272w, https://substackcdn.com/image/fetch/$s_!JIjU!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1978144-dd6b-4eee-a5d2-4362711ec206_738x300.heic 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!JIjU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1978144-dd6b-4eee-a5d2-4362711ec206_738x300.heic" width="738" height="300" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a1978144-dd6b-4eee-a5d2-4362711ec206_738x300.heic&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:300,&quot;width&quot;:738,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:6174,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/heic&quot;,&quot;href&quot;:&quot;https://codepen.io/dannymoerkerke/pen/pvzLgez&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://modernwebweekly.substack.com/i/158030521?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1978144-dd6b-4eee-a5d2-4362711ec206_738x300.heic&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!JIjU!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1978144-dd6b-4eee-a5d2-4362711ec206_738x300.heic 424w, https://substackcdn.com/image/fetch/$s_!JIjU!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1978144-dd6b-4eee-a5d2-4362711ec206_738x300.heic 848w, https://substackcdn.com/image/fetch/$s_!JIjU!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1978144-dd6b-4eee-a5d2-4362711ec206_738x300.heic 1272w, https://substackcdn.com/image/fetch/$s_!JIjU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1978144-dd6b-4eee-a5d2-4362711ec206_738x300.heic 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h4>Tailwind with attr()?</h4><p>Ready for a fun experiment? How about we implement the whole Tailwind library with a single stylesheet?</p><p>Well, sort of...</p><p>Tailwind is a CSS library that uses utility classes to add styling to HTML elements. So instead of setting <code>display: flex</code> on a <code>&lt;div&gt;</code> with CSS like this:</p><pre><code><code>div {
  display: flex;
}</code></code></pre><p>You would do something like this:</p><pre><code><code>&lt;div class="flex"&gt;&lt;/div&gt;</code></code></pre><p>To be honest, I&#8217;m not a fan of this approach because it throws the cascade (the &#8220;C&#8221; in CSS) out the window in favor of classes, but the new and improved <code>attr()</code> function inspired me to implement something similar.</p><p>In case you missed it, in Chrome 133 the <code>attr()</code> function can now be used to set the value of any CSS property whereas before, you could only use this with <code>content</code>. For example, you can now set the <code>background-color</code> of an element with an attribute, say <code>data-bg</code>:</p><pre><code><code>div {
  background-color: attr(data-bg type(&lt;color&gt;));
}  </code></code></pre><pre><code><code>&lt;div data-bg="red"&gt;&lt;/div&gt;</code></code></pre><p>The web app now reads the value &#8220;red&#8221; from the <code>data-bg</code> attribute and sets it as the <code>background-color</code> CSS property on the <code>&lt;div&gt;</code>. We need to specify that the attribute will give a color value so we need to set its type with <code>type(&lt;color&gt;)</code>. If we omit this type parameter it will default to <code>string</code>, but a string is not considered a valid color value in CSS since a value like <code>red</code> is a keyword, not a string.</p><p>This way, we could map any CSS property to an attribute and that&#8217;s exactly what I did in this codepen:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://codepen.io/dannymoerkerke/pen/ogvOpjz" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!jqyF!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7028d2c-67c7-44ae-85ab-49fcf4407e3a_2000x984.png 424w, https://substackcdn.com/image/fetch/$s_!jqyF!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7028d2c-67c7-44ae-85ab-49fcf4407e3a_2000x984.png 848w, https://substackcdn.com/image/fetch/$s_!jqyF!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7028d2c-67c7-44ae-85ab-49fcf4407e3a_2000x984.png 1272w, https://substackcdn.com/image/fetch/$s_!jqyF!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7028d2c-67c7-44ae-85ab-49fcf4407e3a_2000x984.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!jqyF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7028d2c-67c7-44ae-85ab-49fcf4407e3a_2000x984.png" width="1456" height="716" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a7028d2c-67c7-44ae-85ab-49fcf4407e3a_2000x984.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:716,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:&quot;&quot;,&quot;type&quot;:null,&quot;href&quot;:&quot;https://codepen.io/dannymoerkerke/pen/ogvOpjz&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!jqyF!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7028d2c-67c7-44ae-85ab-49fcf4407e3a_2000x984.png 424w, https://substackcdn.com/image/fetch/$s_!jqyF!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7028d2c-67c7-44ae-85ab-49fcf4407e3a_2000x984.png 848w, https://substackcdn.com/image/fetch/$s_!jqyF!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7028d2c-67c7-44ae-85ab-49fcf4407e3a_2000x984.png 1272w, https://substackcdn.com/image/fetch/$s_!jqyF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7028d2c-67c7-44ae-85ab-49fcf4407e3a_2000x984.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Click to edit or run this Codepen</figcaption></figure></div><h3>Limitations</h3><p>For properties that take a value and a unit like <code>px</code> or <code>rem</code> for example, the <code>attr()</code>function needs to specify that unit:</p><pre><code><code>div {
  width: attr(width px);
}</code></code></pre><p>and then you would specify a <code>width</code> of 50px like this:</p><pre><code><code>&lt;div
  width="50"
  &gt;
&lt;/div&gt;</code></code></pre><p>But this means that the value of the attribute is always parsed as a pixel value. You won&#8217;t be able to specify the value as &#8220;%&#8221; or &#8220;rem&#8221; unless you change the CSS rule:</p><pre><code><code>div {
  /* percentage value */
  width: attr(width %);

  /* rem value */
  width: attr(width rem);
}  </code></code></pre><p>Another limitation is that you can&#8217;t specify the <code>width</code> with a keyword like <code>fit-content</code>, for example, unless you change the rule to this:</p><pre><code><code>div {
  width: attr(width type(*));
}  </code></code></pre><p>where <code>type(*)</code> is a shorthand for all allowed keywords.</p><p>I had hoped I would be able to set this type for keywords as a fallback value like this, but unfortunately, that doesn&#8217;t work and causes the whole rule to fail:</p><pre><code><code>/* specify the value as a px value and if that doesn't work, 
 * the fallback value should be used that specifies its type as
 * type(*) meaning keywords can be used but unfortunately, this 
 * doesn't work
*/
div {
  width: attr(width px, attr(width type(*), auto));
}</code></code></pre><p>You can also explicitly mention the keywords to limit the number of allowed values. In the following example, only <code>fit-content</code> and <code>min-content</code> are allowed, and if the user specifies another value, the <code>width</code> will default to <code>auto</code> which is the fallback value specified here:</p><pre><code><code>div {
  width: attr(width type(fit-content | min-content), auto);
}   </code></code></pre><h3>Other things to note</h3><p>For shorthand properties like <code>padding</code> for example, I also added the expanded properties like <code>padding-top</code>, <code>padding-bottom</code> etc.</p><p>For the expanded properties (<code>padding-top</code>, <code>padding-bottom</code> etc.) to be able to override the shorthand property (<code>padding</code>), they need to be defined later in the stylesheet and the expanded properties need to have a fallback value equal to the shorthand property attribute value, (<code>attr(padding px)</code>) and if that attribute is not specified (<code>padding</code>) there needs to be a fallback for that as well (so it becomes <code>attr(padding px,auto)</code>):</p><pre><code><code>div {
  /* the shorthand "padding" comes first with a fallback value "auto" */
  padding: attr(padding px, auto);

  /* the expanded properties read their values from the 
   * corresponding attribute and fallback to the attribute
   * value of the shorthand property "padding" and if that is
   * not specified, they fallback to "auto"
  */
  padding-top: attr(padding-top px, attr(padding px, auto));
  padding-right: attr(padding-right px, attr(padding px, auto));
  padding-bottom: attr(padding-bottom px, attr(padding px, auto));
  padding-left: attr(padding-left px, attr(padding px, auto));
}</code></code></pre><p>If you don&#8217;t put <code>padding</code> first, the expanded properties won&#8217;t be able to override it, and if you don&#8217;t set the fallback value of the expanded properties to the value of the <code>padding</code>attribute (<code>attr(padding px, auto)</code>) they will fall back to <code>0px</code> and <code>padding</code> won&#8217;t work at all. You also need to provide the fallback value <code>auto</code> in <code>attr(padding px, auto) </code>otherwise, you won&#8217;t be able to set any of the expanded properties.</p><p>Lastly, I haven&#8217;t been able to get <code>border</code> to work as a shorthand property. I&#8217;ll post any updates here if I manage to fix this.</p><h3>Just because you can, doesn&#8217;t mean you should</h3><p>While this has been a fun experiment, I&#8217;m not sure if this is really of any practical use, so I don&#8217;t recommend you use this in production. It has taught me a lot about the <code>attr()</code>function, so I recommend you play with it and see what other uses you may come up with. As more CSS functions are getting support, like <code>ident()</code> I will update this demo and write about it here.</p><div><hr></div><h2>Light dismiss a <code>&lt;dialog</code>&gt; with the <code>closedby</code> property</h2><p>Safari Tech Preview also joins the light-dismiss-a-dialog party with full support for the <code>closedby</code> property.</p><p>This property configures which types of user actions can close a &lt;dialog&gt; element, typically the ESC-button and an outside click. You can declaratively set it using the corresponding <code>closedby</code> attribute:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;html&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-html">&lt;dialog closedby="any"&gt;</code></pre></div><p>closedby can take three values:</p><ul><li><p><code>closerequest</code>: the dialog can be dismissed with a platform-specific user action, typically the ESC-key on desktop</p></li><li><p><code>any</code>: the dialog can be dismissed with a light dismiss user action or a platform-specific user action, typically the ESC-key on desktop or an click outside of the dialog</p></li><li><p><code>none</code>: the dialog can only be closed by a developer-specified mechanism, like JavaScript or an invoker command</p></li></ul><p>When the value is <code>closerequest</code> or <code>any</code>, the dialog can always be closed with JavaScript or an invoker command. </p><p>When the closedby attribute is not specified or invalid, and the dialog is opened with <code>showModal()</code> or <code>command=&#8221;show-modal&#8221;</code>, it falls back to <br><code>closedby=&#8221;closerequest&#8221;.</code></p><p>If the dialog is opened with <code>show()</code>, it falls back to <code>closedby=&#8221;none&#8221;</code>.</p><p>When support lands in the main version of Safari, <code>closedby</code> will be supported in all major browsers.</p><p>Check the demo on codepen:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://codepen.io/dannymoerkerke/pen/xbxbJod" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!JIjU!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1978144-dd6b-4eee-a5d2-4362711ec206_738x300.heic 424w, https://substackcdn.com/image/fetch/$s_!JIjU!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1978144-dd6b-4eee-a5d2-4362711ec206_738x300.heic 848w, https://substackcdn.com/image/fetch/$s_!JIjU!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1978144-dd6b-4eee-a5d2-4362711ec206_738x300.heic 1272w, https://substackcdn.com/image/fetch/$s_!JIjU!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1978144-dd6b-4eee-a5d2-4362711ec206_738x300.heic 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!JIjU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1978144-dd6b-4eee-a5d2-4362711ec206_738x300.heic" width="738" height="300" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a1978144-dd6b-4eee-a5d2-4362711ec206_738x300.heic&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:300,&quot;width&quot;:738,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:6174,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:&quot;&quot;,&quot;type&quot;:&quot;image/heic&quot;,&quot;href&quot;:&quot;https://codepen.io/dannymoerkerke/pen/xbxbJod&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://modernwebweekly.substack.com/i/158030521?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1978144-dd6b-4eee-a5d2-4362711ec206_738x300.heic&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!JIjU!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1978144-dd6b-4eee-a5d2-4362711ec206_738x300.heic 424w, https://substackcdn.com/image/fetch/$s_!JIjU!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1978144-dd6b-4eee-a5d2-4362711ec206_738x300.heic 848w, https://substackcdn.com/image/fetch/$s_!JIjU!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1978144-dd6b-4eee-a5d2-4362711ec206_738x300.heic 1272w, https://substackcdn.com/image/fetch/$s_!JIjU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1978144-dd6b-4eee-a5d2-4362711ec206_738x300.heic 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><h2>PWA Audit: on your way to a great PWA</h2><p>Do you already have a PWA, but are you running into issues with performance, security, or functionality? Or are you not sure how to make your PWA better?</p><p>I can help you by running an audit of your PWA</p><p>I will evaluate it on more than 35 criteria and provide you with <strong>clear </strong>and <strong>actionable </strong>instructions on how to improve it. No generic stuff that you can get anywhere, but an in-depth quality checkup to get you on your way to a great PWA.</p><p>Some of the criteria I will evaluate it on are:</p><ul><li><p>Installability</p></li><li><p>Cross-device and cross-platform compatibility</p></li><li><p>Offline support</p></li><li><p>Usability</p></li><li><p>Effective use of modern web APIs</p></li><li><p>Performance</p></li><li><p>Security</p></li></ul><p>Your investment in the improvement of your PWA through the audit is &#8364;499, excluding VAT (where applicable).</p><p>If you want to request an audit or first would like to know more, you can <br><br><a href="https://whatpwacando.today/audit">fill out the form</a> or <a href="https://cal.com/dannymoerkerke/30min">book a call with me</a>.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://cal.com/dannymoerkerke/30min" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!5SyH!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1437bbbc-0e9d-466f-82bd-a29c12026252_733x150.heic 424w, https://substackcdn.com/image/fetch/$s_!5SyH!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1437bbbc-0e9d-466f-82bd-a29c12026252_733x150.heic 848w, https://substackcdn.com/image/fetch/$s_!5SyH!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1437bbbc-0e9d-466f-82bd-a29c12026252_733x150.heic 1272w, https://substackcdn.com/image/fetch/$s_!5SyH!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1437bbbc-0e9d-466f-82bd-a29c12026252_733x150.heic 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!5SyH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1437bbbc-0e9d-466f-82bd-a29c12026252_733x150.heic" width="733" height="150" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1437bbbc-0e9d-466f-82bd-a29c12026252_733x150.heic&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:150,&quot;width&quot;:733,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:9107,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:&quot;&quot;,&quot;type&quot;:&quot;image/heic&quot;,&quot;href&quot;:&quot;https://cal.com/dannymoerkerke/30min&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://modernwebweekly.substack.com/i/158031003?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1437bbbc-0e9d-466f-82bd-a29c12026252_733x150.heic&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!5SyH!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1437bbbc-0e9d-466f-82bd-a29c12026252_733x150.heic 424w, https://substackcdn.com/image/fetch/$s_!5SyH!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1437bbbc-0e9d-466f-82bd-a29c12026252_733x150.heic 848w, https://substackcdn.com/image/fetch/$s_!5SyH!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1437bbbc-0e9d-466f-82bd-a29c12026252_733x150.heic 1272w, https://substackcdn.com/image/fetch/$s_!5SyH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1437bbbc-0e9d-466f-82bd-a29c12026252_733x150.heic 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><div><hr></div><h2>The state of PWA deep linking</h2><p>One of the best features of native apps is that they are able to capture links within their scope to enable deep linking.</p><p>For example, when I have the Instagram app installed on my phone and someone sends me a link to an Instagram post and I click it, it will open the Instagram app instead of a browser.</p><p>PWAs also have this capability, but unfortunately, the story of where this works and when is a bit complicated. It differs on various OSes and browsers, so it&#8217;s not always easy to know when deep linking will work.</p><p>To help understand deep linking into PWAs, I tested all options and here are my findings.</p><h4>Web App link handling</h4><p>Chromium-based browsers like Chrome and Edge support the <code>handle_links</code>manifest.json member that enables PWAs to capture links within their scope, which simply means that the URL is within the <code>scope</code> as defined in manifest.json.</p><p>So when the scope of a PWA is &#8220;https://www.example.com/&#8221; and the URL of the clicked link is &#8220;https://www.example.com/foo&#8221;, then it will be captured by the PWA.</p><p>When a PWA wants to use this behavior, the <code>handle_links</code> member should be added to manifest.json:</p><pre><code><code>"handle_links": "preferred"</code></code></pre><p>The <code>handle_links</code> member can hold the following values:</p><ul><li><p>&#8220;preferred&#8221;: the user agent should handle links using matching PWAs and may promote link handling behavior.</p></li><li><p>&#8220;not-preferred&#8221;: the user agent should not handle links using matching PWAs and may not promote link handling behavior.</p></li><li><p>&#8220;auto&#8221;: default value if <code>handle_links</code> is not found in the manifest. The user agent may choose between &#8220;preferred&#8221; and &#8220;not-preferred&#8221;.</p></li></ul><p>See the <a href="https://github.com/WICG/pwa-url-handler/blob/main/handle_links/explainer.md">explainer</a> for details.</p><p>I tested the behavior of this on all platforms in all major browsers by sending a link in an email to myself and then clicking that link.</p><h4>Android</h4><p><em>PWA installed with Chrome<br><br></em>When I click the link in the GMail app or in Chrome, it&#8217;s always opened in the PWA, even when Chrome is not the default browser.</p><p>When the email is opened in Edge through the GMail website and I click the link, it opens in Edge but also offers to open it in the PWA:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!GbX0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a6102cd-1690-4ce5-b029-dd6dae36d743_300x667.heic" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!GbX0!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a6102cd-1690-4ce5-b029-dd6dae36d743_300x667.heic 424w, https://substackcdn.com/image/fetch/$s_!GbX0!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a6102cd-1690-4ce5-b029-dd6dae36d743_300x667.heic 848w, https://substackcdn.com/image/fetch/$s_!GbX0!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a6102cd-1690-4ce5-b029-dd6dae36d743_300x667.heic 1272w, https://substackcdn.com/image/fetch/$s_!GbX0!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a6102cd-1690-4ce5-b029-dd6dae36d743_300x667.heic 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!GbX0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a6102cd-1690-4ce5-b029-dd6dae36d743_300x667.heic" width="300" height="667" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5a6102cd-1690-4ce5-b029-dd6dae36d743_300x667.heic&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:667,&quot;width&quot;:300,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:33542,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/heic&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://modernwebweekly.substack.com/i/160455943?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a6102cd-1690-4ce5-b029-dd6dae36d743_300x667.heic&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!GbX0!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a6102cd-1690-4ce5-b029-dd6dae36d743_300x667.heic 424w, https://substackcdn.com/image/fetch/$s_!GbX0!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a6102cd-1690-4ce5-b029-dd6dae36d743_300x667.heic 848w, https://substackcdn.com/image/fetch/$s_!GbX0!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a6102cd-1690-4ce5-b029-dd6dae36d743_300x667.heic 1272w, https://substackcdn.com/image/fetch/$s_!GbX0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a6102cd-1690-4ce5-b029-dd6dae36d743_300x667.heic 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Opening link in Edge on Android with PWA installed with Chrome</figcaption></figure></div><p><em>PWA installed with Edge<br><br></em>When I click the link in the GMail app, the link is not opened in the PWA but in an in-app browser powered by the default browser:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!zBeO!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F966813a4-957b-4e7b-a146-e0f55bc6da57_3310x2400.heic" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!zBeO!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F966813a4-957b-4e7b-a146-e0f55bc6da57_3310x2400.heic 424w, https://substackcdn.com/image/fetch/$s_!zBeO!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F966813a4-957b-4e7b-a146-e0f55bc6da57_3310x2400.heic 848w, https://substackcdn.com/image/fetch/$s_!zBeO!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F966813a4-957b-4e7b-a146-e0f55bc6da57_3310x2400.heic 1272w, https://substackcdn.com/image/fetch/$s_!zBeO!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F966813a4-957b-4e7b-a146-e0f55bc6da57_3310x2400.heic 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!zBeO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F966813a4-957b-4e7b-a146-e0f55bc6da57_3310x2400.heic" width="1456" height="1056" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/966813a4-957b-4e7b-a146-e0f55bc6da57_3310x2400.heic&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1056,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:393483,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/heic&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://modernwebweekly.substack.com/i/160455943?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F966813a4-957b-4e7b-a146-e0f55bc6da57_3310x2400.heic&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!zBeO!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F966813a4-957b-4e7b-a146-e0f55bc6da57_3310x2400.heic 424w, https://substackcdn.com/image/fetch/$s_!zBeO!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F966813a4-957b-4e7b-a146-e0f55bc6da57_3310x2400.heic 848w, https://substackcdn.com/image/fetch/$s_!zBeO!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F966813a4-957b-4e7b-a146-e0f55bc6da57_3310x2400.heic 1272w, https://substackcdn.com/image/fetch/$s_!zBeO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F966813a4-957b-4e7b-a146-e0f55bc6da57_3310x2400.heic 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">In-app browser of GMail app on Android powered by the default browser</figcaption></figure></div><p>When the email is opened in any browser through the GMail website and I click it, it&#8217;s opened in the same browser the GMail website was opened with.</p><p><em>PWA installed with Firefox<br><br></em>Here, the behaviour is the same as when the PWA is installed with Edge: when I click the link in the GMail app, the link is not opened in the PWA but in an in-app browser powered by the default browser.</p><p><strong>Conclusion for Android: </strong>deep linking only works reliably when the PWA is installed with Chrome.</p><h4>iOS</h4><p>On iOS, there is unfortunately no support for deep linking. All clicked links open in the default browser, regardless of whether a PWA is installed and with which browser.</p><h4>ChromeOS</h4><p>On ChromeOS, the PWA can only be installed with Chrome and deep linking works reliably.</p><h4>macOS</h4><p><em>PWA installed with Chrome<br><br></em>When I click the link in the Mail app, it opens in the default browser. When it&#8217;s opened in Chrome, it offers to open it in the PWA instead:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!m3Y7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1eb835e-bd50-48e4-bd5a-662934751492_3024x584.heic" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!m3Y7!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1eb835e-bd50-48e4-bd5a-662934751492_3024x584.heic 424w, https://substackcdn.com/image/fetch/$s_!m3Y7!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1eb835e-bd50-48e4-bd5a-662934751492_3024x584.heic 848w, https://substackcdn.com/image/fetch/$s_!m3Y7!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1eb835e-bd50-48e4-bd5a-662934751492_3024x584.heic 1272w, https://substackcdn.com/image/fetch/$s_!m3Y7!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1eb835e-bd50-48e4-bd5a-662934751492_3024x584.heic 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!m3Y7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1eb835e-bd50-48e4-bd5a-662934751492_3024x584.heic" width="1456" height="281" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b1eb835e-bd50-48e4-bd5a-662934751492_3024x584.heic&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:281,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:58622,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/heic&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://modernwebweekly.substack.com/i/160455943?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1eb835e-bd50-48e4-bd5a-662934751492_3024x584.heic&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!m3Y7!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1eb835e-bd50-48e4-bd5a-662934751492_3024x584.heic 424w, https://substackcdn.com/image/fetch/$s_!m3Y7!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1eb835e-bd50-48e4-bd5a-662934751492_3024x584.heic 848w, https://substackcdn.com/image/fetch/$s_!m3Y7!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1eb835e-bd50-48e4-bd5a-662934751492_3024x584.heic 1272w, https://substackcdn.com/image/fetch/$s_!m3Y7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1eb835e-bd50-48e4-bd5a-662934751492_3024x584.heic 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption">Chrome on macOS offering to open the PWA installed with Chrome</figcaption></figure></div><p>When the email is opened in any browser through the GMail website and I click it, it&#8217;s opened in the same browser the GMail website was opened with.</p><p><em>PWA installed with Edge<br><br></em>When I click the link in the Mail app, it opens in the default browser. When it&#8217;s opened in Edge, it offers to open it in the PWA instead:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!m0-I!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11ee28b2-301e-4781-a180-47e24b201e14_3024x584.heic" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!m0-I!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11ee28b2-301e-4781-a180-47e24b201e14_3024x584.heic 424w, https://substackcdn.com/image/fetch/$s_!m0-I!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11ee28b2-301e-4781-a180-47e24b201e14_3024x584.heic 848w, https://substackcdn.com/image/fetch/$s_!m0-I!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11ee28b2-301e-4781-a180-47e24b201e14_3024x584.heic 1272w, https://substackcdn.com/image/fetch/$s_!m0-I!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11ee28b2-301e-4781-a180-47e24b201e14_3024x584.heic 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!m0-I!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11ee28b2-301e-4781-a180-47e24b201e14_3024x584.heic" width="1456" height="281" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/11ee28b2-301e-4781-a180-47e24b201e14_3024x584.heic&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:281,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:60629,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/heic&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://modernwebweekly.substack.com/i/160455943?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11ee28b2-301e-4781-a180-47e24b201e14_3024x584.heic&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!m0-I!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11ee28b2-301e-4781-a180-47e24b201e14_3024x584.heic 424w, https://substackcdn.com/image/fetch/$s_!m0-I!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11ee28b2-301e-4781-a180-47e24b201e14_3024x584.heic 848w, https://substackcdn.com/image/fetch/$s_!m0-I!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11ee28b2-301e-4781-a180-47e24b201e14_3024x584.heic 1272w, https://substackcdn.com/image/fetch/$s_!m0-I!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11ee28b2-301e-4781-a180-47e24b201e14_3024x584.heic 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption">Edge on macOS offering to open the PWA installed with Edge</figcaption></figure></div><p><em>Web app added to Dock with Safari<br><br></em>When I click the link in the Mail app, it opens in the default browser. When it&#8217;s opened in Safari, it offers to open it in the web app added to the Dock instead:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!fDOK!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabe7cb0e-f94e-4163-b202-211a34cd6660_3024x584.heic" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!fDOK!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabe7cb0e-f94e-4163-b202-211a34cd6660_3024x584.heic 424w, https://substackcdn.com/image/fetch/$s_!fDOK!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabe7cb0e-f94e-4163-b202-211a34cd6660_3024x584.heic 848w, https://substackcdn.com/image/fetch/$s_!fDOK!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabe7cb0e-f94e-4163-b202-211a34cd6660_3024x584.heic 1272w, https://substackcdn.com/image/fetch/$s_!fDOK!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabe7cb0e-f94e-4163-b202-211a34cd6660_3024x584.heic 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!fDOK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabe7cb0e-f94e-4163-b202-211a34cd6660_3024x584.heic" width="1456" height="281" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/abe7cb0e-f94e-4163-b202-211a34cd6660_3024x584.heic&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:281,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:43106,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/heic&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://modernwebweekly.substack.com/i/160455943?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabe7cb0e-f94e-4163-b202-211a34cd6660_3024x584.heic&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!fDOK!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabe7cb0e-f94e-4163-b202-211a34cd6660_3024x584.heic 424w, https://substackcdn.com/image/fetch/$s_!fDOK!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabe7cb0e-f94e-4163-b202-211a34cd6660_3024x584.heic 848w, https://substackcdn.com/image/fetch/$s_!fDOK!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabe7cb0e-f94e-4163-b202-211a34cd6660_3024x584.heic 1272w, https://substackcdn.com/image/fetch/$s_!fDOK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabe7cb0e-f94e-4163-b202-211a34cd6660_3024x584.heic 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption">Safari on macOS offering to open the web app added to the Dock with Safari</figcaption></figure></div><p>But when the web app that was added to the Dock has been interacted with for a while, most notably, when it has been opened directly from the Dock at least once, clicked links open in the web app consistently.</p><p>On macOS, PWAs can&#8217;t be installed with Firefox.</p><p><strong>Conclusion for macOS: </strong>deep linking only works reliably when the web app is added to the Dock with Safari.</p><h4>Windows</h4><p>Unfortunately, deep linking is not supported on Windows as all clicked links open in the default browser. Just like on macOS, Chrome and Edge offer to open the PWA instead when the PWA was installed through them.</p><p>On Windows, PWAs can&#8217;t be installed with Firefox.</p>]]></content:encoded></item><item><title><![CDATA[Modern Web Weekly #69]]></title><description><![CDATA[The modern web, tested and explained in plain English]]></description><link>https://modernwebweekly.substack.com/p/modern-web-weekly-69</link><guid isPermaLink="false">https://modernwebweekly.substack.com/p/modern-web-weekly-69</guid><dc:creator><![CDATA[Danny Moerkerke]]></dc:creator><pubDate>Fri, 20 Mar 2026 15:57:45 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Nguk!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F272b2b84-6440-4a5d-b06b-576930b578ba_1200x675.heic" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Nguk!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F272b2b84-6440-4a5d-b06b-576930b578ba_1200x675.heic" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Nguk!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F272b2b84-6440-4a5d-b06b-576930b578ba_1200x675.heic 424w, https://substackcdn.com/image/fetch/$s_!Nguk!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F272b2b84-6440-4a5d-b06b-576930b578ba_1200x675.heic 848w, https://substackcdn.com/image/fetch/$s_!Nguk!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F272b2b84-6440-4a5d-b06b-576930b578ba_1200x675.heic 1272w, https://substackcdn.com/image/fetch/$s_!Nguk!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F272b2b84-6440-4a5d-b06b-576930b578ba_1200x675.heic 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Nguk!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F272b2b84-6440-4a5d-b06b-576930b578ba_1200x675.heic" width="1200" height="675" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/272b2b84-6440-4a5d-b06b-576930b578ba_1200x675.heic&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:675,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:14896,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/heic&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://modernwebweekly.substack.com/i/190109649?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F272b2b84-6440-4a5d-b06b-576930b578ba_1200x675.heic&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Nguk!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F272b2b84-6440-4a5d-b06b-576930b578ba_1200x675.heic 424w, https://substackcdn.com/image/fetch/$s_!Nguk!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F272b2b84-6440-4a5d-b06b-576930b578ba_1200x675.heic 848w, https://substackcdn.com/image/fetch/$s_!Nguk!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F272b2b84-6440-4a5d-b06b-576930b578ba_1200x675.heic 1272w, https://substackcdn.com/image/fetch/$s_!Nguk!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F272b2b84-6440-4a5d-b06b-576930b578ba_1200x675.heic 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>How to install a PWA with Samsung Internet Browser</h2><p>I found <a href="https://www.reddit.com/r/PWA/comments/1ruvnl9/anybody_having_issues_installing_pwas_from/">a report on Reddit</a> this week that pointed to issues with installing PWAs through Samsung Internet Browser on Android.</p><p>Some PWAs were flagged as &#8220;unsafe&#8221; by Google Play Protect, and others, including my What PWA Can Do Today, simply failed silently when trying to install. In that case, a message would appear in the Notification Center, stating that the download of the PWA failed, with no further explanation.</p><p>After researching, I found that the installation silently failed when <code>manifest.json</code> contained the <code>share_target</code> member with <code>method</code> set to <code>POST</code>, which is used to share files to the PWA:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;json&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-json">"share_target": {
  "action": "/share",
  "method": "POST",
  "enctype": "multipart/form-data",
  "params": {
    "title": "title",
    "text": "text",
    "url": "url",
    "files": [
      {
        "name": "files",
        "accept": [
          "image/png",
          "image/jpg",
          "image/jpeg",
          "image/gif",
          "image/webp",
          ".png",
          ".jpg",
          ".jpeg",
          ".gif",
          ".webp"
        ]
      }
    ]
  }
}</code></pre></div><p>While <code>share_target</code> with <code>method</code> <code>GET</code> does work, which is used to share only text content to the PWA:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;json&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-json">"share_target": {
  "action": "/share",
  "method": "GET",
  "params": {
    "title": "title",
    "text": "text",
    "url": "url"
  }
}</code></pre></div><p>After I fixed this, the PWA was still marked as &#8220;unsafe&#8221; by Google Play Protect, and the user was greeted with a rather scary warning:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!QyIt!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc91fde61-e61c-4780-9668-611253bd61f9_350x525.heic" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!QyIt!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc91fde61-e61c-4780-9668-611253bd61f9_350x525.heic 424w, https://substackcdn.com/image/fetch/$s_!QyIt!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc91fde61-e61c-4780-9668-611253bd61f9_350x525.heic 848w, https://substackcdn.com/image/fetch/$s_!QyIt!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc91fde61-e61c-4780-9668-611253bd61f9_350x525.heic 1272w, https://substackcdn.com/image/fetch/$s_!QyIt!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc91fde61-e61c-4780-9668-611253bd61f9_350x525.heic 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!QyIt!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc91fde61-e61c-4780-9668-611253bd61f9_350x525.heic" width="350" height="525" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c91fde61-e61c-4780-9668-611253bd61f9_350x525.heic&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:525,&quot;width&quot;:350,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:21732,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/heic&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://modernwebweekly.substack.com/i/190109649?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc91fde61-e61c-4780-9668-611253bd61f9_350x525.heic&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!QyIt!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc91fde61-e61c-4780-9668-611253bd61f9_350x525.heic 424w, https://substackcdn.com/image/fetch/$s_!QyIt!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc91fde61-e61c-4780-9668-611253bd61f9_350x525.heic 848w, https://substackcdn.com/image/fetch/$s_!QyIt!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc91fde61-e61c-4780-9668-611253bd61f9_350x525.heic 1272w, https://substackcdn.com/image/fetch/$s_!QyIt!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc91fde61-e61c-4780-9668-611253bd61f9_350x525.heic 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Google Play Protect warning in Samsung Browser when installing a PWA</figcaption></figure></div><p>Samsung is currently the only browser with its own pipeline for generating a WebAPK. This is a thin wrapper around a web app that integrates it into Android to make it behave like a native Android app. Until recently, a WebAPK was only generated when a PWA was installed through Chrome, which prompted Samsung to build its own pipeline to generate one when a PWA is installed through Samsung Internet Browser.</p><p>For some reason, Google Play Protect does not always trust this WebAPK, so it displays the warning dialog above. This doesn&#8217;t mean there&#8217;s anything (technically) wrong with the PWA or that it&#8217;s unsafe, but unfortunately, it will scare users. </p><p>The criteria for Google Play Protect to flag the PWA as unsafe may have to do with its reputation. For example, the PWAs of X and Uber are not flagged as unsafe. The X PWA lists its Android native app in its <code>manifest.json</code> file with the <code>android_package_name</code> and the <code>related_applications</code> members. This may signal to Google Play Protect that the X PWA is more trustworthy, although Uber doesn&#8217;t have this, so the criteria may be based on reputation/usage alone.</p><p>When you tap the &#8220;Learn more&#8221; link, the &#8220;Install anyway&#8221; link is displayed, and the PWA can be installed without problems.</p><h4>How to handle installing in Samsung Internet Browser</h4><p>To make sure your PWA can be installed through Samsung Internet Browser on Android, make sure your <code>manifest.json</code> doesn&#8217;t have <code>share_target</code> with method set to <code>POST</code>. For What PWA Can Do Today, this was a problem because I needed this for the Share Target demo.</p><p>I managed to solve this by providing a separate <code>manifest.json</code> for Samsung Internet Browser, which limits the Share Target demo to only text sharing by setting <code>method</code> to <code>GET</code>:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;html&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-html">&lt;link rel="manifest" href="/manifest.json"&gt;
&lt;script&gt;
  if (/SamsungBrowser\//.test(navigator.userAgent)) {
    document.querySelector('link[rel="manifest"]').setAttribute('href', '/manifest-samsung.json');
  }
&lt;/script&gt;</code></pre></div><p>When Samsung Internet Browser is detected by parsing the <code>navigator.userAgent</code> string, the <code>href</code> property of the manifest <code>&lt;link&gt;</code> tag is set to point to the <br><code>manifest-samsung.json</code> file, which contains <code>method</code> set to <code>GET</code>.</p><p>If your PWA installs without (technical) issues but is still flagged as unsafe by Google Play Protect, the only thing you can basically do is inform your users that there&#8217;s nothing wrong with your PWA and they can safely install it.</p><p>I updated the <a href="https://whatpwacando.today/installation">Installation demo</a> of What PWA Can Do Today with a screenshot of the warning that you could also use to inform your users.</p><div><hr></div><h2>Style an anchor-positioned element based on its fallback position</h2><p>In Chrome 143+, you can now style anchor-positioned elements based on the fallback position that is applied to them using <em>anchor queries</em>.</p><p>An anchor query is a type of container query that applies styles to an anchor-positioned element based on which fallback position applies to it.</p><p>You can check out <a href="https://www.youtube.com/watch?v=u5-Vuduc9mk">this video by Una Kravetz</a> on YouTube that explains the concept of anchor queries. This enabled me to finally finish a demo of an animated anchor-positioned menu that needed different animations based on the active fallback position:</p><p>As you can see in the video above, the menu expands in the direction of the opposite corner of the screen by expanding the width and height of a container <code>&lt;div&gt;</code> inside the menu.</p><p>The default position of the button is the top left corner of the screen, and when it&#8217;s clicked, the menu expands towards the bottom left of the screen. To accomplish this effect, the container <code>&lt;div&gt;</code> inside the menu is flex-positioned, and the menu itself is aligned with the bottom right corner of the container <code>&lt;div&gt;</code> so when it expands, the menu itself is revealed from its bottom right corner:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!VjCc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41bde3f9-3a1b-4a56-93a8-24df56a3d08c_628x510.heic" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!VjCc!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41bde3f9-3a1b-4a56-93a8-24df56a3d08c_628x510.heic 424w, https://substackcdn.com/image/fetch/$s_!VjCc!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41bde3f9-3a1b-4a56-93a8-24df56a3d08c_628x510.heic 848w, https://substackcdn.com/image/fetch/$s_!VjCc!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41bde3f9-3a1b-4a56-93a8-24df56a3d08c_628x510.heic 1272w, https://substackcdn.com/image/fetch/$s_!VjCc!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41bde3f9-3a1b-4a56-93a8-24df56a3d08c_628x510.heic 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!VjCc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41bde3f9-3a1b-4a56-93a8-24df56a3d08c_628x510.heic" width="628" height="510" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/41bde3f9-3a1b-4a56-93a8-24df56a3d08c_628x510.heic&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:510,&quot;width&quot;:628,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:10498,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/heic&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://modernwebweekly.substack.com/i/168297548?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41bde3f9-3a1b-4a56-93a8-24df56a3d08c_628x510.heic&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!VjCc!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41bde3f9-3a1b-4a56-93a8-24df56a3d08c_628x510.heic 424w, https://substackcdn.com/image/fetch/$s_!VjCc!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41bde3f9-3a1b-4a56-93a8-24df56a3d08c_628x510.heic 848w, https://substackcdn.com/image/fetch/$s_!VjCc!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41bde3f9-3a1b-4a56-93a8-24df56a3d08c_628x510.heic 1272w, https://substackcdn.com/image/fetch/$s_!VjCc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41bde3f9-3a1b-4a56-93a8-24df56a3d08c_628x510.heic 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Menu revealed from its bottom right corner</figcaption></figure></div><p>The HTML looks like this:</p><pre><code><code>&lt;div id="menu" popover&gt;
  &lt;div id="menu-container"&gt;      
    &lt;ul&gt;
      &lt;li&gt;A menu item&lt;/li&gt;
      &lt;li&gt;Another menu item&lt;/li&gt;
      &lt;li&gt;A very looooong menu item&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/div&gt;
 &lt;/div&gt;</code></code></pre><p>The container <code>&lt;div&gt;</code> has this CSS to accomplish this effect:</p><pre><code><code>#menu-container {
  justify-content: flex-end;
  align-items: flex-end;
}</code></code></pre><p>However, when a fallback position is active, for example when the menu button is in the top right corner, the menu needs to expand towards the bottom left corner of the screen, so this CSS would need to be applied:</p><pre><code><code>#menu-container {
  justify-content: flex-end;
  align-items: flex-start; // changed to flex-start
}</code></code></pre><p>Before anchor queries, this wasn&#8217;t possible, as only the <code>@position-try</code> at-rule was available that only allows a <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@position-try#descriptors">limited list of CSS properties</a>. Using anchor queries, we can now apply all CSS properties.</p><p>First, we need to define the container element, which will be the menu <code>&lt;div&gt;</code> with <code>container-type</code>:</p><pre><code><code>#menu {
  container-type: anchored;
}</code></code></pre><p>Then, we define an anchor query for the container <code>&lt;div&gt; </code>that applies its styles when the fallback position is active for when the menu button is in the top right corner of the screen:</p><pre><code><code>#menu-container {

  // default styles
  justify-content: flex-end;
  align-items: flex-start;

  // styles for the fallback position "--bl"
  @container anchored(fallback: --bl) {
    justify-content: flex-end;
    align-items: flex-start;
  }
}</code></code></pre><p>Now, when the fallback position &#8220;--bl&#8221; is active, <code>align-items</code> will have the value <code>flex-start</code> instead of <code>flex-end</code>.</p><p>This way, we can define anchor queries for all applicable fallback positions.</p><p>In addition to named fallback positions, you can also define anchor queries for fallback positions that use the flip keywords:</p><pre><code><code>#menu {
  position-try-fallbacks: flip-block;
}

#menu-container {

  @container anchored(fallback: flip-block) {
    ...
  }
}</code></code></pre><p>Keep in mind that anchor queries are container queries, so the styles inside these queries apply to <em>an element inside the anchor positioned element</em> and <em>not</em> the anchor positioned element itself, since that element is the container.</p><p>So in the case of the example above, the menu is the container, and the anchor query is defined inside the container <code>&lt;div&gt;</code> and not the menu itself. This wasn&#8217;t fully clear to me in the beginning.</p><p>Check out this <a href="https://codepen.io/dannymoerkerke/pen/YPyyvow">codepen</a> for the demo.</p><div><hr></div><h2>PWA Audit: on your way to a great PWA</h2><p>Do you already have a PWA, but are you running into issues with performance, security, or functionality? Or are you not sure how to make your PWA better?</p><p>I can help you by running an audit of your PWA</p><p>I will evaluate it on more than 35 criteria and provide you with <strong>clear </strong>and <strong>actionable </strong>instructions on how to improve it. No generic stuff that you can get anywhere, but an in-depth quality checkup to get you on your way to a great PWA.</p><p>Some of the criteria I will evaluate it on are:</p><ul><li><p>Installability</p></li><li><p>Cross-device and cross-platform compatibility</p></li><li><p>Offline support</p></li><li><p>Usability</p></li><li><p>Effective use of modern web APIs</p></li><li><p>Performance</p></li><li><p>Security</p></li></ul><p>Your investment in the improvement of your PWA through the audit is &#8364;499, excluding VAT (where applicable).</p><p>If you want to request an audit or first would like to know more, you can <br><a href="https://whatpwacando.today/audit">fill out the form</a> or <a href="https://cal.com/dannymoerkerke/30min">book a call with me</a>.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://cal.com/dannymoerkerke/30min" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!5SyH!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1437bbbc-0e9d-466f-82bd-a29c12026252_733x150.heic 424w, https://substackcdn.com/image/fetch/$s_!5SyH!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1437bbbc-0e9d-466f-82bd-a29c12026252_733x150.heic 848w, https://substackcdn.com/image/fetch/$s_!5SyH!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1437bbbc-0e9d-466f-82bd-a29c12026252_733x150.heic 1272w, https://substackcdn.com/image/fetch/$s_!5SyH!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1437bbbc-0e9d-466f-82bd-a29c12026252_733x150.heic 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!5SyH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1437bbbc-0e9d-466f-82bd-a29c12026252_733x150.heic" width="733" height="150" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1437bbbc-0e9d-466f-82bd-a29c12026252_733x150.heic&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:150,&quot;width&quot;:733,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:9107,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:&quot;&quot;,&quot;type&quot;:&quot;image/heic&quot;,&quot;href&quot;:&quot;https://cal.com/dannymoerkerke/30min&quot;,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://modernwebweekly.substack.com/i/158031003?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1437bbbc-0e9d-466f-82bd-a29c12026252_733x150.heic&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!5SyH!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1437bbbc-0e9d-466f-82bd-a29c12026252_733x150.heic 424w, https://substackcdn.com/image/fetch/$s_!5SyH!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1437bbbc-0e9d-466f-82bd-a29c12026252_733x150.heic 848w, https://substackcdn.com/image/fetch/$s_!5SyH!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1437bbbc-0e9d-466f-82bd-a29c12026252_733x150.heic 1272w, https://substackcdn.com/image/fetch/$s_!5SyH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1437bbbc-0e9d-466f-82bd-a29c12026252_733x150.heic 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><div><hr></div><h2>Updates to basic-service-worker</h2><p>I made a few updates to my project <code>basic-service-worker</code> to improve its functionality and make it more robust.</p><p><a href="https://github.com/DannyMoerkerke/basic-service-worker">basic-service-worker</a> is a, well, basic service worker that makes your web app work offline and that updates as soon as possible when there are changes in your web app.</p><p>A common issue with service workers is that they don&#8217;t always update immediately  when there are changes. <code>basic-service-worker</code> makes sure your service worker updates whenever there are changes and the user navigates to another page.</p><p>This works for 99.9% of the time, but in the rare event that it doesn&#8217;t work, <br><code>basic-service-worker</code> wouldn&#8217;t retry, and this is now fixed. Now, when <code>basic-service-worker</code> detects that the newly installed service worker is not activated, it will retry until it is.</p><h4>Usage in single-page apps </h4><p>Another issue was that the page needed to be fully reloaded for the newly installed service worker to be activated, which means that this wouldn&#8217;t work in single-page apps where navigations do not result in full page reloads.</p><p>It now uses the Navigation API to detect single-page app navigations. When there is a newly installed service worker waiting to be activated, it will replace a single-page app navigation with a full page navigation, so the new service worker is activated.</p><p>Of course, this may not always be the behavior you want, so in that case you can simply remove this event handler, so the service worker will only be activated when the user restarts the app or does a full page reload.</p><p>Check the readme in <a href="https://github.com/DannyMoerkerke/basic-service-worker">the repo on Github</a> for details.</p>]]></content:encoded></item><item><title><![CDATA[Modern Web Weekly #68]]></title><description><![CDATA[The Modern Web, tested and explained in plain English]]></description><link>https://modernwebweekly.substack.com/p/modern-web-weekly-68</link><guid isPermaLink="false">https://modernwebweekly.substack.com/p/modern-web-weekly-68</guid><dc:creator><![CDATA[Danny Moerkerke]]></dc:creator><pubDate>Fri, 06 Mar 2026 14:44:10 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!PpF5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f3f4b6d-2757-44ea-bd8f-f025e7b59524_528x385.heic" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!PpF5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f3f4b6d-2757-44ea-bd8f-f025e7b59524_528x385.heic" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!PpF5!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f3f4b6d-2757-44ea-bd8f-f025e7b59524_528x385.heic 424w, https://substackcdn.com/image/fetch/$s_!PpF5!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f3f4b6d-2757-44ea-bd8f-f025e7b59524_528x385.heic 848w, https://substackcdn.com/image/fetch/$s_!PpF5!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f3f4b6d-2757-44ea-bd8f-f025e7b59524_528x385.heic 1272w, https://substackcdn.com/image/fetch/$s_!PpF5!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f3f4b6d-2757-44ea-bd8f-f025e7b59524_528x385.heic 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!PpF5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f3f4b6d-2757-44ea-bd8f-f025e7b59524_528x385.heic" width="528" height="385" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7f3f4b6d-2757-44ea-bd8f-f025e7b59524_528x385.heic&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:385,&quot;width&quot;:528,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:8756,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/heic&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://modernwebweekly.substack.com/i/188487600?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f3f4b6d-2757-44ea-bd8f-f025e7b59524_528x385.heic&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!PpF5!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f3f4b6d-2757-44ea-bd8f-f025e7b59524_528x385.heic 424w, https://substackcdn.com/image/fetch/$s_!PpF5!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f3f4b6d-2757-44ea-bd8f-f025e7b59524_528x385.heic 848w, https://substackcdn.com/image/fetch/$s_!PpF5!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f3f4b6d-2757-44ea-bd8f-f025e7b59524_528x385.heic 1272w, https://substackcdn.com/image/fetch/$s_!PpF5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f3f4b6d-2757-44ea-bd8f-f025e7b59524_528x385.heic 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Customisable &lt;select&gt; now in Safari Tech Preview 238</h2><p>After Chrome, Safari has now also implemented the fully customisable <code>&lt;select&gt;</code> element in Tech Preview 238. This means we can now fully style this element, which was barely possible before.</p><p>To opt-in to the new customizable <code>&lt;select&gt;</code> add this to your CSS:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;css&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-css">select,
::picker(select) {
  appearance: base-selec&#8230;</code></pre></div>
      <p>
          <a href="https://modernwebweekly.substack.com/p/modern-web-weekly-68">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Modern Web Weekly #67]]></title><description><![CDATA[The modern web, tested and explain in plain English]]></description><link>https://modernwebweekly.substack.com/p/modern-web-weekly-67</link><guid isPermaLink="false">https://modernwebweekly.substack.com/p/modern-web-weekly-67</guid><dc:creator><![CDATA[Danny Moerkerke]]></dc:creator><pubDate>Thu, 19 Feb 2026 15:33:03 GMT</pubDate><enclosure url="https://images.unsplash.com/photo-1685381949388-bb0402fbe133?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw2fHxub3RpZmljYXRpb258ZW58MHx8fHwxNzcxNDI3MjY4fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1685381949388-bb0402fbe133?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw2fHxub3RpZmljYXRpb258ZW58MHx8fHwxNzcxNDI3MjY4fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1685381949388-bb0402fbe133?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw2fHxub3RpZmljYXRpb258ZW58MHx8fHwxNzcxNDI3MjY4fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1685381949388-bb0402fbe133?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw2fHxub3RpZmljYXRpb258ZW58MHx8fHwxNzcxNDI3MjY4fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1685381949388-bb0402fbe133?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw2fHxub3RpZmljYXRpb258ZW58MHx8fHwxNzcxNDI3MjY4fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1685381949388-bb0402fbe133?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw2fHxub3RpZmljYXRpb258ZW58MHx8fHwxNzcxNDI3MjY4fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1685381949388-bb0402fbe133?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw2fHxub3RpZmljYXRpb258ZW58MHx8fHwxNzcxNDI3MjY4fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" width="4500" height="3000" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1685381949388-bb0402fbe133?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw2fHxub3RpZmljYXRpb258ZW58MHx8fHwxNzcxNDI3MjY4fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:3000,&quot;width&quot;:4500,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;a white square with a red circle on top of it&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="a white square with a red circle on top of it" title="a white square with a red circle on top of it" srcset="https://images.unsplash.com/photo-1685381949388-bb0402fbe133?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw2fHxub3RpZmljYXRpb258ZW58MHx8fHwxNzcxNDI3MjY4fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1685381949388-bb0402fbe133?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw2fHxub3RpZmljYXRpb258ZW58MHx8fHwxNzcxNDI3MjY4fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1685381949388-bb0402fbe133?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw2fHxub3RpZmljYXRpb258ZW58MHx8fHwxNzcxNDI3MjY4fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1685381949388-bb0402fbe133?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw2fHxub3RpZmljYXRpb258ZW58MHx8fHwxNzcxNDI3MjY4fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@philipsfuture">Philip Oroni</a> on <a href="https://unsplash.com">Unsplash</a></figcaption></figure></div><h2>A declarative install element</h2><p>In Modern Web Weekly <a href="https://modernwebweekly.substack.com/p/modern-web-weekly-65">#65</a> and <a href="https://modernwebweekly.substack.com/p/modern-web-weekly-59">#59</a>, I wrote about the Web Install API (now stable in Chrome 144!) that adds the <code>install</code> method to <code>navigator</code> and that enables web apps to be installed as PWAs, also from other domains.</p><p>Chrome 147 now also introduces an origin trial for the declarative <code>&lt;install&gt;</code> ele&#8230;</p>
      <p>
          <a href="https://modernwebweekly.substack.com/p/modern-web-weekly-67">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Modern Web Weekly #66 ]]></title><description><![CDATA[The modern web, tested and explained in plain English]]></description><link>https://modernwebweekly.substack.com/p/modern-web-weekly-66</link><guid isPermaLink="false">https://modernwebweekly.substack.com/p/modern-web-weekly-66</guid><dc:creator><![CDATA[Danny Moerkerke]]></dc:creator><pubDate>Thu, 05 Feb 2026 15:13:43 GMT</pubDate><enclosure url="https://images.unsplash.com/photo-1531376653594-e9bcf0f0c65b?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMXx8Y29tcHJlc3Npb258ZW58MHx8fHwxNzY4NDk3Mzc1fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1531376653594-e9bcf0f0c65b?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMXx8Y29tcHJlc3Npb258ZW58MHx8fHwxNzY4NDk3Mzc1fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1531376653594-e9bcf0f0c65b?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMXx8Y29tcHJlc3Npb258ZW58MHx8fHwxNzY4NDk3Mzc1fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1531376653594-e9bcf0f0c65b?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMXx8Y29tcHJlc3Npb258ZW58MHx8fHwxNzY4NDk3Mzc1fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1531376653594-e9bcf0f0c65b?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMXx8Y29tcHJlc3Npb258ZW58MHx8fHwxNzY4NDk3Mzc1fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1531376653594-e9bcf0f0c65b?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMXx8Y29tcHJlc3Npb258ZW58MHx8fHwxNzY4NDk3Mzc1fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1531376653594-e9bcf0f0c65b?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMXx8Y29tcHJlc3Npb258ZW58MHx8fHwxNzY4NDk3Mzc1fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" width="3031" height="2006" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1531376653594-e9bcf0f0c65b?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMXx8Y29tcHJlc3Npb258ZW58MHx8fHwxNzY4NDk3Mzc1fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2006,&quot;width&quot;:3031,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;photo of red and blue zippers&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="photo of red and blue zippers" title="photo of red and blue zippers" srcset="https://images.unsplash.com/photo-1531376653594-e9bcf0f0c65b?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMXx8Y29tcHJlc3Npb258ZW58MHx8fHwxNzY4NDk3Mzc1fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1531376653594-e9bcf0f0c65b?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMXx8Y29tcHJlc3Npb258ZW58MHx8fHwxNzY4NDk3Mzc1fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1531376653594-e9bcf0f0c65b?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMXx8Y29tcHJlc3Npb258ZW58MHx8fHwxNzY4NDk3Mzc1fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1531376653594-e9bcf0f0c65b?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMXx8Y29tcHJlc3Npb258ZW58MHx8fHwxNzY4NDk3Mzc1fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@tomas_nz">Tomas Sobek</a> on <a href="https://unsplash.com">Unsplash</a></figcaption></figure></div><div class="pullquote"><p>It&#8217;s been a while, but after an extended break, Modern Web Weekly is now back! &#127881;</p></div><h2>Can I compress it? Yes, you can!</h2><p>If your web app needs to compress and decompress data, you no longer need an external library. The Compression Streams API is now available in all browsers and enables your app to (de)compress data using GZIP a&#8230;</p>
      <p>
          <a href="https://modernwebweekly.substack.com/p/modern-web-weekly-66">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Modern Web Weekly #65]]></title><description><![CDATA[The modern web, tested and explained in plain English]]></description><link>https://modernwebweekly.substack.com/p/modern-web-weekly-65</link><guid isPermaLink="false">https://modernwebweekly.substack.com/p/modern-web-weekly-65</guid><dc:creator><![CDATA[Danny Moerkerke]]></dc:creator><pubDate>Fri, 21 Nov 2025 12:56:02 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!qUUR!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a89c2c4-fb4b-4915-a478-bd39e93d12bb_1536x1024.heic" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!qUUR!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a89c2c4-fb4b-4915-a478-bd39e93d12bb_1536x1024.heic" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!qUUR!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a89c2c4-fb4b-4915-a478-bd39e93d12bb_1536x1024.heic 424w, https://substackcdn.com/image/fetch/$s_!qUUR!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a89c2c4-fb4b-4915-a478-bd39e93d12bb_1536x1024.heic 848w, https://substackcdn.com/image/fetch/$s_!qUUR!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a89c2c4-fb4b-4915-a478-bd39e93d12bb_1536x1024.heic 1272w, https://substackcdn.com/image/fetch/$s_!qUUR!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a89c2c4-fb4b-4915-a478-bd39e93d12bb_1536x1024.heic 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!qUUR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a89c2c4-fb4b-4915-a478-bd39e93d12bb_1536x1024.heic" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2a89c2c4-fb4b-4915-a478-bd39e93d12bb_1536x1024.heic&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:246556,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/heic&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://modernwebweekly.substack.com/i/178875756?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a89c2c4-fb4b-4915-a478-bd39e93d12bb_1536x1024.heic&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!qUUR!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a89c2c4-fb4b-4915-a478-bd39e93d12bb_1536x1024.heic 424w, https://substackcdn.com/image/fetch/$s_!qUUR!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a89c2c4-fb4b-4915-a478-bd39e93d12bb_1536x1024.heic 848w, https://substackcdn.com/image/fetch/$s_!qUUR!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a89c2c4-fb4b-4915-a478-bd39e93d12bb_1536x1024.heic 1272w, https://substackcdn.com/image/fetch/$s_!qUUR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a89c2c4-fb4b-4915-a478-bd39e93d12bb_1536x1024.heic 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Web Install API now in Chrome 143</h2><p>Chrome 143 now supports the Web Install API as an origin trial.</p><p>This API provides a declarative way of installing web apps through <code>navigator.install()</code>.</p><p>When it&#8217;s called without arguments, it installs the current web app (the one on the domain where this method is called from:</p><pre><code>// installs the current web app
navigator.instal&#8230;</code></pre>
      <p>
          <a href="https://modernwebweekly.substack.com/p/modern-web-weekly-65">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Modern Web Weekly #64]]></title><description><![CDATA[The modern web, tested and explained in plain English]]></description><link>https://modernwebweekly.substack.com/p/modern-web-weekly-64</link><guid isPermaLink="false">https://modernwebweekly.substack.com/p/modern-web-weekly-64</guid><dc:creator><![CDATA[Danny Moerkerke]]></dc:creator><pubDate>Fri, 14 Nov 2025 11:26:11 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!JZLb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7cc23cd-e0b9-4b47-a1c7-14732e2f9ef8_1024x608.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2></h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!JZLb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7cc23cd-e0b9-4b47-a1c7-14732e2f9ef8_1024x608.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!JZLb!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7cc23cd-e0b9-4b47-a1c7-14732e2f9ef8_1024x608.png 424w, https://substackcdn.com/image/fetch/$s_!JZLb!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7cc23cd-e0b9-4b47-a1c7-14732e2f9ef8_1024x608.png 848w, https://substackcdn.com/image/fetch/$s_!JZLb!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7cc23cd-e0b9-4b47-a1c7-14732e2f9ef8_1024x608.png 1272w, https://substackcdn.com/image/fetch/$s_!JZLb!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7cc23cd-e0b9-4b47-a1c7-14732e2f9ef8_1024x608.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!JZLb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7cc23cd-e0b9-4b47-a1c7-14732e2f9ef8_1024x608.png" width="1024" height="608" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e7cc23cd-e0b9-4b47-a1c7-14732e2f9ef8_1024x608.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:&quot;normal&quot;,&quot;height&quot;:608,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!JZLb!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7cc23cd-e0b9-4b47-a1c7-14732e2f9ef8_1024x608.png 424w, https://substackcdn.com/image/fetch/$s_!JZLb!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7cc23cd-e0b9-4b47-a1c7-14732e2f9ef8_1024x608.png 848w, https://substackcdn.com/image/fetch/$s_!JZLb!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7cc23cd-e0b9-4b47-a1c7-14732e2f9ef8_1024x608.png 1272w, https://substackcdn.com/image/fetch/$s_!JZLb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7cc23cd-e0b9-4b47-a1c7-14732e2f9ef8_1024x608.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Better drag-and-drop with Observer</h2><p>If you&#8217;ve ever built drag-and-drop functionality, you know that this can be tricky to implement and can result in brittle code that&#8217;s hard to follow.</p><p>You need to implement a handler for the <code>mousemove</code> event, but this handler should only be called when it&#8217;s preceded by a <code>mousedown</code> event on the element that needs to be drag&#8230;</p>
      <p>
          <a href="https://modernwebweekly.substack.com/p/modern-web-weekly-64">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Modern Web Weekly #63]]></title><description><![CDATA[The modern web, tested and explained in plain English]]></description><link>https://modernwebweekly.substack.com/p/modern-web-weekly-63</link><guid isPermaLink="false">https://modernwebweekly.substack.com/p/modern-web-weekly-63</guid><dc:creator><![CDATA[Danny Moerkerke]]></dc:creator><pubDate>Wed, 05 Nov 2025 13:58:40 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!HbFY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa441e48f-aa06-47a0-a6c4-fa274fbd82b1_1020x579.heic" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!HbFY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa441e48f-aa06-47a0-a6c4-fa274fbd82b1_1020x579.heic" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!HbFY!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa441e48f-aa06-47a0-a6c4-fa274fbd82b1_1020x579.heic 424w, https://substackcdn.com/image/fetch/$s_!HbFY!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa441e48f-aa06-47a0-a6c4-fa274fbd82b1_1020x579.heic 848w, https://substackcdn.com/image/fetch/$s_!HbFY!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa441e48f-aa06-47a0-a6c4-fa274fbd82b1_1020x579.heic 1272w, https://substackcdn.com/image/fetch/$s_!HbFY!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa441e48f-aa06-47a0-a6c4-fa274fbd82b1_1020x579.heic 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!HbFY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa441e48f-aa06-47a0-a6c4-fa274fbd82b1_1020x579.heic" width="1020" height="579" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a441e48f-aa06-47a0-a6c4-fa274fbd82b1_1020x579.heic&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:579,&quot;width&quot;:1020,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:40008,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/heic&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://modernwebweekly.substack.com/i/177501059?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa441e48f-aa06-47a0-a6c4-fa274fbd82b1_1020x579.heic&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!HbFY!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa441e48f-aa06-47a0-a6c4-fa274fbd82b1_1020x579.heic 424w, https://substackcdn.com/image/fetch/$s_!HbFY!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa441e48f-aa06-47a0-a6c4-fa274fbd82b1_1020x579.heic 848w, https://substackcdn.com/image/fetch/$s_!HbFY!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa441e48f-aa06-47a0-a6c4-fa274fbd82b1_1020x579.heic 1272w, https://substackcdn.com/image/fetch/$s_!HbFY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa441e48f-aa06-47a0-a6c4-fa274fbd82b1_1020x579.heic 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>CMA designates Apple as having Strategic Market Status</h2><p>The Open Web Advocacy (OWA) reports that the UK&#8217;s Competition and Markets Authority (CMA) has officially designated Apple as having Strategic Market Status (SMS).</p><p>SMS means that Apple &#8220;has substantial and entrenched market power and a position of strategic significance&#8221;. It means that Apple&#8217;s iOS plat&#8230;</p>
      <p>
          <a href="https://modernwebweekly.substack.com/p/modern-web-weekly-63">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Modern Web Weekly #62]]></title><description><![CDATA[The modern web, tested and explained in plain English]]></description><link>https://modernwebweekly.substack.com/p/modern-web-weekly-62</link><guid isPermaLink="false">https://modernwebweekly.substack.com/p/modern-web-weekly-62</guid><dc:creator><![CDATA[Danny Moerkerke]]></dc:creator><pubDate>Fri, 24 Oct 2025 08:40:45 GMT</pubDate><enclosure url="https://images.unsplash.com/photo-1585060282215-39a72f82385c?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw2MXx8YXBwfGVufDB8fHx8MTc2MTI5NDg2MXww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2></h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1585060282215-39a72f82385c?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw2MXx8YXBwfGVufDB8fHx8MTc2MTI5NDg2MXww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1585060282215-39a72f82385c?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw2MXx8YXBwfGVufDB8fHx8MTc2MTI5NDg2MXww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1585060282215-39a72f82385c?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw2MXx8YXBwfGVufDB8fHx8MTc2MTI5NDg2MXww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1585060282215-39a72f82385c?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw2MXx8YXBwfGVufDB8fHx8MTc2MTI5NDg2MXww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1585060282215-39a72f82385c?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw2MXx8YXBwfGVufDB8fHx8MTc2MTI5NDg2MXww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1585060282215-39a72f82385c?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw2MXx8YXBwfGVufDB8fHx8MTc2MTI5NDg2MXww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" width="4402" height="2880" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1585060282215-39a72f82385c?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw2MXx8YXBwfGVufDB8fHx8MTc2MTI5NDg2MXww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2880,&quot;width&quot;:4402,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;person holding black iphone 7&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="person holding black iphone 7" title="person holding black iphone 7" srcset="https://images.unsplash.com/photo-1585060282215-39a72f82385c?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw2MXx8YXBwfGVufDB8fHx8MTc2MTI5NDg2MXww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1585060282215-39a72f82385c?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw2MXx8YXBwfGVufDB8fHx8MTc2MTI5NDg2MXww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1585060282215-39a72f82385c?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw2MXx8YXBwfGVufDB8fHx8MTc2MTI5NDg2MXww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1585060282215-39a72f82385c?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw2MXx8YXBwfGVufDB8fHx8MTc2MTI5NDg2MXww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@vojtechbruzek">Vojtech Bruzek</a> on <a href="https://unsplash.com">Unsplash</a></figcaption></figure></div><h2><strong>What We Need To Make Web Apps A Success</strong></h2><p>We don&#8217;t need to wait for Apple to jump on board</p><p>Here&#8217;s a fun experiment: put your phone in flight mode so there&#8217;s no internet connection, and make sure it&#8217;s not connected to wifi either.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://modernwebweekly.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Modern Web Weekly is a reader-supported publication. To receive new posts and support my work, &#8230;</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>
      <p>
          <a href="https://modernwebweekly.substack.com/p/modern-web-weekly-62">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Modern Web Weekly #61]]></title><description><![CDATA[The modern web, tested and explained in plain English]]></description><link>https://modernwebweekly.substack.com/p/modern-web-weekly-61</link><guid isPermaLink="false">https://modernwebweekly.substack.com/p/modern-web-weekly-61</guid><dc:creator><![CDATA[Danny Moerkerke]]></dc:creator><pubDate>Thu, 16 Oct 2025 12:46:22 GMT</pubDate><enclosure url="https://images.unsplash.com/photo-1526045612212-70caf35c14df?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxtZXNzYWdlfGVufDB8fHx8MTc2MDYxNjM3N3ww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2></h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1526045612212-70caf35c14df?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxtZXNzYWdlfGVufDB8fHx8MTc2MDYxNjM3N3ww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1526045612212-70caf35c14df?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxtZXNzYWdlfGVufDB8fHx8MTc2MDYxNjM3N3ww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1526045612212-70caf35c14df?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxtZXNzYWdlfGVufDB8fHx8MTc2MDYxNjM3N3ww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1526045612212-70caf35c14df?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxtZXNzYWdlfGVufDB8fHx8MTc2MDYxNjM3N3ww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1526045612212-70caf35c14df?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxtZXNzYWdlfGVufDB8fHx8MTc2MDYxNjM3N3ww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1526045612212-70caf35c14df?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxtZXNzYWdlfGVufDB8fHx8MTc2MDYxNjM3N3ww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" width="5472" height="3648" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1526045612212-70caf35c14df?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxtZXNzYWdlfGVufDB8fHx8MTc2MDYxNjM3N3ww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:3648,&quot;width&quot;:5472,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;selective focus photography of person using smartphone&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="selective focus photography of person using smartphone" title="selective focus photography of person using smartphone" srcset="https://images.unsplash.com/photo-1526045612212-70caf35c14df?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxtZXNzYWdlfGVufDB8fHx8MTc2MDYxNjM3N3ww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1526045612212-70caf35c14df?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxtZXNzYWdlfGVufDB8fHx8MTc2MDYxNjM3N3ww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1526045612212-70caf35c14df?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxtZXNzYWdlfGVufDB8fHx8MTc2MDYxNjM3N3ww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1526045612212-70caf35c14df?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxtZXNzYWdlfGVufDB8fHx8MTc2MDYxNjM3N3ww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@freestocks">freestocks</a> on <a href="https://unsplash.com">Unsplash</a></figcaption></figure></div><h2>Taming the on-screen keyboard with <code>interactive-widget</code></h2><p>In Chromium-based browsers, you can now control how the layout of the page behaves when the on-screen keyboard is shown on mobile devices.</p><p>Different browsers behave differently when the on-screen keyboard is shown. They can resize the <em>Layout Viewport</em>, the <em>Visual Viewport</em>,&#8230;</p>
      <p>
          <a href="https://modernwebweekly.substack.com/p/modern-web-weekly-61">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Modern Web Weekly #60]]></title><description><![CDATA[The modern web, tested and explained in plain English]]></description><link>https://modernwebweekly.substack.com/p/modern-web-weekly-60</link><guid isPermaLink="false">https://modernwebweekly.substack.com/p/modern-web-weekly-60</guid><dc:creator><![CDATA[Danny Moerkerke]]></dc:creator><pubDate>Fri, 03 Oct 2025 13:05:59 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!vX-I!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7495230-c5fa-40c4-874c-e417b970ccb5_500x500.heic" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!vX-I!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7495230-c5fa-40c4-874c-e417b970ccb5_500x500.heic" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!vX-I!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7495230-c5fa-40c4-874c-e417b970ccb5_500x500.heic 424w, https://substackcdn.com/image/fetch/$s_!vX-I!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7495230-c5fa-40c4-874c-e417b970ccb5_500x500.heic 848w, https://substackcdn.com/image/fetch/$s_!vX-I!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7495230-c5fa-40c4-874c-e417b970ccb5_500x500.heic 1272w, https://substackcdn.com/image/fetch/$s_!vX-I!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7495230-c5fa-40c4-874c-e417b970ccb5_500x500.heic 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!vX-I!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7495230-c5fa-40c4-874c-e417b970ccb5_500x500.heic" width="500" height="500" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f7495230-c5fa-40c4-874c-e417b970ccb5_500x500.heic&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:500,&quot;width&quot;:500,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:38495,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/heic&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://modernwebweekly.substack.com/i/173932347?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7495230-c5fa-40c4-874c-e417b970ccb5_500x500.heic&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!vX-I!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7495230-c5fa-40c4-874c-e417b970ccb5_500x500.heic 424w, https://substackcdn.com/image/fetch/$s_!vX-I!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7495230-c5fa-40c4-874c-e417b970ccb5_500x500.heic 848w, https://substackcdn.com/image/fetch/$s_!vX-I!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7495230-c5fa-40c4-874c-e417b970ccb5_500x500.heic 1272w, https://substackcdn.com/image/fetch/$s_!vX-I!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7495230-c5fa-40c4-874c-e417b970ccb5_500x500.heic 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>iOS 26 for PWAs: The Good, the Bad, and the Ugly</h2><p>iOS 26 has arrived, and of course, I combed through the release notes and did a lot of testing to see what has changed for PWA support.</p><p>In short, there is unfortunately not too much to be excited about, but let&#8217;s start with the good news.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://modernwebweekly.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Modern Web Weekly is a reader-supported publication. To receive new po&#8230;</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>
      <p>
          <a href="https://modernwebweekly.substack.com/p/modern-web-weekly-60">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Modern Web Weekly #59]]></title><description><![CDATA[The modern web, tested and explain in plain English]]></description><link>https://modernwebweekly.substack.com/p/modern-web-weekly-59</link><guid isPermaLink="false">https://modernwebweekly.substack.com/p/modern-web-weekly-59</guid><dc:creator><![CDATA[Danny Moerkerke]]></dc:creator><pubDate>Mon, 21 Jul 2025 14:19:55 GMT</pubDate><enclosure url="https://images.unsplash.com/photo-1565263965454-a44e2ede252a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw5OXx8aW5zdGFsbCUyMGFwcHxlbnwwfHx8fDE3NTI3NDk1MjN8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2></h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1565263965454-a44e2ede252a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw5OXx8aW5zdGFsbCUyMGFwcHxlbnwwfHx8fDE3NTI3NDk1MjN8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1565263965454-a44e2ede252a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw5OXx8aW5zdGFsbCUyMGFwcHxlbnwwfHx8fDE3NTI3NDk1MjN8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1565263965454-a44e2ede252a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw5OXx8aW5zdGFsbCUyMGFwcHxlbnwwfHx8fDE3NTI3NDk1MjN8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1565263965454-a44e2ede252a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw5OXx8aW5zdGFsbCUyMGFwcHxlbnwwfHx8fDE3NTI3NDk1MjN8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1565263965454-a44e2ede252a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw5OXx8aW5zdGFsbCUyMGFwcHxlbnwwfHx8fDE3NTI3NDk1MjN8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1565263965454-a44e2ede252a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw5OXx8aW5zdGFsbCUyMGFwcHxlbnwwfHx8fDE3NTI3NDk1MjN8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" width="4608" height="3072" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1565263965454-a44e2ede252a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw5OXx8aW5zdGFsbCUyMGFwcHxlbnwwfHx8fDE3NTI3NDk1MjN8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:3072,&quot;width&quot;:4608,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;space gray iPhone 6 turned on on marble surface&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="space gray iPhone 6 turned on on marble surface" title="space gray iPhone 6 turned on on marble surface" srcset="https://images.unsplash.com/photo-1565263965454-a44e2ede252a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw5OXx8aW5zdGFsbCUyMGFwcHxlbnwwfHx8fDE3NTI3NDk1MjN8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1565263965454-a44e2ede252a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw5OXx8aW5zdGFsbCUyMGFwcHxlbnwwfHx8fDE3NTI3NDk1MjN8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1565263965454-a44e2ede252a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw5OXx8aW5zdGFsbCUyMGFwcHxlbnwwfHx8fDE3NTI3NDk1MjN8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1565263965454-a44e2ede252a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw5OXx8aW5zdGFsbCUyMGFwcHxlbnwwfHx8fDE3NTI3NDk1MjN8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Photo by <a href="true">Maccy</a> on <a href="https://unsplash.com">Unsplash</a></figcaption></figure></div><h2>Install your PWA from anywhere</h2><p>The dev trial for Microsoft&#8217;s proposal for the <a href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/WebInstall/explainer.md">Web Install UI</a> is now live.</p><p>This API adds the <code>install</code> method to <code>navigator</code> to install a web app on the user&#8217;s device. When <code>navigator.install()</code> is called without any arguments, the browser will prompt to install the current web app, meaning the web app ho&#8230;</p>
      <p>
          <a href="https://modernwebweekly.substack.com/p/modern-web-weekly-59">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Modern Web Weekly #58]]></title><description><![CDATA[The modern web, tested and explained in plain English]]></description><link>https://modernwebweekly.substack.com/p/modern-web-weekly-58</link><guid isPermaLink="false">https://modernwebweekly.substack.com/p/modern-web-weekly-58</guid><dc:creator><![CDATA[Danny Moerkerke]]></dc:creator><pubDate>Thu, 10 Jul 2025 14:09:21 GMT</pubDate><enclosure url="https://images.unsplash.com/photo-1644337541310-d451eb4c9316?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyOTB8fG1hY2Jvb2t8ZW58MHx8fHwxNzUyMDUzODc5fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1644337541310-d451eb4c9316?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyOTB8fG1hY2Jvb2t8ZW58MHx8fHwxNzUyMDUzODc5fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1644337541310-d451eb4c9316?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyOTB8fG1hY2Jvb2t8ZW58MHx8fHwxNzUyMDUzODc5fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1644337541310-d451eb4c9316?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyOTB8fG1hY2Jvb2t8ZW58MHx8fHwxNzUyMDUzODc5fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1644337541310-d451eb4c9316?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyOTB8fG1hY2Jvb2t8ZW58MHx8fHwxNzUyMDUzODc5fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1644337541310-d451eb4c9316?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyOTB8fG1hY2Jvb2t8ZW58MHx8fHwxNzUyMDUzODc5fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1644337541310-d451eb4c9316?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyOTB8fG1hY2Jvb2t8ZW58MHx8fHwxNzUyMDUzODc5fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" width="6240" height="4160" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1644337541310-d451eb4c9316?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyOTB8fG1hY2Jvb2t8ZW58MHx8fHwxNzUyMDUzODc5fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:4160,&quot;width&quot;:6240,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;a close up of a laptop with a keyboard&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="a close up of a laptop with a keyboard" title="a close up of a laptop with a keyboard" srcset="https://images.unsplash.com/photo-1644337541310-d451eb4c9316?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyOTB8fG1hY2Jvb2t8ZW58MHx8fHwxNzUyMDUzODc5fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1644337541310-d451eb4c9316?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyOTB8fG1hY2Jvb2t8ZW58MHx8fHwxNzUyMDUzODc5fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1644337541310-d451eb4c9316?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyOTB8fG1hY2Jvb2t8ZW58MHx8fHwxNzUyMDUzODc5fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1644337541310-d451eb4c9316?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyOTB8fG1hY2Jvb2t8ZW58MHx8fHwxNzUyMDUzODc5fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Photo by <a href="true">Elijah Pilchard</a> on <a href="https://unsplash.com">Unsplash</a></figcaption></figure></div><h2>Web apps added to the Dock on macOS, use <code>scope</code></h2><p>On macOS, you can now add web apps to the Dock and these apps will behave like PWAs. If you have a manifest, it will take the icons and other configurations from there, but you can also &#8220;install&#8221; web apps that don&#8217;t have a manifest and/or service worker.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://modernwebweekly.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Modern Web Weekly i&#8230;</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>
      <p>
          <a href="https://modernwebweekly.substack.com/p/modern-web-weekly-58">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Modern Web Weekly #57]]></title><description><![CDATA[The modern web, tested and explained in plain English]]></description><link>https://modernwebweekly.substack.com/p/modern-web-weekly-57</link><guid isPermaLink="false">https://modernwebweekly.substack.com/p/modern-web-weekly-57</guid><dc:creator><![CDATA[Danny Moerkerke]]></dc:creator><pubDate>Thu, 26 Jun 2025 18:20:40 GMT</pubDate><enclosure url="https://images.unsplash.com/photo-1599009432031-ba5d3a794aec?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMnx8aXBhZHxlbnwwfHx8fDE3NTA3NzU3MjF8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1599009432031-ba5d3a794aec?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMnx8aXBhZHxlbnwwfHx8fDE3NTA3NzU3MjF8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1599009432031-ba5d3a794aec?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMnx8aXBhZHxlbnwwfHx8fDE3NTA3NzU3MjF8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1599009432031-ba5d3a794aec?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMnx8aXBhZHxlbnwwfHx8fDE3NTA3NzU3MjF8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1599009432031-ba5d3a794aec?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMnx8aXBhZHxlbnwwfHx8fDE3NTA3NzU3MjF8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1599009432031-ba5d3a794aec?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMnx8aXBhZHxlbnwwfHx8fDE3NTA3NzU3MjF8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1599009432031-ba5d3a794aec?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMnx8aXBhZHxlbnwwfHx8fDE3NTA3NzU3MjF8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" width="5458" height="3639" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1599009432031-ba5d3a794aec?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMnx8aXBhZHxlbnwwfHx8fDE3NTA3NzU3MjF8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:3639,&quot;width&quot;:5458,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;person holding white printer paper&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="person holding white printer paper" title="person holding white printer paper" srcset="https://images.unsplash.com/photo-1599009432031-ba5d3a794aec?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMnx8aXBhZHxlbnwwfHx8fDE3NTA3NzU3MjF8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1599009432031-ba5d3a794aec?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMnx8aXBhZHxlbnwwfHx8fDE3NTA3NzU3MjF8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1599009432031-ba5d3a794aec?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMnx8aXBhZHxlbnwwfHx8fDE3NTA3NzU3MjF8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1599009432031-ba5d3a794aec?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMnx8aXBhZHxlbnwwfHx8fDE3NTA3NzU3MjF8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Photo by <a href="true">Kelly Sikkema</a> on <a href="https://unsplash.com">Unsplash</a></figcaption></figure></div><h2>The horror of PWA splash screens on iPadOS</h2><p>When you build a PWA for iOS or iPadOS, you will need to provide so-called splash screen images. This is the image that is displayed while the app is starting up.</p><p>Usually, this will be the logo of the app, centered on the screen, but you can really use any kind of image you want.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://modernwebweekly.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">&#8230;</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>
      <p>
          <a href="https://modernwebweekly.substack.com/p/modern-web-weekly-57">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Modern Web Weekly #56]]></title><description><![CDATA[The modern web, tested and explained in plain English]]></description><link>https://modernwebweekly.substack.com/p/modern-web-weekly-56</link><guid isPermaLink="false">https://modernwebweekly.substack.com/p/modern-web-weekly-56</guid><dc:creator><![CDATA[Danny Moerkerke]]></dc:creator><pubDate>Thu, 19 Jun 2025 14:03:20 GMT</pubDate><enclosure url="https://images.unsplash.com/photo-1541591708423-9001fe827349?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyMnx8dXNlciUyMGludGVyZmFjZXxlbnwwfHx8fDE3NTAzNDE3MzF8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1541591708423-9001fe827349?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyMnx8dXNlciUyMGludGVyZmFjZXxlbnwwfHx8fDE3NTAzNDE3MzF8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1541591708423-9001fe827349?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyMnx8dXNlciUyMGludGVyZmFjZXxlbnwwfHx8fDE3NTAzNDE3MzF8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1541591708423-9001fe827349?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyMnx8dXNlciUyMGludGVyZmFjZXxlbnwwfHx8fDE3NTAzNDE3MzF8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1541591708423-9001fe827349?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyMnx8dXNlciUyMGludGVyZmFjZXxlbnwwfHx8fDE3NTAzNDE3MzF8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1541591708423-9001fe827349?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyMnx8dXNlciUyMGludGVyZmFjZXxlbnwwfHx8fDE3NTAzNDE3MzF8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1541591708423-9001fe827349?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyMnx8dXNlciUyMGludGVyZmFjZXxlbnwwfHx8fDE3NTAzNDE3MzF8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" width="3944" height="4930" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1541591708423-9001fe827349?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyMnx8dXNlciUyMGludGVyZmFjZXxlbnwwfHx8fDE3NTAzNDE3MzF8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:4930,&quot;width&quot;:3944,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;black smartphone&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="black smartphone" title="black smartphone" srcset="https://images.unsplash.com/photo-1541591708423-9001fe827349?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyMnx8dXNlciUyMGludGVyZmFjZXxlbnwwfHx8fDE3NTAzNDE3MzF8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1541591708423-9001fe827349?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyMnx8dXNlciUyMGludGVyZmFjZXxlbnwwfHx8fDE3NTAzNDE3MzF8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1541591708423-9001fe827349?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyMnx8dXNlciUyMGludGVyZmFjZXxlbnwwfHx8fDE3NTAzNDE3MzF8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1541591708423-9001fe827349?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyMnx8dXNlciUyMGludGVyZmFjZXxlbnwwfHx8fDE3NTAzNDE3MzF8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Photo by <a href="true">Daniel Korpai</a> on <a href="https://unsplash.com">Unsplash</a></figcaption></figure></div><h2>Modern Web Weekly chat is now available &#127881;</h2><p>If you&#8217;re a paid subscriber, you now have access to the Modern Web Weekly chat.</p><p>This is a conversation space exclusively for paid subscribers&#8212;kind of like a group chat or live hangout. I&#8217;ll post questions and updates that come my way, and you can jump into the discussion.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://modernwebweekly.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Modern W&#8230;</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>
      <p>
          <a href="https://modernwebweekly.substack.com/p/modern-web-weekly-56">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Join the Modern Web Weekly subscriber chat]]></title><description><![CDATA[A private space for us to converse and connect]]></description><link>https://modernwebweekly.substack.com/p/join-the-modern-web-weekly-subscriber</link><guid isPermaLink="false">https://modernwebweekly.substack.com/p/join-the-modern-web-weekly-subscriber</guid><dc:creator><![CDATA[Danny Moerkerke]]></dc:creator><pubDate>Mon, 16 Jun 2025 10:17:50 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!KYZT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0f63c9a-2296-4c96-a2f9-52648999bb00_2000x1000.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Today, I&#8217;m announcing a brand new addition to my Substack publication: Modern Web Weekly subscriber chat.</p><p>This is a conversation space exclusively for paid subscribers&#8212;kind of like a group chat or live hangout. I&#8217;ll post questions and updates that come my way, and you can jump into the discussion.</p><p>I will do my best to answer all your questions, and occasi&#8230;</p>
      <p>
          <a href="https://modernwebweekly.substack.com/p/join-the-modern-web-weekly-subscriber">
              Read more
          </a>
      </p>
   ]]></content:encoded></item></channel></rss>