<?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>Tue, 19 May 2026 06:42:03 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[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-select;
}</code></pre></div><p>This will add the new <code>::picker(select)</code> pseudo-element that represents the popover containing the <code>&lt;option&gt;</code> elements.</p><p>With default styling, the <code>&lt;select&gt;</code> with <code>appearance: base-select</code> doesn&#8217;t look very different from the regular version, but things get interesting when we customize it further.</p><p>We can add a <code>&lt;button&gt;</code> that will open the select and inside it, we can place a <code>&lt;selectedcontent&gt;</code> element, which will contain the currently selected <code>&lt;option&gt;</code>. Inside this <code>&lt;button&gt;</code> we can place a custom indicator to indicate the open and closed state of the <code>&lt;select&gt;</code>. The default arrow indicator can be hidden with:</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">::picker-icon {
  display: none;
}</code></pre></div><p>The <code>&lt;select&gt;</code> could now look something like this:</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">&lt;select&gt;
  
  &lt;!-- button to open and close the select --&gt;
  &lt;button&gt;
    
    &lt;!-- element where the selected option will be placed --&gt;
    &lt;selectedcontent&gt;&lt;/selectedcontent&gt;

    &lt;!-- custom arrow indicator --&gt;
    &lt;span class="arrow"&gt;&lt;/span&gt;
  &lt;/button&gt;

  &lt;!-- options --&gt;
  &lt;option value="1"&gt;
    Option 1
  &lt;/option&gt;
  &lt;option value="2"&gt;
    Option 2
  &lt;/option&gt;
  &lt;option value="3"&gt;
    Option 3
  &lt;/option&gt;
  &lt;option value="4"&gt;
    Option 4
  &lt;/option&gt;
&lt;/select&gt;</code></pre></div><p>Keep in mind that only the contents of the selected <code>&lt;option&gt;</code> will be placed inside <code>&lt;selectedcontent&gt;</code> and not the <code>&lt;option&gt;</code> element itself. This is important to know when you want to style the contents of <code>&lt;selectedcontent&gt;</code>.</p><p>In the screenshot below, the second <code>&lt;option&gt;</code> is selected:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!pwbm!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff39991bb-866e-4ac7-b21c-28187178f394_826x406.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!pwbm!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff39991bb-866e-4ac7-b21c-28187178f394_826x406.png 424w, https://substackcdn.com/image/fetch/$s_!pwbm!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff39991bb-866e-4ac7-b21c-28187178f394_826x406.png 848w, https://substackcdn.com/image/fetch/$s_!pwbm!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff39991bb-866e-4ac7-b21c-28187178f394_826x406.png 1272w, https://substackcdn.com/image/fetch/$s_!pwbm!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff39991bb-866e-4ac7-b21c-28187178f394_826x406.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!pwbm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff39991bb-866e-4ac7-b21c-28187178f394_826x406.png" width="826" height="406" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f39991bb-866e-4ac7-b21c-28187178f394_826x406.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:406,&quot;width&quot;:826,&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;: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="" title="" srcset="https://substackcdn.com/image/fetch/$s_!pwbm!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff39991bb-866e-4ac7-b21c-28187178f394_826x406.png 424w, https://substackcdn.com/image/fetch/$s_!pwbm!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff39991bb-866e-4ac7-b21c-28187178f394_826x406.png 848w, https://substackcdn.com/image/fetch/$s_!pwbm!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff39991bb-866e-4ac7-b21c-28187178f394_826x406.png 1272w, https://substackcdn.com/image/fetch/$s_!pwbm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff39991bb-866e-4ac7-b21c-28187178f394_826x406.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"><code>&lt;select&gt;</code> with contents of selected <code>&lt;option&gt;</code> in <code>&lt;selectedcontent&gt;</code></figcaption></figure></div><p>By default, a checkmark is shown in front of the selected <code>&lt;option&gt;</code>:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!IAES!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d047266-7dbe-4cb8-9bc8-f1ef8cf21a4a_404x478.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!IAES!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d047266-7dbe-4cb8-9bc8-f1ef8cf21a4a_404x478.png 424w, https://substackcdn.com/image/fetch/$s_!IAES!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d047266-7dbe-4cb8-9bc8-f1ef8cf21a4a_404x478.png 848w, https://substackcdn.com/image/fetch/$s_!IAES!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d047266-7dbe-4cb8-9bc8-f1ef8cf21a4a_404x478.png 1272w, https://substackcdn.com/image/fetch/$s_!IAES!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d047266-7dbe-4cb8-9bc8-f1ef8cf21a4a_404x478.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!IAES!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d047266-7dbe-4cb8-9bc8-f1ef8cf21a4a_404x478.png" width="404" height="478" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9d047266-7dbe-4cb8-9bc8-f1ef8cf21a4a_404x478.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:478,&quot;width&quot;:404,&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;: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="" title="" srcset="https://substackcdn.com/image/fetch/$s_!IAES!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d047266-7dbe-4cb8-9bc8-f1ef8cf21a4a_404x478.png 424w, https://substackcdn.com/image/fetch/$s_!IAES!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d047266-7dbe-4cb8-9bc8-f1ef8cf21a4a_404x478.png 848w, https://substackcdn.com/image/fetch/$s_!IAES!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d047266-7dbe-4cb8-9bc8-f1ef8cf21a4a_404x478.png 1272w, https://substackcdn.com/image/fetch/$s_!IAES!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d047266-7dbe-4cb8-9bc8-f1ef8cf21a4a_404x478.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">Checkmark in front of the selected <code>&lt;option&gt;</code></figcaption></figure></div><p>If needed, you can hide this checkmark with:</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">option::checkmark {
  display: none;
}</code></pre></div><p><strong>Adding HTML elements to </strong><code>&lt;option&gt;</code><br>In addition to text, you can now also add other HTML elements to <code>&lt;option&gt;</code>. Here&#8217;s an example of a currency picker using flag images that I adapted from an example by Una Kravetz:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!5n69!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f77230f-3830-44e9-a9e4-7613aae94a95_250x337.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!5n69!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f77230f-3830-44e9-a9e4-7613aae94a95_250x337.png 424w, https://substackcdn.com/image/fetch/$s_!5n69!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f77230f-3830-44e9-a9e4-7613aae94a95_250x337.png 848w, https://substackcdn.com/image/fetch/$s_!5n69!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f77230f-3830-44e9-a9e4-7613aae94a95_250x337.png 1272w, https://substackcdn.com/image/fetch/$s_!5n69!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f77230f-3830-44e9-a9e4-7613aae94a95_250x337.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!5n69!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f77230f-3830-44e9-a9e4-7613aae94a95_250x337.png" width="250" height="337" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7f77230f-3830-44e9-a9e4-7613aae94a95_250x337.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:337,&quot;width&quot;:250,&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;: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="" title="" srcset="https://substackcdn.com/image/fetch/$s_!5n69!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f77230f-3830-44e9-a9e4-7613aae94a95_250x337.png 424w, https://substackcdn.com/image/fetch/$s_!5n69!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f77230f-3830-44e9-a9e4-7613aae94a95_250x337.png 848w, https://substackcdn.com/image/fetch/$s_!5n69!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f77230f-3830-44e9-a9e4-7613aae94a95_250x337.png 1272w, https://substackcdn.com/image/fetch/$s_!5n69!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f77230f-3830-44e9-a9e4-7613aae94a95_250x337.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"><code>&lt;select&gt;</code> as a currency picker with flag images</figcaption></figure></div><p>You can now simply accomplish this by placing the needed HTML elements inside <code>&lt;option&gt;</code> and style them with CSS:</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;option value="eur"&gt;
  &lt;img src="europe.png"&gt;
  &lt;div class="currency"&gt;
    &lt;div class="currency-short"&gt;EUR&lt;/div&gt;
    &lt;div class="currency-long"&gt;Euro&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="symbol"&gt;&#8364;&lt;/div&gt;
