Jeremy Keith

Jeremy Keith

Making websites. Writing books. Hosting a podcast. Speaking at events. Living in Brighton. Working at Clearleft. Playing music. Taking photos. Answering email.

Journal 3201 sparkline Links 10729 sparkline Articles 87 sparkline Notes 7974 sparkline

Saturday, November 29th, 2025

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.

Friday, November 28th, 2025

Thursday, November 27th, 2025

Today was my last working day for 2025. I’m off until January. Christmas starts now!

The schedule for Web Day Out

Here’s the schedule for Web Day Out—what a fantastic collection of talks!

Web Day Out
10:00 – 10:30 I can’t believe it’s not JavaScript Jemima Abu
10:30 – 11:00 A pragmatic guide to browser support Rachel Andrew
11:30 – 12:00 Progressive web apps from the trenches Aleth Gueguen
12:00 – 12:30 Build for the web, build on the web, build with the web Harry Roberts
14:00 – 14:30 Breaking with habits Manuel Matuzovič
14:30 – 15:00 What’s new in web typography? Richard Rutter
15:30 – 16:00 Customisable <select> and the friends we made along the way Jake Archibald
16:00 – 16:30 The browser is the playground Lola Odelola

Seeing all of those talk titles in a row is getting me very, very excited for this day!

I hope that you’re excited too, and I hope you’ve got your ticket already.

If you need to convince your boss to send you (and your team) to Web Day Out I’ve put together some reasons to attend along with an email template that you can use as a starting point.

Also, if your company is sending a group of people anyway, consider sponsoring Web Day Out. You get a bunch of conference tickets as part of the sponsorship deal.

Hope to see you in Brighton on Thursday, 12 March 2026!

The line and the stream. — Ethan Marcotte

I’ve come to realize that statements about the future aren’t predictions: they’re more like spells. When someone describes something to you as the future, they’re sharing a heartfelt belief that this something will be part of whatever comes next. “Artificial intelligence isn’t going anywhere” quite literally involves casting a technology forward into time. How could that be anything else but a kind of magic?

Escape Velocity: Break Free from Framework Gravity — Den Odell

React is no longer just a library. It’s a full ecosystem that defines how frontend developers are allowed to think.

Real talk!

Browsers now ship View Transitions, Container Queries, and smarter scheduling primitives. The platform keeps evolving at a fair pace, but most teams won’t touch these capabilities until React officially wraps them in a hook or they show up in Next.js docs.

Innovation keeps happening right across the ecosystem, but for many it only becomes “real” once React validates the approach. Which is fine, assuming you enjoy waiting for permission to use the platform you’re already building on.

Zing!

The critique isn’t that React is bad, but that treating any single framework as infrastructure creates blind spots in how we think and build. When React becomes the lens through which we see the web, we stop noticing what the platform itself can already do, and we stop reaching for native solutions because we’re waiting for the framework-approved version to show up first.

If your team’s evolution depends on a single framework’s roadmap, you are not steering your product; you are waiting for permission to move.

Wednesday, November 26th, 2025

Why use React?

This isn’t a rhetorical question. I genuinely want to know why developers choose to build websites using React.

There are many possible reasons. Alas, none of them relate directly to user experience, other than a trickle-down justification: happy productive developers will make better websites. Citation needed.

It’s also worth mentioning that some people don’t choose to use React, but its use is mandated by their workplace (like some other more recent technologies I could mention). By my definition, this makes React enterprise software in this situation. My definition of enterprise software is any software that you use but that you yourself didn’t choose.

Inertia

By far the most common reason for choosing React today is inertia. If it’s what you’re comfortable with, you’d need a really compelling reason not to use it. That’s generally the reason behind usage mandates too. If we “standardise” on React, then it’ll make hiring more straightforward (though the reality isn’t quite so simple, as the React ecosystem has mutated and bifurcated over time).

And you know what? Inertia is a perfectly valid reason to choose a technology. If time is of the essence, and you know it’s going to take you time to learn a new technology, it makes sense to stick with what you know, even if it’s out of date. This isn’t just true of React, it’s true of any tech stack.

