Installing web apps

Safari, Chrome, and Edge all allow you to install websites as though they’re apps.

On mobile Safari, this is done with the “Add to home screen” option that’s buried deep in the “share” menu, making it all but useless.

On the desktop, this is “Add to dock” in Safari, or “Install” in Chrome or Edge.

Firefox doesn’t offer this functionality, which as a shame. Firefox is my browser of choice but they decided a while back to completely abandon progressive web apps (though they might reverse that decision soon).

Anyway, being able to install websites as apps is fantastic! I’ve got a number of these “apps” in my dock: Mastodon, Bluesky, Instagram, The Session, Google Calendar, Google Meet. They all behave just like native apps. I can’t even tell which browser I used to initially install them.

If you’d like to prompt users to install your website as an app, there’s not much you can do other than show them how to do it. But that might be about to change…

I’ve been eagerly watching the proposal for a Web Install API. This would allow authors to put a button on a page that, when clicked, would trigger the installation process (the user would still need to confirm this, of course).

Right now it’s a JavaScript API called navigator.install, but there’s talk of having a declarative version too. Personally, I think this would be an ideal job for an invoker command. Making a whole new install element seems ludicrously over-engineered to me when button invoketarget="share" is right there.

Microsoft recently announced that they’d be testing the JavaScript API in an origin trial. I immediately signed up The Session for the trial. Then I updated the site to output the appropriate HTTP header.

You still need to mess around in the browser configs to test this locally. Go to edge://flags or chrome://flags/ and search for ‘Web App Installation API’, enable it and restart.

I’m now using this API on the homepage of The Session. Unsurprisingly, I’ve wrapped up the functionality into an HTML web component that I call button-install.

Here’s the code. You use it like this:

<button-install>
  <button>Install the app</button>
</button-install>

Use whatever text you like inside the button.

I wasn’t sure whether to keep the button element in the regular DOM or generate it in the Shadow DOM of the custom element. Seeing as the button requires JavaScript to do anything, the Shadow DOM option would make sense. As Tess put it, Shadow DOM is for hiding your shame—the bits of your interface that depend on JavaScript.

In the end I decided to stick with a regular button element within the custom element, but I take steps to remove it when it’s not necessary.

There’s a potential issue in having an element that could self-destruct if the browser doesn’t cut the mustard. There might be a flash of seeing the button before it gets removed. That could even cause a nasty layout shift.

So far I haven’t seen this problem myself but I should probably use something like Scott’s CSS in reverse: fade in the button with a little delay (during which time the button might end up getting removed anyway).

My connectedCallback method starts by finding the button nested in the custom element:

class ButtonInstall extends HTMLElement {
  connectedCallback () {
    this.button = this.querySelector('button');
    …
  }
customElements.define('button-install', ButtonInstall);

If the navigator.install method doesn’t exist, remove the button.

if (!navigator.install) {
  this.button.remove();
  return;
}

If the current display-mode is standalone, then the site has already been installed, so remove the button.

if (window.matchMedia('(display-mode: standalone)').matches) {
  this.button.remove();
  return;
}

As an extra measure, I could also use the display-mode media query in CSS to hide the button:

@media (display-mode: standalone) {
  button-install button {
    display: none;
  }
}

If the button has survived these tests, I can wire it up to the navigator.install method:

this.button.addEventListener('click', async (ev) => {
  await navigator.install();
});

That’s all I’m doing for now. I’m not doing any try/catch stuff to handle all the permutations of what might happen next. I just hand it over to the browser from there.

Feel free to use this code if you want. Adjust the code as needed. If your manifest file says display: fullscreen you’ll need to change the test in the JavaScript accordingly.

Oh, and make sure your site already has a manifest file that has an id field in it. That’s required for navigator.install to work.

Have you published a response to this? :

Responses

Christian "Schepp" Schaefer

@adactio this is a fantastic first step and long overdue! 👏🏻👏🏻👏🏻

And I’m equally excited about the other new API that’s being discussed for installing PWAs located at other URLs / origins than where the button sits as this would finally enable web app catalogues and “stores” and thereby help again with discoverability.

15 Shares

# Shared by Dawn Ahukanna on Saturday, November 29th, 2025 at 1:54pm

# Shared by Evil Jim O’Donnell on Saturday, November 29th, 2025 at 1:55pm

# Shared by Djoerd Hiemstra 🍉 on Saturday, November 29th, 2025 at 2:27pm

# Shared by KB on Saturday, November 29th, 2025 at 2:37pm

# Shared by Ruud Steltenpool on Saturday, November 29th, 2025 at 2:43pm

# Shared by sudo rm -rf fascism on Saturday, November 29th, 2025 at 4:37pm

# Shared by Sue on Saturday, November 29th, 2025 at 7:09pm

# Shared by Paul Melero on Saturday, November 29th, 2025 at 7:41pm

# Shared by planeth on Sunday, November 30th, 2025 at 9:18pm

# Shared by Matt Panhans on Friday, December 12th, 2025 at 6:30am

# Shared by Fabio! 🐈 on Friday, December 12th, 2025 at 6:30am

# Shared by Lil PWA on Friday, December 12th, 2025 at 6:30am

# Shared by Alex Russell on Friday, December 12th, 2025 at 6:30am

# Shared by Tom Arild Jakobsen on Friday, December 12th, 2025 at 8:10am

# Shared by meronz on Friday, December 12th, 2025 at 9:27am

27 Likes

# Liked by Martin Grubinger on Saturday, November 29th, 2025 at 12:39pm

# Liked by Paul Burgess on Saturday, November 29th, 2025 at 1:11pm

# Liked by Mads Stoumann on Saturday, November 29th, 2025 at 1:11pm

# Liked by Evil Jim O’Donnell on Saturday, November 29th, 2025 at 1:11pm

# Liked by Sérgio Nunes on Saturday, November 29th, 2025 at 1:45pm

# Liked by Dawn Ahukanna on Saturday, November 29th, 2025 at 1:54pm

# Liked by Luca Fabbri on Saturday, November 29th, 2025 at 2:10pm

# Liked by Tobias Fedder on Saturday, November 29th, 2025 at 2:16pm

# Liked by Dave Letorey on Saturday, November 29th, 2025 at 2:48pm

# Liked by Поміркований бандера 🇺🇦 on Saturday, November 29th, 2025 at 3:17pm

# Liked by Johannes Odland on Saturday, November 29th, 2025 at 4:07pm

# Liked by sudo rm -rf fascism on Saturday, November 29th, 2025 at 4:37pm

# Liked by Zoë Bijl on Saturday, November 29th, 2025 at 6:32pm

# Liked by Sue on Saturday, November 29th, 2025 at 7:09pm

# Liked by Arby on Saturday, November 29th, 2025 at 9:30pm

# Liked by Ben Ricaud on Saturday, November 29th, 2025 at 11:16pm

# Liked by finxol on Saturday, November 29th, 2025 at 11:48pm

# Liked by Frank // Mottokrosh on Sunday, November 30th, 2025 at 9:27am

# Liked by onpolitics.news on Sunday, November 30th, 2025 at 4:24pm

# Liked by Matt Panhans on Friday, December 12th, 2025 at 6:29am

# Liked by galvao|galvaoetibr@bsky.social on Friday, December 12th, 2025 at 6:30am

# Liked by Stuart Rutherford on Friday, December 12th, 2025 at 6:30am

# Liked by Alex Russell on Friday, December 12th, 2025 at 6:30am

# Liked by christianoliff :-) on Friday, December 12th, 2025 at 7:34am

# Liked by Marco Otte-Witte on Friday, December 12th, 2025 at 8:10am

# Liked by Ed on Friday, December 12th, 2025 at 4:13pm

# Liked by Miguel on Monday, December 15th, 2025 at 11:18pm

Related posts

Train coding

Generating a static copy of The Session from the comfort of European trains.

Preventing automated sign-ups

Here’s a bit of PHP I’m using on The Session.

Pickin’ dates on iOS

Mobile Safari doesn’t support the min and max attributes on date inputs.

Today, the distant future

2022 was once unimaginable to some web folks.

Lists

Do websites need to sound the same in every screen reader?

Related links

Building WebSites With LLMS - Jim Nielsen’s Blog

And by LLMS I mean: (L)ots of (L)ittle ht(M)l page(S).

I really like this approach: using separate pages instead of in-page interactions. I remember Simon talking about how great this works, and that was a few years back, before we had view transitions.

I build separate, small HTML pages for each “interaction” I want, then I let CSS transitions take over and I get something that feels better than its JS counterpart for way less work.

Tagged with

Moving on from React, a Year Later

Many interactions are not possible without JavaScript, but that doesn’t mean we should look to write more than we have to. The server doing something useful is a requirement for building an interesting business. The client doing something is often a nice-to-have.

There’s also this:

It’s really fast

One of the arguments for a SPA is that it provides a more reactive customer experience. I think that’s mostly debunked at this point, due to the performance creep and complexity that comes in with a more complicated client-server relationship.

Tagged with

Hyper-responsive web components | Trys Mudford

Trys describes exactly the situation where you really do need to use the Shadow DOM in a web component—as opposed to just sticking to HTML web components—, and that’s when the component is going to be distributed and you have no idea where:

This component needed to be incredibly portable, looking great on any third-party website, in any position, at any viewport, with any amount of content. It had to be a “hyper-responsive” component.

Tagged with

Untapped – Using Simple Tools as a Radical Act of Independence

It would be much harder for a 15-year-old today to View Source and understand the code structure that built the website they’re on. Every site is layered with analytics, code snippets, javascript plugins, CMS data, and more.

This is why the simplicity of HTML and CSS now feels like a radical act. To build a website with just these tools is a small protest against platform capitalism: a way to assert sustainability, independence, longevity.

Tagged with

a view source web

As a self-initiated learner, being able to view source brought to mind the experience of a slow walk through someone else’s map.

This ability to “observe” software makes HTML special to work with.

Tagged with

Previously on this day

4 years ago I wrote UX London 2023

It’s back! And if you hurry, you can grab a super early-bird ticket now.

11 years ago I wrote Cache-limiting in Service Workers …again

Trying to figure out which event to use as a trigger.

19 years ago I wrote Legrid

Lego as a design tool.

24 years ago I wrote The Session

I’ve had some free time this week and, in typical geek fashion, I’ve been spending it coding PHP.

25 years ago I wrote More Anagram Fun

Here’s some more fun from the Modern Humorist: "If poets wrote poems whose titles were anagrams of their names".

25 years ago I wrote Ban on DVD-cracking code upheld

Following up on an earlier post, a court has now ruled that it is illegal for me to write this here:

25 years ago I wrote Walk For Capitalism

I think I’m going to be sick.