&lt;/option&gt;</code></pre></div><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">option {
  .currency {
    flex-grow: 1;
  }
  
  .currency-long {
    color: #595959;
  }
  
  .symbol {
    justify-self: end;
  }
}</code></pre></div><p><strong>Standing on the shoulders of giants</strong><br>Internally, the customizable <code>&lt;select&gt;</code> uses Anchor Positioning to anchor the <code>::picker(select)</code> pseudo-element to it and the Popover API to position it in the top layer, above all other elements on the page.</p><p>This is important to know if you want to animate the opening and closing of the picker, which is now possible as well!</p><p>Let&#8217;s implement a nice sliding transition where the picker&#8217;s height is animated and the <code>&lt;option&gt;</code> elements inside it slide down. To do this, we can place a <code>&lt;div&gt;</code> around the <code>&lt;option&gt;</code> elements and then give this <code>&lt;div&gt;</code> the following 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">display: flex;
flex-direction: column;
justify-content: flex-end;</code></pre></div><p>which aligns the <code>&lt;option&gt;</code> elements to the bottom so they&#8217;ll slide down when the picker is opened by expanding its height.</p><p>We can do this by simply wrapping the <code>&lt;option&gt;</code> elements inside a <code>&lt;div&gt;</code> because any content that is placed inside the <code>&lt;select&gt;</code> outside the <code>&lt;button&gt;</code> will be automatically placed inside <code>::picker(select)</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;div class="options"&gt;
  &lt;option value="1"&gt;
    Option 1
  &lt;/option&gt;
  &lt;option value="2"&gt;
    Option 2
  &lt;/option&gt;