This would all be absolutely fine if React weren’t a framework that gets executed in browsers. Any client-side framework is a tax on the end user. They have to download, parse, and execute the framework in order for you to benefit.

But maybe React doesn’t need to run in the browser at all. That’s the promise of server-side rendering.

The front end

There used to be a fairly clear distinction between front-end development and back-end development. The front end consisted of HTML, CSS, and client-side JavaScript. The back end was anything you wanted as long as it could spit out those bits of the front end: PHP, Ruby, Python, or even just a plain web server with static files.

Then it became possible to write JavaScript on the back end. Great! Now you didn’t need to context-switch when you were scripting for the client or the server. But this blessing also turned out to be a bit of a curse.

When you’re writing code for the back end, some things matter more than others. File size, for example, isn’t really a concern. Your code can get really long and it probably won’t slow down the execution. And if it does, you can always buy your way out of the problem by getting a more powerful server.

On the front end, your code should have different priorities. File size matters, especially with JavaScript. The code won’t be executed on your server. It’s executed on all sorts of devices on all sorts of networks running all sorts of browsers. If things get slow, you can’t buy your way out of the problem because you can’t buy every single one of your users a new device and a new network plan.

Now that JavaScript can run on the server as well as the client, it’s tempting to just treat the code the same. It’s the same language after all. But the context really matters. Some JavaScript that’s perfectly fine to run on the server can be a resource hog on the client.

And this is where it gets interesting with React. Because most of the things people like about React still apply on the back end.

React developers

When React first appeared, it was touted as front-end tool. State management and a near-magical virtual DOM were the main selling points.

Over time, that’s changed. The claimed speed benefits of the virtual DOM turned out to be just plain false. That just left state management.

But by that time, the selling points had changed. The component-based architecture turned out to be really popular. Developers liked JSX. A lot. Once you got used to it, it was a neat way to encapsulate little bits of functionality into building blocks that can be combined in all sorts of ways.

For the longest time, I didn’t realise this had happened. I was still thinking of React as being a framework like jQuery. But React is a framework like Rails or Django. As a developer, it’s where you do all your work. Heck, it’s pretty much your identity.

But whereas Rails or Django run on the back end, React runs on the front end …except when it doesn’t.

JavaScript can run on the server, which means React can run on the server. It’s entirely possible to have your React cake and eat it. You can write all of your code in React without serving up a single line of React to your users.

That’s true in theory. The devil is in the tooling.

Priorities

Next.js allows you to write in React and do server-side rendering. But it really, really wants to output React to the client as well.

By default, you get the dreaded hydration pattern—do all the computing on the server in JavaScript (yay!), serve up HTML straight away (yay! yay!) …and then serve up all the same JavaScript that’s on the server anyway (ya—wait, what?).

It’s possible to get Next.js to skip that last step, but it’s not easy. You’ll be battling it every step of the way.

Astro takes a very different approach. It will do everything it can to keep the client-side JavaScript to a minimum. Developers get to keep their beloved JSX authoring environment without penalising users.

Alas, the collective inertia of the “modern” development community is bound up in the React/Next/Vercel ecosystem. That’s a shame, because Astro shows us that it doesn’t have to be this way.

Switching away from using React on the front end doesn’t mean you have to switch away from using React on the back end.

Why use React?

The titular question I asked is too broad and naïve. There are plenty of reasons to use React, just as there are plenty of reasons to use Wordpress, Eleventy, or any other technology that works on the back end. If it’s what you like or what you’re comfortable with, that’s reason enough.

All I really care about is the front end. I’m not going to pass judgment on anyone’s choice of server-side framework, as long as it doesn’t impact what you can do in the client. Like Harry says:

…if you’re going to use one, I shouldn’t be able to smell it.

Here’s the question I should be asking:

Why use React in the browser?

Because if the reason you’re using React is cultural—the whole team works in JSX, it makes hiring easier—then there’s probably no need to make your users download React.

If you’re making a single-page app, then …well, the first thing you should do is ask yourself if it really needs to be a single-page app. They should be the exception, not the default. But if you’re determined to make a single-page app, then I can see why state management becomes very important.