&lt;/div&gt;</code></pre></div><p>Even though the <code>&lt;div&gt;</code> will be placed inside <code>::picker(select)</code>, it needs to be styled with <code>select .options</code> and not <code>select::picker(select) .options</code> so it will look like this:</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 .options {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
}</code></pre></div><p>We will then define the styles for <code>::picker(select)</code> by setting its <code>height</code> to <code>0</code> and defining the transitions:</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">::picker(select) {
  height: 0;
  min-height: 0;
  transition-property: height, overlay, display;
  transition-duration: .3s;
  transition-behavior: allow-discrete;
}</code></pre></div><p>In addition to <code>height</code>, we also need to set <code>overlay</code> and <code>display</code> as transition properties because the picker will not have <code>display</code> by default and it will be moved to the top layer when shown. For that reason, we need to transition these properties as well, otherwise the transition will be instant.</p><p>To indicate we want to allow transitioning of <code>overlay</code> and <code>display</code>, which are discrete properties, we set <code>transition-behavior</code> to <code>allow-discrete</code>. The property <code>min-height: 0</code> is needed for a smooth transition.</p><p>Since the picker doesn&#8217;t have <code>display</code> when it&#8217;s not shown, we need to define a <code>@starting-style</code> rule for it because only elements that have <code>display</code> can be animated. This rule defines the property and value we want to transition <em>from</em>, in our case <code>height: 0</code>:</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">@starting-style {
  ::picker(select) {
    height: 0;
    min-height: 0;
  }
}</code></pre></div><p>Finally, we need to define the property and value we want to transition to, in our case the <code>height</code> of the picker when the <code>&lt;select&gt;</code> is open:</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:open::picker(select) {
  height: auto;
}</code></pre></div><p>To enable transitioning to a keyword like <code>auto</code>, we need to set this CSS as well:</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">:root {
  interpolate-size: allow-keywords; 
}</code></pre></div><p>And with that, we now have a smooth sliding <code>&lt;select&gt;</code>! &#128170;</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!0nvr!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feda9d082-6484-454b-ab6f-64d917df40fc_506x654.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!0nvr!,w_424,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feda9d082-6484-454b-ab6f-64d917df40fc_506x654.gif 424w, https://substackcdn.com/image/fetch/$s_!0nvr!,w_848,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feda9d082-6484-454b-ab6f-64d917df40fc_506x654.gif 848w, https://substackcdn.com/image/fetch/$s_!0nvr!,w_1272,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feda9d082-6484-454b-ab6f-64d917df40fc_506x654.gif 1272w, https://substackcdn.com/image/fetch/$s_!0nvr!,w_1456,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feda9d082-6484-454b-ab6f-64d917df40fc_506x654.gif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!0nvr!,w_1456,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feda9d082-6484-454b-ab6f-64d917df40fc_506x654.gif" width="506" height="654" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/eda9d082-6484-454b-ab6f-64d917df40fc_506x654.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:654,&quot;width&quot;:506,&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;: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="" title="" srcset="https://substackcdn.com/image/fetch/$s_!0nvr!,w_424,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feda9d082-6484-454b-ab6f-64d917df40fc_506x654.gif 424w, https://substackcdn.com/image/fetch/$s_!0nvr!,w_848,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feda9d082-6484-454b-ab6f-64d917df40fc_506x654.gif 848w, https://substackcdn.com/image/fetch/$s_!0nvr!,w_1272,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feda9d082-6484-454b-ab6f-64d917df40fc_506x654.gif 1272w, https://substackcdn.com/image/fetch/$s_!0nvr!,w_1456,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feda9d082-6484-454b-ab6f-64d917df40fc_506x654.gif 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"><code>&lt;select&gt;</code> with animated open and close transitions</figcaption></figure></div><p>I created a <a href="https://codepen.io/dannymoerkerke/pen/zxOYeEp">demo on codepen.io</a>, but unfortunately, Codepen doesn&#8217;t work for some reason (bug) in Safari Tech Preview 238. If you want to view it there, you can copy the HTML and CSS to a local HTML page and run it from there for now &#129335;&#8205;&#9794;&#65039;</p><p>I also had to apply a small Safari-only fix to this demo with the sliding opening and closing. When the <code>&lt;select&gt;</code> is opened, the options are correctly displayed below the button, but when closing, the options slide up to the top of the button, covering it:</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;e41ee914-f568-4294-95b0-e695f1f4b1e8&quot;,&quot;duration&quot;:null}"></div><p>By adding <code>margin-top</code> to the picker, this is solved:</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"> @supports (font: -apple-system-body) {
    &amp;:not(:open)::picker(select) {
      margin-top: 38px;
    }
  }</code></pre></div><p>The <code>@supports</code> block makes sure this is only applied in Safari.</p><div><hr></div><h2>Accessible arrow navigation without JavaScript with <code>focusgroup</code></h2><p>In Chrome 146 with Experimental Web Features enabled (or an origin trial), you can now try the <code>focusgroup</code> attribute. This is a declarative way to add keyboard arrow-key navigation to widgets like toolbars, tablists, menus, listboxes, etc., without the need for JavaScript.</p><p>Before, to implement keyboard navigation with the arrow keys for these widgets, you would need to write a lot of boilerplate code. <code>focusgroup</code> aims to solve all this.</p><p>The attribute takes a behavior token and three optional modifier tokens: </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">focusgroup="[behavior] [modifier1] [modifier2] [modifier3]"</code></pre></div><p>The behavior token defines the widget pattern and can take the values:</p><ul><li><p><code>toolbar</code></p></li><li><p><code>tablist</code></p></li><li><p><code>radiogroup</code></p></li><li><p><code>listbox</code></p></li><li><p><code>menu</code></p></li><li><p><code>menubar</code></p></li><li><p><code>none</code></p></li></ul><p>The first modifier defines the navigation direction and can take the values <code>block</code> (vertical) or <code>inline</code> (horizontal). So a widget with the modifier <code>inline</code> can be navigated with the left and right arrow keys, and a widget with the modifier <code>block</code> can be navigated with the up and down arrow keys.</p><p>The second modifier defines the wrapping behavior and can take the values <code>wrap</code> (allow navigation to wrap around) or <code>nowrap</code> (prevent navigation from wrapping). When set to <code>wrap</code>, the navigation will wrap around to the next item in the group, so when the last item is reached, the focus will move to the first item in the group and vice versa. When set to <code>nowrap</code>, the navigation will stop at the last item, and the focus will remain on the last item.</p><p>The third modifier determines whether the last-focused item in a group regains focus on re-entry with Tab. Use the <code>nomemory</code> modifier to disable it. This is mainly used in combination with the <code>focusgroupstart</code> attribute (see below).</p><p>Any modifier can be omitted if you only want to specify certain ones. By default, toolbar, tablist, and menubar have the modifier <code>inline</code>, menu has the modifier <code>block</code>, and tablist, menu, and menubar have the modifier <code>wrap</code>.</p><h4>focusgroupstart and nomemory</h4><p>When a widget with <code>focusgroup</code> is entered with TAB, the first focusable element will get focus, or the element with the <code>focusgroupstart</code> attribute. By default, this only applies when the widget <em>is entered for the first time</em>. </p><p>When you focus another element in this group with the arrow keys, then TAB to another group, and then go back to this group with shift+TAB, the last focused element will get focus again, <strong>not</strong> the element with <code>focusgroupstart</code>.</p><p>You can disable this memory behavior with the nomemory modifier. With that modifier, the element with <code>focusgroupstart</code> will always get focus when the widget is entered with TAB or shift+TAB, even if another element was last focused when the widget was exited.</p><h4>Nested groups</h4><p>You can also nest elements with <code>focusgroup,</code> where each group has its own scope. A nested <code>focusgroup</code> automatically opts out of its ancestor&#8217;s arrow navigation, and you can use TAB and shift+TAB to move between the groups.</p><p>Here&#8217;s an example of a menubar with horizontal navigation through the <code>menubar</code> behavior, where each button can open a submenu, which has vertical navigation through the <code>menu</code> behavior.:</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;!-- menubar with horizontal navigation --&gt;
&lt;ul focusgroup="menubar"&gt;
  &lt;li&gt;
    &lt;button type="button" popovertarget="aboutpop"&gt;About&lt;/button&gt;
    
    &lt;!-- submenu with vertical navigation --&gt;
    &lt;ul focusgroup="menu" autofocus id="aboutpop" popover&gt;
      &lt;li&gt;&lt;a tabindex="0"&gt;Overview&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a tabindex="0"&gt;Administration&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;

  &lt;/li&gt;
  &lt;li&gt;
    &lt;button type="button" popovertarget="admitpop"&gt;Admissions&lt;/button&gt;
    
    &lt;!-- submenu with vertical navigation --&gt;
    &lt;ul focusgroup="menu" autofocus id="admitpop" popover&gt;
      &lt;li&gt;&lt;a tabindex="0"&gt;Apply&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;
        &lt;button type="button" popovertarget="tuitpop"&gt;Tuition&lt;/button&gt;
        
        &lt;!-- submenu with vertical navigation --&gt;
        &lt;ul focusgroup="menu" autofocus id="tuitpop" popover&gt;
          &lt;li&gt;&lt;a tabindex="0"&gt;Undergraduate&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a tabindex="0"&gt;Graduate&lt;/a&gt;&lt;/li&gt;
        &lt;/ul&gt;

      &lt;/li&gt;
      &lt;li&gt;&lt;a tabindex="0"&gt;Visit&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;    
    </code></pre></div><h4>Excluding elements with <code>focusgroup=&#8221;none&#8221;</code></h4><p>You can exclude elements in a focusgroup by setting <code>focusgroup=&#8221;none&#8221;</code> on them. This will make an element unreachable by arrow navigation, but you can still use TAB and shift+TAB to access it. </p><p>In the example below, the second button is not reachable with the arrow keys:</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 focusgroup="toolbar wrap"&gt;
  &lt;button&gt;A&lt;/button&gt;
  &lt;button focusgroup="none"&gt;(Not arrow reachable)&lt;/button&gt;
  &lt;button&gt;B&lt;/button&gt;