In that situation, try shipping Preact instead of React. As a developer, you’ll almost certainly notice no difference, but your users will appreciate the refreshing lack of bloat.

Mostly though, I’d encourage you to investigate what you can do with vanilla JavaScript in the browser. I totally get why you’d want to hold on to React as an authoring environment, but don’t let your framework limit what you can do on the front end. If you use React on the client, you’re not doing your users any favours.

You can continue to write in React. You can continue to use JSX. You can continue to hire React developers. But keep it on your machine. For your users, make the most of what web browsers can do.

Once you keep React on the server, then a whole world of possibilities opens up on the client. Web browsers have become incredibly powerful in what they offer you. Don’t let React-on-the-client hold you back.

And if you want to know more about what web browsers are capable of today, come to Web Day Out in Brighton on Thursday, 12th March 2026.

Tuesday, November 25th, 2025

Saturday, November 22nd, 2025

A child’s Halloween in Ireland

As part of their on-stage banter, The Dubliners used to quip that “All the books that are banned in Ireland should be published in Irish, to encourage more people to learn their native tongue.”

There was no shortage of banned books back in the day. I’m reading one of them now. The Country Girls by Edna O’Brien.

About halfway through the book, I read this passage:

The parcels for the Halloween party were coming every day. I couldn’t ask my father for one because a man is not able to do these things, so I wrote to him for money instead and a day girl brought me a barmbrack, apples, and monkey-nuts.

Emphasis mine, because that little list sounded so familiar to me.

Back in 2011, I wrote a candygram for Jason. It was called Monkey nuts, barmbrack and apples.

It’s not exactly Edna O’Brien, but looking back at it fifteen years on, I think it turned out okay.

Friday, November 21st, 2025

Thursday, November 20th, 2025

Manuel Matuzovič is speaking at Web Day Out

The line-up for Web Day Out is now complete! The final speaker to be added to the line-up is the one and only Manuel Matuzovič.

You may know Manuel from his superb Web Accessibility Cookbook (full disclosure: I had the honour of writing the foreword to that book). Or perhaps you’re familiar with the crimes against markup that he documents at HTMHell. But at Web Day Out, he’s going to be talking about CSS.

The past few years have seen a veritable explosion in CSS capabilities. It’s one thing to hear about all the new stuff in CSS, but how do you actually start using it?

You may need to unlearn what you have previously learned. That’s what Manuel’s talk will be covering:

Manuel built a new project from scratch with modern CSS and questioned every line of code he wrote.

In this talk, he presents what he has learned and encourages you to review your best practices.

You can see why I’m so excited about this—it’s perfect for the agenda of Web Day Out:

Do you feel like you’re missing out on some of the latest advances in HTML, CSS, and JavaScript APIs? Web Day Out is your chance to get up to speed on what matters.

There’ll be eight brilliant speakers for your entertainment:

  1. Jemima Abu
  2. Rachel Andrew
  3. Jake Archibald
  4. Aleth Gueguen
  5. Manuel Matuzovič
  6. Lola Odelola
  7. Harry Roberts
  8. Richard Rutter

You won’t want to miss this, so get your ticket now for the ludicrously reasonable price of just £225+VAT!

See you in Brighton on 12 March 2026!

Wednesday, November 19th, 2025

David Chisnall (*Now with 50% more sarcasm!*): “I think this needs to be repeated…”

Machine learning is amazing if … the value of a correct answer is much higher than the cost of an incorrect answer.

Related to Laissez-faire Cognitive Debt:

And that’s where I start to get really annoyed by a lot of the LLM hype. It’s pushing machine-learning approaches into places where there are significant harms for sometimes giving the wrong answer. And it’s doing so while trying to outsource the liability to the customers who are using these machines in ways in which they are advertised as working. It’s great for translation! Unless a mistranslated word could kill a business deal or start a war. It’s great for summarisation! Unless missing a key point could cost you a load of money. It’s great for writing code! Unless a security vulnerability would cost you lost revenue or a copyright infringement lawsuit from having accidentally put something from the training set directly in your codebase in contravention of its license would kill your business. And so on. Lots of risks that are outsourced and liabilities that are passed directly to the user.

Older »