&lt;/div&gt;    
    </code></pre></div><p>Check the <a href="https://whatpwacando.today/focusgroup">demo on What PWA Can Do Today</a> in Chrome 146+ and definitely read the <a href="https://developer.chrome.com/blog/focusgroup-rfc">article</a> on the Chrome Developers Blog for more details.</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>Better title attributes with <code>interestfor</code></h2><p>Chromium-based browsers now support Interest Invokers, which you can compare to Invoker Commands. But instead of clicking, they invoke an action when the user hovers over an element. Think of popovers, but then they are opened when a button is hovered instead of clicked.</p><p>For example, you can let a button open a popover on hover with the <code>interestfor</code> attribute which holds the <code>id</code> of the popover as its value:</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;button type="button" interestfor="title-popover"&gt;Hover me&lt;/button&gt;

&lt;div id="title-popover" popover="hint"&gt;Here I am!&lt;/div&gt;</code></pre></div><p>The <code>interestfor</code> attribute also implicitly anchors the popover to the button, so you can use anchor positioning to position the popover:</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">#title-popover {
  position-area: bottom span-right;
  position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;
}</code></pre></div><p>Of course, you can use the native <code>title</code> attribute to show a title on hover, but this way, you can style the title and configure the time it takes before the popover is shown and hidden with 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">[interestfor] {
  /* delay before interest is shown */
  interest-delay-start: 200ms;

  /* delay before interest is hidden */
  interest-delay-end: 200ms;
}</code></pre></div><p>The API also exposed events when the element with <code>interestfor</code> is hovered and unhovered; respectively, &#8220;interest&#8221; and &#8220;loseinterest&#8221;. Just like with the Invoker Commands API, the event is dispatched on the target of the invoker and not the invoker (button) itself, so you only need to set an event on the popover and not an event on each button.</p><p>In the demo I created on codepen, I use the &#8220;interest&#8221; event to read the <code>data-title</code> attribute of the hovered button to show its value (the title of the button) in the popover.</p><p>Check the demo <a href="https://codepen.io/dannymoerkerke/pen/NPrEWOM">here</a>.</p><p></p><p></p><p></p><p></p><p></p><p>check sw update in spa with navigation api</p><p>title with interestfor</p><p>geolocation element, usermedia element</p><p>focusgroup</p><p>datatransfer</p><p>share_target </p><p>https://github.com/danielsakhapov/declarative-scroll-commands-for-html-explainer </p><p>clipboardchange</p><p>transform in anchor positioning https://codepen.io/bramus/pen/pvyrmWv</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> element for this.</p><p>At the time of writing, the original trial hasn&#8217;t been launched yet, but you can already enable it in Chrome 147 at <a href="http://chrome://flags/#web-app-installation-api">chrome://flags/#web-app-installation-api</a></p><p>The declarative <code>&lt;install&gt;</code> renders a button. When the app is not installed, it will show and install icon and the text &#8220;Install&#8221;. When the app is installed, it will show a launch icon and the text &#8220;Launch&#8221;.</p><p>When used without attributes, it will install the current web app:</p><pre><code>&lt;install&gt;
  [optional fallback content]
&lt;/install&gt;</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>&lt;install installurl="https://whatpwacando.today/"
         manifestid="https://whatpwacando.today/pwa-today"&gt;
  [optional fallback content]
&lt;/install&gt;</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;: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/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="" 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>After the app has been installed, it will look 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_!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;: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/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="" 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 has been installed</figcaption></figure></div><p>This 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 can now automatically show a &#8220;Launch&#8221; button instead of an &#8220;Install&#8221; button.</p><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>As soon as the original trial is launched, I will register and you can view the demo of <code>&lt;install&gt;</code> at <a href="https://whatpwacando.today/installation">https://whatpwacando.today/installation</a> in Chrome 147. </p><p>If you enable the flag at <a href="http://chrome://flags/#web-app-installation-api">chrome://flags/#web-app-installation-api</a> you can already view it right now.</p><div><hr></div><h2>How to make sure push notifications open your PWA on Android</h2><p>I recently worked on a bug where an installed PWA was not opened when a push notification was sent to it and the user tapped it. Instead, the default browser was opened, despite the fact that the <code>notificationclick</code> event handler was correct.</p><p>Here&#8217;s a condensed version of the handler I use:</p><pre><code>const notificationClickHandler = async (e) =&gt; {
  const {notification} = e;
  const {data} = notification;

  notification.close();

  e.waitUntil(clients.openWindow(data.url))
};</code></pre><p>The <code>notification</code> property of <code>NotificationEvent</code> contains the data that was sent as part of the push notification in its <code>data</code> property. This in turn has a <code>url</code> property that points to the URL that must be opened in the PWA when it&#8217;s installed.</p><p>This worked fine on iOS but on Android, the URL was opened in the default brower instead of the PWA.</p><p>After examining, I found that the <code>manifest.json</code> file of the PWA didn&#8217;t have a <code>scope</code> member. </p><p><code>scope</code> defines the top-level URL path that contains the web app&#8217;s pages and subdirectories. It effectively defines the scope of the service worker, in the sense that only URLs within the scope will be opened in the app, and any URLs outside it will be opened in the default browser or in-app browser when applicable. </p><p>The spec states that when <code>scope</code> is not defined, it will be set to the <code>start_url </code>value after removing its filename, query, and fragment, but on Android this apparently  doesn&#8217;t apply when a push notification is clicked.</p><p>I always specify a <code>scope</code> out of habit so I wasn&#8217;t even aware of this issue, but this fixed the issue immediately.</p><p>Bottom line: always set <code>scope</code> in your <code>manifest.json</code>.</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>The importance of keeping the service worker alive with event.waitUntil()</h2><p>Another bug was reported to me this week that initially left me puzzled.</p><p>A user reported that the <a href="https://whatpwacando.today/notifications">notifications demo of What PWA Can Do Today</a> never showed a badge on the app icon when a push notification was received. This happened to him since iOS 17.4 on the same model iPhone I have myself.</p><p>My initial reply was that he probably had badges not enabled in the Notifications settings for 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_!50YT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1e33656-ec84-4043-9f1d-5295e5ef9fe9_300x649.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!50YT!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1e33656-ec84-4043-9f1d-5295e5ef9fe9_300x649.png 424w, https://substackcdn.com/image/fetch/$s_!50YT!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1e33656-ec84-4043-9f1d-5295e5ef9fe9_300x649.png 848w, https://substackcdn.com/image/fetch/$s_!50YT!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1e33656-ec84-4043-9f1d-5295e5ef9fe9_300x649.png 1272w, https://substackcdn.com/image/fetch/$s_!50YT!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1e33656-ec84-4043-9f1d-5295e5ef9fe9_300x649.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!50YT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1e33656-ec84-4043-9f1d-5295e5ef9fe9_300x649.png" width="300" height="649" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f1e33656-ec84-4043-9f1d-5295e5ef9fe9_300x649.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:649,&quot;width&quot;:300,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:69914,&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/186978872?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1e33656-ec84-4043-9f1d-5295e5ef9fe9_300x649.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_!50YT!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1e33656-ec84-4043-9f1d-5295e5ef9fe9_300x649.png 424w, https://substackcdn.com/image/fetch/$s_!50YT!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1e33656-ec84-4043-9f1d-5295e5ef9fe9_300x649.png 848w, https://substackcdn.com/image/fetch/$s_!50YT!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1e33656-ec84-4043-9f1d-5295e5ef9fe9_300x649.png 1272w, https://substackcdn.com/image/fetch/$s_!50YT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1e33656-ec84-4043-9f1d-5295e5ef9fe9_300x649.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">iOS Notifications settings with Badges disabled</figcaption></figure></div><p>But this was not the case, so what was going on? Same device, same settings, same iOS version, yet it worked on my device but not his&#8230;</p><p>Some context first; the handler for the <code>push</code> event shows the notification and then updates the badge when the app is not running. It keeps a count of the unread messages in IndexedDB and when a new push notification arrives, it increments the number of unread notifications and shows the number with a call to <code>navigator.setAppBadge(count)</code>.</p><p>I examined the whole flow multiple times and it consistently worked on my device so I didn&#8217;t know what was wrong. Until I took a closer look.</p><p>Here&#8217;s a condensed version of the handler for the <code>push</code> event:</p><pre><code>const pushHandler = e =&gt; {
  e.waitUntil((async () =&gt; {
    const data = e.data.json();
    const { title, message, interaction, url } = data;

    const options = {
      body: message,
      icon: '/src/img/icons/icon-512x512.png',
      
      ...
    };

    await self.registration.showNotification(title, options);

    const activeClients = await hasActiveClients();
    if (!activeClients) {
      updateBadges();
    }
  })().catch(err =&gt; sendMessage(err)));
};</code></pre><p>And this is the <code>updateBadges</code> function that updates the number of unread messages and shows the badge:</p><pre><code>const updateBadges = async (inc = 1) =&gt; {
  const store = await openStore(IDBConfig.stores.badgeStore, 'readwrite');
  const result = await store.get('num') || {num: 'num', count: 0};
  const numBadges = result.count;
  const count = Math.max(0, numBadges + inc);

  await store.put({num: 'num', count});

  navigator.setAppBadge(count);
};</code></pre><p>The <code>updateBadges</code> function uses <code>e.waitUntil</code> to make sure the service worker stays active until the <code>Promise</code> that it receives as its argument resolves.</p><p>Can you already guess where this is going?</p><p>Upon a closer look I realized I called <code>updateBadges</code> <em>without</em> <code>await</code>. </p><p>And <code>updateBadges</code> in turn called <code>navigator.setAppBadge(count) </code>without <br><code>await</code> as well!</p><p>So this could mean that the anonymous <code>async</code> function that was passed to <br><code>e.waitUntil</code> could resolve before <code>updateBadges</code> finished, causing the service worker to be terminated before the badge could be shown.</p><p>But it worked on my device without <code>await</code>!</p><p>Since the <code>await</code> really needed to be there in both places I added them and, lo and behold, <a href="https://github.com/DannyMoerkerke/whatpwacando.today/issues/87#issuecomment-3917664622">it started working for the bug reporter</a>. &#128170;</p><p>Here are the updated versions:</p><pre><code><code>const pushHandler = e =&gt; {
  e.waitUntil((async () =&gt; {
    const data = e.data.json();
    const { title, message, interaction, url } = data;

    const options = {
      body: message,
      icon: '/src/img/icons/icon-512x512.png',
      
      ...
    };

    await self.registration.showNotification(title, options);

    const activeClients = await hasActiveClients();
    if (!activeClients) {
      await updateBadges(); // await added
    }
  })().catch(err =&gt; sendMessage(err)));
};</code></code></pre><pre><code><code>const updateBadges = async (inc = 1) =&gt; {
  const store = await openStore(IDBConfig.stores.badgeStore, 'readwrite');
  const result = await store.get('num') || {num: 'num', count: 0};
  const numBadges = result.count;
  const count = Math.max(0, numBadges + inc);

  await store.put({num: 'num', count});

  await navigator.setAppBadge(count);  // await added
};</code></code></pre><p>Bottom line: in any event handler in the service worker that uses <code>e.waitUntil</code> (install, activate, push, sync, notificationclick), <em>always make sure that the </em><code>Promise</code><em> doesn&#8217;t resolve untill all work is done</em>.</p><p>Add <code>await</code> to all calls to <code>async</code> functions. If you are using <code>.then</code> with Promises, make sure to return them:</p><pre><code>e.waitUntil(
  foo().then(() =&gt; {
    return bar();
  })
  .then(...)
);</code></pre><p>I initially missed this because it worked without issues on my device, but service worker timing may differ between devices, so you can never guarantee that it will work properly unless you properly await or return Promises in <code>e.waitUntil</code>.</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><item><title><![CDATA[Modern Web Weekly #55]]></title><description><![CDATA[The modern web, tested and explained in plain English]]></description><link>https://modernwebweekly.substack.com/p/modern-web-weekly-55</link><guid isPermaLink="false">https://modernwebweekly.substack.com/p/modern-web-weekly-55</guid><dc:creator><![CDATA[Danny Moerkerke]]></dc:creator><pubDate>Thu, 12 Jun 2025 14:05:59 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!ozQf!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6db1beed-af83-42c9-816d-fcd07cfcaddf_8192x5464.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_!ozQf!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6db1beed-af83-42c9-816d-fcd07cfcaddf_8192x5464.heic" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ozQf!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6db1beed-af83-42c9-816d-fcd07cfcaddf_8192x5464.heic 424w, https://substackcdn.com/image/fetch/$s_!ozQf!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6db1beed-af83-42c9-816d-fcd07cfcaddf_8192x5464.heic 848w, https://substackcdn.com/image/fetch/$s_!ozQf!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6db1beed-af83-42c9-816d-fcd07cfcaddf_8192x5464.heic 1272w, https://substackcdn.com/image/fetch/$s_!ozQf!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6db1beed-af83-42c9-816d-fcd07cfcaddf_8192x5464.heic 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ozQf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6db1beed-af83-42c9-816d-fcd07cfcaddf_8192x5464.heic" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6db1beed-af83-42c9-816d-fcd07cfcaddf_8192x5464.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;:1029081,&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/165340627?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6db1beed-af83-42c9-816d-fcd07cfcaddf_8192x5464.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_!ozQf!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6db1beed-af83-42c9-816d-fcd07cfcaddf_8192x5464.heic 424w, https://substackcdn.com/image/fetch/$s_!ozQf!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6db1beed-af83-42c9-816d-fcd07cfcaddf_8192x5464.heic 848w, https://substackcdn.com/image/fetch/$s_!ozQf!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6db1beed-af83-42c9-816d-fcd07cfcaddf_8192x5464.heic 1272w, https://substackcdn.com/image/fetch/$s_!ozQf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6db1beed-af83-42c9-816d-fcd07cfcaddf_8192x5464.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><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@solenfeyissa?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Solen Feyissa</a> on <a href="https://unsplash.com/photos/a-close-up-of-a-cell-phone-with-buttons-39ZA5Nx3T7o?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Unsplash</a></figcaption></figure></div><h2>How screenshots for the install dialog of PWAs really work</h2><p>If you have created an installable web app, you really should include screenshots in the <code>manifest.json</code> file to enable the enhanced install dialog. This dialog is available in Chrome on desktop and Android and shows a native app-like install dialog that shows the &#8230;</p>
      <p>
          <a href="https://modernwebweekly.substack.com/p/modern-web-weekly-55">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Modern Web Weekly #54]]></title><description><![CDATA[The modern web, tested and explained in plain English]]></description><link>https://modernwebweekly.substack.com/p/modern-web-weekly-54</link><guid isPermaLink="false">https://modernwebweekly.substack.com/p/modern-web-weekly-54</guid><dc:creator><![CDATA[Danny Moerkerke]]></dc:creator><pubDate>Fri, 23 May 2025 08:30:52 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!oljU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde6bcbb1-b77c-41b6-b813-3efa5c4413c3_3840x1920.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_!oljU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde6bcbb1-b77c-41b6-b813-3efa5c4413c3_3840x1920.heic" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!oljU!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde6bcbb1-b77c-41b6-b813-3efa5c4413c3_3840x1920.heic 424w, https://substackcdn.com/image/fetch/$s_!oljU!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde6bcbb1-b77c-41b6-b813-3efa5c4413c3_3840x1920.heic 848w, https://substackcdn.com/image/fetch/$s_!oljU!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde6bcbb1-b77c-41b6-b813-3efa5c4413c3_3840x1920.heic 1272w, https://substackcdn.com/image/fetch/$s_!oljU!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde6bcbb1-b77c-41b6-b813-3efa5c4413c3_3840x1920.heic 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!oljU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde6bcbb1-b77c-41b6-b813-3efa5c4413c3_3840x1920.heic" width="1456" height="728" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/de6bcbb1-b77c-41b6-b813-3efa5c4413c3_3840x1920.heic&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:728,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:238355,&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/164150909?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde6bcbb1-b77c-41b6-b813-3efa5c4413c3_3840x1920.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_!oljU!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde6bcbb1-b77c-41b6-b813-3efa5c4413c3_3840x1920.heic 424w, https://substackcdn.com/image/fetch/$s_!oljU!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde6bcbb1-b77c-41b6-b813-3efa5c4413c3_3840x1920.heic 848w, https://substackcdn.com/image/fetch/$s_!oljU!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde6bcbb1-b77c-41b6-b813-3efa5c4413c3_3840x1920.heic 1272w, https://substackcdn.com/image/fetch/$s_!oljU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde6bcbb1-b77c-41b6-b813-3efa5c4413c3_3840x1920.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><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@teamnocoloco?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Team Nocoloco</a> on <a href="https://unsplash.com/photos/a-computer-screen-with-the-words-the-modern-way-to-build-for-the-web-OX1TXahR7Ng?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Unsplash</a></figcaption></figure></div><h2>Declarative Web Push in Safari on macOS</h2><p>Safari 18.5 on macOS now supports Declarative Web Push for both web apps running in Safari and web apps added to the Dock. </p><p>In <a href="https://modernwebweekly.substack.com/p/modern-web-weekly-51">Modern Web Weekly #51</a>, I showed the payload that is needed for a notification to be handled declaratively on iOS (and now also macOS):</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&#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-54">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Modern Web Weekly #53]]></title><description><![CDATA[The modern web, tested and explained in plain English]]></description><link>https://modernwebweekly.substack.com/p/modern-web-weekly-53</link><guid isPermaLink="false">https://modernwebweekly.substack.com/p/modern-web-weekly-53</guid><dc:creator><![CDATA[Danny Moerkerke]]></dc:creator><pubDate>Thu, 08 May 2025 19:08:22 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!IZUH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F657b1011-10b4-45a2-ae66-d8438177187c_5507x3098.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_!IZUH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F657b1011-10b4-45a2-ae66-d8438177187c_5507x3098.heic" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!IZUH!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F657b1011-10b4-45a2-ae66-d8438177187c_5507x3098.heic 424w, https://substackcdn.com/image/fetch/$s_!IZUH!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F657b1011-10b4-45a2-ae66-d8438177187c_5507x3098.heic 848w, https://substackcdn.com/image/fetch/$s_!IZUH!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F657b1011-10b4-45a2-ae66-d8438177187c_5507x3098.heic 1272w, https://substackcdn.com/image/fetch/$s_!IZUH!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F657b1011-10b4-45a2-ae66-d8438177187c_5507x3098.heic 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!IZUH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F657b1011-10b4-45a2-ae66-d8438177187c_5507x3098.heic" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/657b1011-10b4-45a2-ae66-d8438177187c_5507x3098.heic&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2324257,&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/162039876?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F657b1011-10b4-45a2-ae66-d8438177187c_5507x3098.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_!IZUH!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F657b1011-10b4-45a2-ae66-d8438177187c_5507x3098.heic 424w, https://substackcdn.com/image/fetch/$s_!IZUH!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F657b1011-10b4-45a2-ae66-d8438177187c_5507x3098.heic 848w, https://substackcdn.com/image/fetch/$s_!IZUH!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F657b1011-10b4-45a2-ae66-d8438177187c_5507x3098.heic 1272w, https://substackcdn.com/image/fetch/$s_!IZUH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F657b1011-10b4-45a2-ae66-d8438177187c_5507x3098.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><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@emilep?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Emile Perron</a> on <a href="https://unsplash.com/photos/macbook-pro-showing-programming-language-xrVDYZRGdw4?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Unsplash</a></figcaption></figure></div><h2>Better event handling with Observers</h2><p>If you have ever worked with a library like RxJS, you will know how well its declarative API works for composing event handlers. Without such a library, something like drag-and-drop can be complicated because it requires nested event handlers and hard-to-follow callback chains.</p><p>For exam&#8230;</p>
      <p>
          <a href="https://modernwebweekly.substack.com/p/modern-web-weekly-53">
              Read more
          </a>
      </p>
   ]]></content:encoded></item></channel></rss>