Journal tags: ar

999

sparkline

Image to text

At The Web You Want event in Amsterdam last month, Léonie gave an absolutely fantastic talk. You can now read the whole thing: Accessibility is resistance!

She makes the very good point that generative tools are a necessary accessibility recourse when humans have failed to do their job properly.

Images without alt text? Somebody messed up. But this is damage that can be routed around with large language models.

So if you—like me—don’t like the idea of people using extractive generative tools made by the worst kind of people, pay heed:

If you want to resist AI, make accessibility part of everything you do, every decision you make, every product you design and build. Remove the need for people to use AI to compensate, and remember, accessibility is resistance.

Also last month, I wrote about enhancing images from a carousel to masonry using CSS Grid Lanes. Here’s the context:

Over on The Session, I added a little enhancement to the events and sessions listings recently. I make a call to the Google Places API to see if I can find a match for the venue, and if I do, pull in some photos.

Sidenote: right now there’s a major issue with this. None of the photos come with text descriptions. This is something I need to fix, and I’ve got some ideas on how to do that.

I was one of those people with a website that doesn’t have alt text for images. I was one of those people not doing their job.

The first fix I put in place was very much in keeping with the ethos of The Session. I added a form so that any member of the site can edit the alt text of the images in the carousel/masonry layout.

The Session is quite WikiPedia-like. Anyone can edit the track listings in the discography. Anyone can edit the details of any tune. Anyone can edit the details of any session or event. So it made sense to add one more thing that anyone can edit.

With that functionality in place, I put out the call:

If everyone were to take just one or two sessions or events and add alt text to the photos, it would really, really help make the site more accessible.

The community went to work and did a pretty good job.

But there are a lot images. I’d like to have a least some kind of alt text for all of them, even if it’s just a placeholder until someone gets around to adding something better.

I was going to have to use generative “AI”, wasn’t I?

Holding my nose, I investigated some alt-text-as-a-service APIs out there. I signed up for some trials and kicked the tyres. They were rubbish. I suspect most of them were vibe-coded.

The more I thought about it, the more I realised that I probably didn’t need the latest and greatest energy-guzzling large langauge models. Some good old-fashioned machine learning could do the trick here. I’m aiming to do a relatively straightforward transformation—image to text.

Check out the COCO project: Common Objects in Context. It’s a few years old now, but it’s basically a dataset that’s been trained on images.

Ah, but where did the training data come from?

All images in COCO were sourced from Flickr, a photo-sharing platform where users upload images under various Creative Commons licenses.

Works for me!

I found a model on Hugging Face that was created by Microsoft from the COCO data. Confusingly the model is called GIT. It’s nothing to do with version control. It stands for GenerativeImage2Text.

I downloaded the model and set to work creating a script to trawl through all those images and generate alt text if the image doesn’t already have one.

I should mention: the way that I store the alt text is not in a database, but in the file system. Every image has its own folder. Inside that folder there can also be a file called alt.txt.

So the script needed to loop through lots and lots of folders, generate alt text where needed, and save it into a text file in the same folder.

Python is the go-to language for working with models like GIT. But Python is not my strong suit, to put it mildly. And this was going to be a throwaway script that I was only going to use once. So I held my nose again and turned on Copilot in VSCode (I usually keep it switched off).

With the right instructions I was able to get a script that—crucially—I could read and understand. Then I switched off Copilot and had a shower to try to wash the shame away.

Long story short, the script worked.

The results are mostly fine. The alt text is functional. Sometimes it’s wrong, but not disastrously so. A human-generated image description will always be preferable, so any member of The Session can over-ride the placeholder alt text.

It took a few hours to loop through all those folders and generate all those text files. My laptop got hot. But doing it locally sure beats paying for tokens from the baddies.

Like Scott says:

While the utility is there, the current landscape of these frontier corporate models comes with massive ethical and environmental baggage. This is not a long term solution!

That’s exactly why my hope is that ethically trained open-weight models will eventually catch up and that in the next couple of years, we’ll have something with this level of capability that can run locally. That feels like a much better place to be with this technology.

Enhancing with CSS Grid Lanes

CSS Grid Lanes has started to ship in browsers. It’s in Safari and behind a flag in Chrome and Edge.

It enables masonry layouts, where items get packed together in the most efficient way possible.

Unsurprisingly, I’m a fan of a layout tool where the browser does all the hard work. It very much aligns with the idea of declarative design; you specify the boundary conditions, and then browser does the maths and heavy lifting.

There’s a handy website called The Field Guide to Grid Lanes where you can play around with possibilities.

At the most recent CSS Day, Patrick Brosset gave a great talk showing what you could do with Grid Lanes. I immediately started playing around with it, and I spotted what I think could be a useful pattern…

Over on The Session, I added a little enhancement to the events and sessions listings recently. I make a call to the Google Places API to see if I can find a match for the venue, and if I do, pull in some photos.

Sidenote: right now there’s a major issue with this. None of the photos come with text descriptions. This is something I need to fix, and I’ve got some ideas on how to do that.

Anyway, these photos are just nice-to-haves so I’ve tucked them away into a details element with a simple summary like “Ten photos” or “Twenty photos”. If you open up that details element you get the photos in a horizontal swipable row. A carousel, if you will.

This works fine, but on larger screens I think it would be okay to show all the photos at once. That’s where Grid Lanes comes in.

Take a look at an event or a session in Safari to see what I mean.

Here’s the CSS that creates a carousel:

.gallery {
    display: flex;
    align-items: center;
    inline-size: fit-content;
    max-inline-size: 100%;
    overflow-inline: auto;
    scroll-snap-type: inline mandatory;
    overscroll-behavior-inline: contain;
    scroll-behavior: smooth;
    scrollbar-gutter: stable;
}
.gallery > * {
    flex-shrink: 0;
    scroll-snap-align: center;
}

And here’s the media query that turns it into a masonry layout:

@supports (display: grid-lanes) {
    @media all and (min-width: 56em) {
        .gallery {
            all: initial;
            display: grid-lanes;
            grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
            gap: 0.5em;
        }
        .gallery > * {
            inline-size: 100%;
        }
    }
}

I’m using all: initial to unset the previous styles, which is a bit of a sledgehammer but it works.

I think this could be a useful responsive design pattern. Masonry layouts are great for large screens but kind of rubbish for small screens where you end up with just a single column. Carousels aren’t much cop on large screens but maybe have their place on small screens where real estate is at a premium.

Oh, and needless to say, this is a progressive enhancement. If a browser doesn’t yet understand display: grid-lanes it continues to get the carousel layout.

A tale of two browsers

I give Apple a hard time. That’s mostly due to how they treat the web on their own mobile devices.

Though iOS ostensibly supports the ability for websites to be added to the homescreen, they make it so difficult for users to do, the functionality is practically worthless. It’s dispiriting to see the web so hamstrung by that decision.

The Webkit team has come in for other criticism too. For a while there, Safari was lagging so far behind in features that people were calling it the new Internet Explorer. Ouch!

But credit where credit is due. The upcoming version 27 of Safari is looking very good.

That’s not because it’s at the cutting edge of the latest web standards. Quite the opposite. Most of the changes listed for this release are bug fixes. That’s what I want to acknowledge and applaud.

Far too often a browser will rush out an implementation of an exciting new web standard that gets plenty of attention. But that initial implementation is rarely 100% correct. Then the next release rolls around and the focus has moved on to a different new web standard. The result is an ever-growing backlog of almost-but-not-quite-supported features.

Clearly the focus for Safari 27 was on that backlog. I bet that wasn’t an easy decision. Like I said, the kudos and recognition tends to go to the browser that ships new stuff, not the browser that goes back to fix long-standing issues.

Don’t get me wrong, there’s some exciting new stuff in Safari 27 too, like styleable select, but it’s great to see the focus on maintenance and repair:

If you look through the lists of features and fixes in Safari 27, you’ll notice that, although there are 58 brand-new features and 525 fixes — the largest pile of fixes in any Safari release in recent memory — most of what is released is not about new things.

Most of this work has been about existing features behaving more correctly, handling more edge cases, and fitting together with other features the way you’d expect.

This in sharp contrast to the most recent release of Chrome that shipped support for the prompt API despite opposition from other browsers and no positive signals from developers. I hope some Googler got a nice promotion for shoving a proprietary technology into a web browser, but they should be aware of the damage they’ve done.

At this year’s CSS Day, the represenatatives from Google Chrome were once again there to talk to developers and ask what we wanted them to prioritise. Those requests rang very hollow. Why should we waste our time and energy telling a browser team what we need if they’re just going to ship whatever crap they want?

The truth is that the folks from Google who were canvassing opinions from the attendees at CSS Day are not the same people who torpedoed the browser with unwanted proprietary tech. This team has spent years doing excellent outreach, documenting web standards, and meeting with developers. They built up an impressive amount of trust, respect, and goodwill.

That stock has now plummeted.

So well done to the Webkit team for Safari 27. And shame on the Chrome team for Chrome 148.

25 years of The Session

The Session existed in a very basic form since the late 1990s. It was just me posting a different tune every week.

But The Session as it is today—a community website where everyone can add tunes—first went online on June 3rd, 2001. That’s 25 years ago today.

Considering the typical lifespan of a web page, I’m proud of having a website still online and thriving a quarter of a century after launching it.

At this point it’s fair to say that thesession.org is my life’s work. Though, really, I’m just the curator; the site would literally be nothing without all the contributions that people have made to it.

It’s been a great 25 years so far, and I’m looking forward to the next 25.

Gaeltacht cois Tamaise 2026

Bhí me i Londain an deireadh seachtaine seo caite mar gheall ar an Gaeltacht cois Tamaise. Cúpla lá iontach ba ea iad!

Bhí na ranganna ar siúl Dé Sathairn agus Dé Domhnaigh, ceithre huaire an chloig gach lá, i gColáiste na Rí. Bhí ceithre leibhéal ann—tosathóiri, meanleibhéal-iseal, meanleibhéal-ard, agus an ardleibhéal. Bhí gach rang lán le foghlaimeoirí.

Roghnaigh mé an rang meanleibhéal-ard agus bhí an leibhéal foirfe. D’fhreastail Jessica ar an rang tosathóirí agus dúirt sí go raibh a mhúinteor iontach deas freisin.

Bhraith sé aisteach a bheith ag labhairt Gaeilge i lár na phriomhcathair Shasana, ach bhain mé go leor sult as!

Roimh na ranganna, bhí imeachta ar siúl ar an Embasáid na hÉireann ar an tráthnóna Dé hAoine; taifeadadh beo ar an bpodchraoladh How To Gael le Louis Cantillon agus Doireann ní Ghlacáin. Éistim leis an podchraoladh, mar sin thapaigh mé an deis iad a fheiceáll beo. Mná cliste agus greanmhar is ea iad!

Bhí imeachta eile ar siúl ar an tráthnóna De Sathairn ach ní raibh mé ann. Chuaigh mé go dtí an teach tabhairne Brendan The Navigator i Highgate—i bhfad ó croílár na caithreach!—mar gheall go raibh seisiún ceoil ann. Seisiún iontach iontach deas a bhí ann le daoine fáiltiúil agus go leor poirt áille.

Beidh mé ar ais!

The closing talks at UX London 2026

When I told you about the schedule for UX London 2026, I said:

After your afternoon workshop there’ll be one final closing talk at the end of each day before we head to the bar.

These closing talks are a way of bringing everyone back into the same space after spending the afternoon in different workshops. It feels right to start the day and end the day with a shared experience.

On day one, discovery day, the closing talk will be delivered by Michael Kibedi. It’s called Whose English gets to be default?

Ben Sauer will be giving the closing talk on day two, design day. His talk is called Story before screens.

Finally, on day three, delivery day, the closing talk will be from Lou Downe. It’s called Bad services, which also happens to be the title of their brand new book!

As you can see, each day at UX London is crafted to be a distinct one-day event, but all three days also flow together nicely.

If you haven’t got a ticket yet, grab one now before the standard pricing ends at midnight. And don’t forget that you can use the discount code JOIN_JEREMY to get a tasty 20% off.

The schedule for UX London 2026

There’s just under a month to go until UX London 2026—exciting!

You can peruse the full schedule if you need to decide wether you’re coming for just one day or for all three. The event is designed to flow together, from discovery day to design day to delivery day, but each individual day is also designed to be a standalone experience by itself.

Day one on Tuesday, June 2nd has a focus on research:

  1. Maria Isachenko will talk about how You don’t need more research time: You need a system that keeps research in product decisions.
  2. Melin Edomwonyi covers Validation as a UX superpower.
  3. Marley Dizney Swanson will present From insight to impact: A hypothesis-driven framework for product teams.
  4. Luisa Berta will be talking about Turning failure into opportunity.

A black and white profile of a young woman with long hair. A woman with curly hair and glasses smiling and tilting her head. A young person with short hair smiling wearing a jacket. A smiling woman with long straight brown hair and a pink top.

Day two on Wednesday, June 3rd is all about the nitty-gritty details of design:

  1. Julia Petretta kicks things off with From onboarding to “a-ha!”: Designing the moments that really matter.
  2. Andrea Grigsby has a case study called Why must things be this way? Designing with intention.
  3. Piccia Neri puts a positive spin on accessibility with her talk, The best creative brief.
  4. Hidde de Vries will explain why The future of UX is green: On the Web Sustainability Guidelines.

A black and white portrait of a woman with dark shoulder-length hair. A smiling young woman with straight dark hair wearing a red top. A woman with shoulder-length white hair and a jacket outdoors standing to the side and looking at the camera. A smiling man with short hair wearing a collared shirt under his jumper

Day three on Thursday, June 4th will cover collaboration and design systems:

  1. Ben Callahan will impart Wisdom from the trees.
  2. Lucy Blackwell and Alex Edwards will give a case study on Putting the user at the centre of your design system.
  3. Rachel Ilan Simpson will take us From 0 to scale: Building and transforming design at startups & scale-ups.
  4. Matt LeMay will cover why The communication of the thing IS the thing

A shaven-headed man with a beard looking right at you with his tilted slightly to one side. A smiling young woman with shoulder-length blonde hair wearing a dark top. A woman wearing glasses and a colourful floral shirt. A woman with short hair and a dark top against a pastel background. A man with short curly hair and glasses wearing a light plaid shirt in front of a light background

And those are just the morning talks!

On each day you’ll have your choice of workshop for the afternoon.

  1. Feyikemi Akinwolemiwa will cover Future friction: Horizon scanning for UX.
  2. Natasha den Dekker will help you answer the question How well do you know your users? Exploring assumptions through play
  3. Chris How’s workshop is Yippee IA: Information architecture for digital designers
  4. Oore Babatunde will help you put together UX practitioner’s code of ethics.
  5. Lucrezia Ponzano will take you From chaos to clarity: A tactical workshop for real alignment.
  6. Ben Callahan will guide you through Assessing organisational culture.

Portrait of a woman dressed in black wearing glasses with her hair tied up. A young woman with a yellow top holding a microphone and speaking as she gestures, looking to the side. A smiling man with curly dark hair and glasses wearing a purple shirt. A smiling woman with glasses and shoulder-length hair wearing a floral top in front of a patterned background. A woman facing to the side but with her head turned to the camera, wearing a white shirt against a grey background. A shaven-headed man with a beard looking right at you with his tilted slightly to one side.

After your afternoon workshop there’ll be one final closing talk at the end of each day before we head to the bar. I haven’t announced those speakers yet, but believe me when I say they’re going to be quite special!

UX London 2026 is shaping up to be an excellent three days of design. Get your ticket now if you haven’t already got one.

(And just between you and me, you can use the discount code JOIN_JEREMY to get a whopping 20% off any ticket price!)

Summary punishment

In the latest issue of Matthias’s excellent Own Your Web series, he describes the recent betrayal by Google:

The search engine no longer says “here, go read what this person wrote.” It now says “here, I’ve already read it for you.” The contract is broken.

He’s absolutely right.

But…

Have you ever clicked on a result from a search engine? Unless you’re lucky enough to land on a nice personal website, you’re more than likely to be confronted with pop-ups to allow tracking, or a desparate plea to subscribe to a newsletter, or just rubbish ads all accompanied by a slow page loading somewhere in the mix.

Don’t get me wrong. I’m not saying that what Google is doing is okay. But let’s not pretend that everything indexed by Google is just fine and dandy for people to visit.

And of course the main reason why websites are so terrible is because they’ve tied their business model to heaps of behavioral advertising driven by invasive tracking courtesy of …Google.

This reminds me of AMP. Remember Google AMP? It was a terrible solution to a real problem. Web pages were (and still are) bloated and slow. The correct solution would be to encourage people to fix that, but instead Google mandated a proprietary format for your content that had to be hosted on their servers.

AMP was a disaster, both in practical terms and in the reputational damage it did to Google’s developer relations.

Now they’re doing it again, powerwashing away any goodwill they ever had with site owners. Now Google doesn’t even send search engine traffic to the websites that host the ads that Google encouraged people to put on every page.

It’s almost as if Google is a company so large and with so many competing interests that it now suffers from an incurable split personality disorder.

Personally I think they’re missing a trick. They should be using “AI” summaries as a stick.

If your site is slow, or filled with user-hostile annoyances then it should be cockblocked by a hallucinated summary. But a nice fast respectful website? Send the traffic their way! Everyone wins—users, site owners, Google, the World Wide Web.

Could you imagine how quickly this would revolutionise the world of search engine optimisation? They’ve always told us that we should make websites for humans in order to get good Google juice. This would be a way of making it come true, without any of the over-engineered woefulness of AMP.

It’ll never happen of course. But I can dream.

Threat models

People talk about the effectiveness (or lack thereof) of large language models as though all tasks are comparable. But it strikes me that there are three broad categories of work that large language models are applied to:

  1. Compression.
  2. Transformation.
  3. Expansion.

Compression is when you feed a large language model something big that you want to make small. Summarise this book. Give me the gist of this meeting. Large language models are generally pretty good at this, which makes sense given that they themselves are kind of like compressed artifacts.

Transformation is when large language models convert from one format into another. Turn this audio into text. Turn this jumble of data into structured JSON. A large language model can handle these tasks pretty well. There’ll probably be a few errors so make sure that’s not a deal-breaker.

Expansion is when you give a large language model a prompt to generate something from scratch. An image. A presentation. An email. A poem. This is where slop lives. The output inevitably betrays its origins, glistening with a sheen of mediocrity.

Laurie spotted this three-way split a while back:

Is what you’re doing taking a large amount of text and asking the LLM to convert it into a smaller amount of text? Then it’s probably going to be great at it. If you’re asking it to convert into a roughly equal amount of text it will be so-so. If you’re asking it to create more text than you gave it, forget about it.

I hope that when the bubble finally bursts, we’ll see the surviving large language models put to work on the first two categories. The boring stuff. The work that’s tedious for humans.

But tedious is as tedious does. Something I consider drudgery might be the very thing that gives you life. Like Giles says:

I have a feeling that everyone likes using AI tools to try doing someone else’s profession. They’re much less keen when someone else uses it for their profession.

The big exception seems to be programming. Apparently there are plenty of coders who never before expressed an interest in being managers who are now happily hanging up their coding spurs in favour being the overseer of non-human workers.

It’s a reasonable outlook. It could even be considered a user-centred approach. Users don’t care about the elegance of your code; they care about accomplishing their tasks.

Programming is something of an exception to the efficacy of large language models in general. Instead of relying on the subjectivity of painting, poetry, or prose, programming can be objectively tested. Throw enough money at the worst people in the world and they’ll give you tokens you can use to get the machines to test their own output. So you can get a large language model to create something reasonably good from scratch as long as that something is code.

If you had asked me about the threat model of large language models two years ago, I probably would’ve been worried for artists, writers, and musicians. I thought that software had enough inherent complexity to be relatively safe.

Now my opinion has completely reversed. Software is almost certainly the killer app for large language models.

I think the artists, writers, and musicians will be okay, or at least as okay as they ever were. It turns out that humans like things made by other humans.

And y’know what? If I had to choose which endeavour I’d rather see automated away—programming or art—it’s no competition.

Don’t get me wrong—it would be nice if everyone got paid for doing what they enjoy. It’s just that I’m okay with software engineers not being at the front of that line.

I remember when I first started getting paid money to make websites. “Really?” I thought, “Someone is willing to pay me to do something I’d do anyway?” I kept waiting for the jig to be up. Instead I saw my profession grow and expand.

Perhaps there’s a long-overdue compression happening.

Or maybe it’s more like a transformation.

My salary history

Times are tough out there. I know that a lot of people are looking for work, which can be a very stressful experience.

One of the things that can make the job search stressful is uncertainty. There’s a real taboo around talking about salaries. This taboo ends up benefiting employers and punishing potential employees. There’s an information gap that can be exploited (see also: job postings that don’t list salary ranges).

That’s why I’m always pleased when people voluntarily share their income. Here are some of the people who have done this over the years:

Because the jobs are generally in software or design, you can sort of make apples-to-apples comparisons. You can definitely get the general gist of what kind of salary to expect for certain roles.

In the interest of full transparency, I figured I’d share my own income numbers, though as you’ll see, they’re not very representative of a normal career:

  • 2003: £15,434 (freelance)
  • 2004: £15,900 (freelance)
  • 2005: £14,125 (freelance)
  • 2006: £43,009 (freelance/Clearleft)
  • 2007: £34,900 (Clearleft)
  • 2008: £33,833 (Clearleft)
  • 2009: £35,549 (Clearleft)
  • 2010: £37,174 (Clearleft)
  • 2011: £40,666 (Clearleft)
  • 2012: £39,750 (Clearleft)
  • 2013: £39,500 (Clearleft)
  • 2014: £48,655 (Clearleft)
  • 2015: £46,499 (Clearleft)
  • 2016: £52,106 (Clearleft)
  • 2017: £56,492 (Clearleft)
  • 2018: £59,498 (Clearleft)
  • 2019: £59,670 (Clearleft)
  • 2020: £43,807 (Clearleft)
  • 2021: £48,344 (Clearleft)
  • 2022: £60,446 (Clearleft)
  • 2023: £55,721 (Clearleft)
  • 2024: £47,104 (Clearleft)
  • 2025: £42,133 (Clearleft)

The first thing you’ll notice is that agency work isn’t nearly as well paid as in-house work at a technology company. So don’t embrace agency life for the money. Speaking personally, the benefits are in autonomy and variety. Those are things I value highly.

Also, I haven’t put any job titles or levels on there because they’ve never really been codified for me. I just made up my own job titles as I went along. Again, this is not very helpful to you if you’re looking for a job at a typical company.

You’ll see that things got weird in 2020, which is to be expected because things did get weird in 2020. I was furloughed, and I also took some more time off. I got a taste for it, which is why I went down to a four-day week and later a three-day week, which is what I’m doing now. So those last five years of numbers are loopy—I’m making less than before, but if you were to adjust it for a five-day week, I’m still getting paid more than before …if that makes sense.

Perhaps the most unusual thing about my career trajectory is that I’ve been at the same place for twenty years now. That’s pretty much unheard of in tech. It’s far more usual to see people switch companies—and get a salary bump—every couple of years.

So I’m not sure if there’s any value in me sharing my numbers like this. But like I said, I admire when other people do it so I figured I’d throw mine out there.

Perhaps you’d like to share your numbers too.

TinyStart

Sometimes I look back through my blogging archives and notice what’s changed over time.

For example, I used to write quite enthusiastically about the arrival of a new operating system from Apple. That is no longer the case, to put it mildly. I’m currently holed up on Sequioa, trying to resist all the nudgings to “upgrade” to the tacky design nightmare that is Tahoe. I feel like the protagonist of Pluribus.

I used to write about software I really liked. Sometimes it was software made by Apple. More often it was from some independent developer.

Like, I remember how much I loved a little application called Quicksilver. It just did one thing. You pressed control and space and then started typing the name of any programme installed on your computer. After a few characters Quicksilver would show you the match, you hit enter and the programme launched.

If that process sounds familiar, it’s because Apple ended up incorporating it into their own Spotlight feature. Quicksilver got sherlocked (ask your parents).

Recently though, Spotlight got worse and worse at doing its one job. It’s been laggy and inaccurate, even though I set my Spotlight indexing options to only index the Applications folder.

Then I found TinyStart. It’s like Quicksilver reborn!

A tiny launcher for macOS, fast and focused on the essentials.

Actually, it does double duty. As well as being an application launcher, it’s also an emoji picker. 👍

Best of all, not only is TinyStart a return to the focus and quality of software of yore, it’s also a return to the pricing model. You buy the software—for a measly €5—and that’s it. You own it now. There’s no subscription you have to pay every month.

I love everything about this.

Mistrust

Four years ago I wrote about something that has long puzzled me in the world of front-end development. Trust:

The mindset I’ve noticed is that many developers are suspicious of browser features but trusting of third-party libraries.

Developers are more likely to trust, say, Bootstrap than they are to trust CSS grid or custom properties. Developers are more likely to trust React than they are to trust web components.

That post got some thoughtful responses but I never really understood the imbalance of trust and suspicion:

I’m kind of confused by this prevalent mindset of trusting third-party code more than built-in browser features.

But something happened recently that helped me understand that mindset better.

I wrote a while back about how the datalist element on iOS has been completely fucked up. It’s worse than if Safari simply didn’t support it.

Breaking the web like that should be a five-alarm fire, but nobody is in any rush to fix it. I recall a similar lackadaisical attitude when Safari completely broke their implentation of IndexedDB.

I had it in my head that browser features followed a forward path generally. They’d be iterated on and improved on to iron out any glitches, but it was reasonable to expect things to get better with each new version of a browser.

Now I see that’s not necessarily the case.

Had I used an over-engineered JavaScript library instead of the datalist element, I wouldn’t be facing the current situation of having to use browser-sniffing to avoid sending a standard HTML element to any browser on iOS.

Sure, that third-party JavaScript would mean that users are downloading more code, and it probably wouldn’t work well with assistive technology, but as long as I didn’t touch it, it would continue to work. That should be true of web standards—I should be able to use them secure in the knowledge that they won’t suddenly shit the bed.

Perhaps I should be grateful to Apple for dispelling my naïveté. I now have much more empathy and understanding for web developers who are suspicious of web standards and prefer to use third-party libraries instead.

Good job, Apple. Happy anniversary.

Early-bird tickets for UX London

You should come to UX London in the first week of June. Why? Because it’s going to be awesome, that’s why!

You probably knew that already. You probably already decided to get a ticket because you’re smart like that.

But don’t dilly-dally! Early-bird tickets are available now but in just over one week, they won’t be.

So get your ticket by Friday, March 27th. If you get your ticket now, it’s a win for everyone. You get a cheaper ticket. We know for sure that you’re coming.

Every time someone buys a conference ticket in plenty of time, the conference organiser sleeps a little better at night.

If you need to convince your boss, you can give them these reasons to attend. I even made an email template you can use a starting point for making the case.

You could come for all three days of UX London, or you can pick just one day.

Tuesday, June 2nd is discovery day with a focus on user research. You’ll hear from great speakers like Melin Edomwonyi and Maria Isachenko as well as getting workshops from Natasha den Dekker and Feyikemi Akinwolemiwa.

Wednesday, June 3rd is design day where it’s all about the nitty-gritty details. Not only will there be great talks from Andrea Grigsby, Julia Petretta, and Hidde de Vries, there’s going to be the best-named workshop ever from my colleague Chris How: Yippee IA!

Thursday, June 4th is delivery with a focus on design systems and collaboration. Alex Edwards, Lucy Blackwell, Rachel Ilan Simpson and Ben Callahan will all be giving talks (and Ben’s doing a workshop too).

That’s not even close to the final line-up. I’m confirming more speakers right now and getting very, very excited about how it’s all shaping up.

You know you don’t want to miss this one. So get your early-bird ticket now while you still can.

A black and white profil…g woman with long hair. A woman with curly hair …g and tilting her head. Portrait of a woman dres… with her hair tied up. A smiling young woman wi…n front of neon lights. A smiling young woman wi…inst a blue background. A black and white portra…k shoulder-length hair. A smiling man with short… shirt under his jumper A smiling man with curly…wearing a purple shirt. A smiling young woman wi…air wearing a dark top. A woman wearing glasses …colourful floral shirt. A woman with short hair …st a pastel background. A shaven-headed man with…d slightly to one side.

That was Web Day Out

On March 12th, 1989, Tim Berners-Lee submitted Information Management: A Proposal. This would form the basis of what became the World Wide Web.

On March 12th, 2026, Web Day Out happened in Brighton.

Coincidence?

Yes. Yes, it is a coincidence. But it’s a pretty nice coincidence, you must admit.

It was a day dedicated to the World Wide Web. Not just the foundational languages of the web—HTML, CSS, and JavaScript—but also the foundational ideas of the web.

“Share what you know!” That was the original motto of the World Wide Web project. That was the motto of Web Day Out too.

Look, I’m biased because I put the line-up together but honestly, all of the speakers were superb! So much knowledge delivered in such entertaining fashion.

I had a blast. And I’ll give myself a little pat on the back for how I grouped the talks into rhyming couplets:

Browsers: Jemima talked about what you can do with just HTML and CSS these days, and Rachel followed up with how to come up with your own browser support strategy.

Performance: Aleth made the case for multi-page progressive web apps that work under any network conditions, and Harry followed up with an impassioned rant about how much time and energy has been wasted on over-engineered single-page apps that ignore what browsers can do.

Styling: Manuel walked us through a whole new approach to writing modern CSS, and Rich followed up with a whirlwind tour of all the great typographic possibilities in CSS.

Standards Jake took us on the standards journey to customisable select elements, including anchor positioning and popovers, and then Lola showed us exactly what it takes to add a new feature to a web browser.

Everything flowed together really nicely.

I was a little apprehensive going into Web Day Out that it would just be preaching to the converted. And sure, there were plenty of veteran devs there who already knew the value of progressive enhancement and making the most of web standards. But I was gratified to also see lots of younger faces in the crowd.

I was talking to one young developer afterwards and she told me what an eye-opening experience it was. Whereas before she would have defaulted to a framework-driven single-page app for everything, now she’s got the knowledge to make an appropriate architectural choice.

Mission accomplished!

If you couldn’t make it to Web Day Out and you want to experience some RAMO, here’s the chatter on Bluesky and Mastodon, lovely photos by Marc, a post by Dave, and a lovely post by Amber.

Thank you so much to everyone who came. I think you’ll agree it was a most excellent day out.

A web font strategy

The Session has been online in some form since the late 1990s. That’s long before web fonts existed.

To begin with, Times New Roman was the only game in town if you wanted serif type on a website. When Microsoft introduced Georgia it was a godsend. A beautiful typeface designed by Matthew Carter for the screen. I put it right at the start of my font stack for The Session.

Later, web fonts came along. Boy, does that short sentence belie the drama! There were very heated discussions about whether web browsers should provide this ability at all, and what it would mean for type foundries.

Microsoft led the way with their prorietary EOT format. Then everyone agreed on WOFF. Finally we got WOFF2, Electric Boogaloo.

Perhaps more important than that, we got intermediaries. Typekit, Fontdeck, and then the big daddy, Google Fonts.

That’s pretty much the state of play today. Oh yeah, and we’ve got variable fonts now.

I remember Nick Sherman presenting the idea of variable fonts at an Ampersand event years ago. I remember thinking “great idea, but it’ll never happen.” Pure science fiction. I thought the same thing when I first saw a conference presentation about a miraculous image format called Scalable Vector Graphics.

Sometimes I like to stop and take stock of what we take for granted in web browsers now. Web fonts. Variable web fonts. SVG. Flexbox. Grid. Media queries. Container queries. Fluid typography. And I haven’t even mentioned how we were once limited to just 216 colours on the web.

Georgia

Given all the advances in web typography, you might be wondering how my font strategy for The Session changed over the years.

It didn’t.

I mean, sure, I added fluid typography. That was a natural extension of my love for liquid layouts and, later, responsive design. But the font stack itself? That was still Georgia all the way.

Y’see, performance has always been a top priority for The Session. If I was going to replace a system font with a web font that the user had to download, it really needed to be worth it.

Over the years I dabbled with different typefaces but none of them felt quite right to me. And I still think Georgia is a beautiful typeface.

“But your website will look like lots of other websites!” some may cry. That used to be true when all we had was system fonts. But now that web fonts have become the norm, it’s actually pretty unusual to see Georgia in the wild.

Lora

Recently I found a font I liked. Part of why I like it is that it shares a lot of qualities with Georgia. It’s Lora by Olga Karpushina and Alexei Vanyashin.

I started to dabble with it and began seriously contemplating using it on The Session.

It’s a variable font, which is great. But actually, I’m not using that many weights on The Session. I could potentially just use a non-variable variety. It comes in fixed weights of regular, medium, semibold, and bold.

Alas, the regular weight (400) is a bit too light and the medium weight (500) is a bit too heavy. My goldilocks font weight is more like 450.

Okay, so the variable font it is. That also allows me to play around with some subtle variations in weights. As the font size gets bigger for headings, the font weight can reduce ever so slightly. And I can adjust the overall font weight down in dark mode (there’s no grading feature in this font, alas).

Subsetting

Lora supports a lot of alphabets, which is great—quite a few alphabets turn up on The Session occasionally. But this means that the font file size is quite large. 84K.

Subsetting to the rescue!

I created a subset of Lora that has everything except Cyrillic, Greek, and Latin Extended-B. I created another subset that only has Cyrillic, Greek, and Latin Extended-B. Now I’ve got two separate font files that are 48K and 41K in size.

I wrote two @font-face declarations for the two files. They’ve got the same font-family (Lora), the same font-weight (400 700), and the same font-style (normal) but they’ve got different values for unicode-range. That way, browsers know to only use appropriate file when characters on the page actually match the unicode range.

The first file is definitely going to be used. The second one might not even be needed on most pages.

I want to prioritise the loading of that first subsetted font file so it gets referenced in a link element with rel="preload".

The switcheroo

As well as file size, my other concern was how the swapping from Georgia to Lora would be perceived, especially on a slow connection. I wanted to avoid any visible rejiggering of the content.

This is where size-adjust comes in, along with its compadres ascent-override and descent-override.

Rather than adjusting the default size of Lora to match that of Georgia, I want to do it the other way around; adjust the fallback font to match the web font.

Here’s how I’m doing it:

@font-face {
    font-family: 'Fallback for Lora';
    src: local('Georgia');
    size-adjust: 105.77%;
    ascent-override: 95.11%;
    descent-override: 25.9%;
}

And then my font stack is:

font-family: Lora, 'Fallback for Lora', Georgia, serif;

It’s highly unlikely that any device out there has a system font called “Fallback for Lora” so I can be pretty confident that the @font-face adjustment rules will only get applied to browsers that have the right local font, Georgia.

But where did those magic numbers come from for size-adjust, ascent-override, and descent-override?

They came from Katie Hempenius. As well as maintaing a repo of font metrics, she provides the formula needed to calculate all three values. Or you could use this handy tool to eyeball it.

With that, Georgia gets swapped out for Lora with a minimum of layout shift.

First-timers and repeat visitors

Even with the layout shift taken care of, do I want to serve up web fonts to someone on a slow connection?

It depends. Specifically, it depends on whether it’s their first time visiting.

The Session already treats first time visitors differently to repeat visitors. The first time you visit the site, critical CSS is embedded in the head of the HTML page instead of being referenced in an external style sheet. Only once the page has loaded does the full style sheet also get downloaded and cached.

I decided that my @font-face rules pointing to the web fonts are not critical CSS. If it’s your first time visiting, those CSS rules only get downloaded after the page is done loading.

And unless you’re on a fast connection, you won’t see Georgia get swapped out for Lora. That’s because I’ve gone with a font-display value of “optional”.

Most people use “swap”. Some people use “fallback”. You’ve got to be pretty hardcore to use “optional”.

But the next page you go to, or the next time you come to the site, you more than likely will see Lora straight away. That’s because of the service worker I’ve got quietly putting static assets into the Cache API: CSS, JavaScript, and now web fonts.

So even though I’m prioritising snappy performance over visual consistency, it’s a trade-off that only really comes into play for first visits.

Next

I’m pretty happy with the overall strategy. Still, I’m not going to just set it and forget it. I’ll be monitoring the CRUX data for The Session keeping a particular eye on cumulative layout shift.

Before adding web fonts, the cumulative layout shift on The Session was zero. I think I’ve taken all the necessary steps to keep it nice and low, but if I’m wrong I’ll need to revisit my strategy.

Update: Big thanks to Roel Nieskens—of Wakamai Fondue fame—who managed to get the file size of my main subsetted font down even further; bedankt!

Testing browser support for `focusgroup`

In my previous post, I mentioned that I’ve used the web install API in production. Specifically, I’ve used it on The Session. In order to do that, I had to register for the origin trial.

I’ve just signed up for another origin trial. This time it’s for the proposed focusgroup attribute:

The focusgroup HTML attribute is a proposed declarative way to add keyboard arrow-key navigation to composite widgets such as toolbars, tablists, menus, listboxes, etc. without writing any roving-tabindex JavaScript. One attribute replaces hundreds of lines of boilerplate.

I’ve got an HTML web component on The Session called tab-controls. And yes, there’s a bunch of code in there to listen for keyboard events and respond appropriately. I would very much like to rip that code out.

So now that I’ve opted into the origin trial, I’ve added this to my HTML:

<tab-controls role="tablist" focusgroup="tablist">

If this focusgroup attribute takes off, I’ll be able to remove the role attribute but for now, it’s very much needed.

In the JavaScript for my tab-controls custom element, I need to be able to detect support for focusgroup. Here’s how I’m doing it:

if (!this.focusgroup) {
// do all my key handling stuff here
}

Here’s the important thing: don’t use getAttribute('focusgroup') to test for browser support. That will return true if the attribute is in the HTML. But the attribute will only get converted into a property if the browser understands it.

Jake has a lot more detail on the differences between attributes and properties.

Anyway, I figured I’d share that little snippet in case you too were interested in trying out the focusgroup proposal using progressive enhancement.

Installing web apps

I have websites in my dock on my computer. I have websites on the home screen of my phone. When I open these websites from the dock or from the home screen, they behave just like native apps. It’s brilliant!

But knowing that you can add a website to the dock or to the home screen remains arcane knowledge. If you don’t know it’s possible, most web browsers aren’t going to tell you it’s an option. As a site owner, you pretty much have to explain to your users what they can do.

Lately it feels like there’s been some movement to change this situation. Or, at the very least, there’s been some discussion.

As a site owner, what you want is a way for someone visiting your site to press a button to initiate the process of adding the site to the dock or the home screen.

From what I can see from the discussion, there are two contenders for how to do this: BeforeInstallPromptEvent versus navigator.install.

I’ve used both APIs in production, so I’d like to offer my balanced feedback on both:

  • BeforeInstallPromptEvent sucks.
  • navigator.install rocks.

To add more detail…

The BeforeInstallPromptEvent API relies on you capturing and delaying an event that may or not fire at all.

Based on some arbitrary heuristics, Chrome—for example—will prompt the user to install the current website. This easily-dismissable prompt looks indistinguishable from a prompt to sign up to a newsletter or grant permission for cookies, so most people dismiss it. The idea with BeforeInstallPromptEvent is that you capture that prompt, prevent it from prompting, and then release it when you think it’s an appropriate time.

If you think it takes mental gymnastics to understand that, just imagine what it’s like trying to implement it!

The whole thing rests on this flawed idea of an install prompt being shown when certain conditions are met. Other browser vendors rightly point out that users should be able to install any website they want. Ideally it should have a manifest file. But making a service worker a requirement is a step too far (and I say that as someone who literally wrote a book about service workers).

Contrast that with the Web Install API, AKA navigator.install.

Based on a user interaction—like a click on a button—the browser initiates the installation process. The user still has to confirm they want to do this, of course. You know how geolocation or web notifactions work? It’s like that. You can’t trigger any of those APIs without the user’s permission.

That’s it. No contest.

It would be absolutely wonderful if more browsers supported navigator.install. It would be a pain in the ass if they decided to support BeforeInstallPromptEvent instead.

Feedback

If you wanted to make a really crude approximation of project management, you could say there are two main styles: waterfall and agile.

It’s not as simple as that by any means. And the two aren’t really separate things; agile came about as a response to the failures of waterfall. But if we’re going to stick with crude approximations, here we go:

  • In a waterfall process, you define everything up front and then execute.
  • In an agile process, you start executing and then adjust based on what you learn.

So crude! Much approximation!

It only recently struck me that the agile approach is basically a cybernetic system.

Cybernetics is pretty much anything that involves feedback. If it’s got inputs and outputs that are connected in some way, it’s probably cybernetic. Politics. Finance. Your YouTube recommendations. Every video game you’ve ever played. You. Every living thing on the planet. That’s cybernetics.

Fun fact: early on in the history of cybernetics, a bunch of folks wanted to get together at an event to geek about this stuff. But they knew that if they used the word “cybernetics” to describe the event, Norbert Wiener would show up and completely dominate proceedings. So they invented a new alias for the same thing. They coined the term “artificial intelligence”, or AI for short.

Yes, ironically the term “AI” was invented in order to repel a Reply Guy. Now it’s Reply Guy catnip. In today’s AI world, everyone’s a Norbert Wiener.

The thing that has the Wieners really excited right now in the world of programming is the idea of agentic AI. In this set-up, you don’t do any of the actual coding. Instead you specify everything up front and then have a team of artificial agents execute your plan.

That’s right; it’s a return to waterfall. But that’s not as crazy as it sounds. Waterfall was wasteful because execution was expensive and time-consuming. Now that execution is relatively cheap (you pay a bit of money to line the pockets of the worst people in exchange for literal tokens), you can afford to throw some spaghetti at the wall and see if it sticks.

But you lose the learning. The idea of a cybernetic system like, say, agile development, is that you try something, learn from it, and adjust accordingly. You remember what worked. You remember what didn’t. That’s learning.

Outsourcing execution to machines makes a lot of sense.

I’m not so sure it makes sense to outsource learning.

Counting down to Web Day Out

Not long now ’till Web Day Out — just three weeks!

It’s also not that long until the start of a new financial year so if you’ve got training budget that needs to be used this year, send your team to Web Day Out. Not only is it excellent value for money, it’s also going to have an incredibly high density of knowledge bombs per talk.

CSS! Progressive web apps! Web typography! Browser support! And much more.

If you like the sound of Web Day Out, you’ll also like State Of The Browser, which is just ten days away. In-person tickets for that event are now sold out, but online streaming tickets are still available.

Better yet, if you buy a ticket to Web Day Out, you automatically get a free online streaming ticket for State Of The Browser!

So get your ticket in the next ten days, enjoy State Of The Browser from the comfort of your own home, and then enjoy a trip to Brighton for Web Day Out on Thursday, 12 March. See you there!

Magic

I don’t like magic.

I’m not talking about acts of prestidigitation and illusion. I mean the kind of magic that’s used to market technologies. It’s magic. It just works. Don’t think about it.

I’ve written about seamless and seamful design before. Seamlessness is often touted as the ultimate goal of UX—“don’t make me think!”—but it comes with a price. That price is the reduction of agency.

When it comes to front-end development, my distrust of magic tips over into being a complete control freak.

I don’t like using code that I haven’t written and understood myself. Sometimes its unavoidable. I use two JavaScript libraries on The Session. One for displaying interactive maps and another for generating sheet music. As dependencies go, they’re very good but I still don’t like the feeling of being dependant on anything I don’t fully understand.

I can’t stomach the idea of using npm to install client-side JavaScript (which then installs more JavaScript, which in turn is dependant on even more JavaScript). It gives me the heebie-jeebies. I’m kind of astonished that most front-end developers have normalised doing daily trust falls with their codebases.

While I’m mistrustful of libraries, I’m completely allergic to frameworks.

Often I don’t distinguish between libraries and frameworks but the distinction matters here. Libraries are bits of other people’s code that I call from my code. Frameworks are other people’s code that call bits of my code.

Think of React. In order to use it, you basically have to adopt its idioms, its approach, its syntax. It’s a deeper level of dependency than just dropping in a regular piece of JavaScript.

I’ve always avoided client-side React because of its direct harm to end users (over-engineered bloated sites that take way longer to load than they need to). But the truth is that I also really dislike the extra layer of abstraction it puts between me and the browser.

Now, whenever there’s any talk about abstractions someone inevitably points out that, when it comes to computers, there’s always some layer of abstraction. If you’re not writing in binary, you don’t get to complain about an extra layer of abstraction making you uncomfortable.

I get that. But I still draw a line. When it comes to front-end development, that line is for me to stay as close as I can to raw HTML, CSS, and JavaScript. After all, that’s what users are going to get in their browsers.

My control freakery is not typical. It’s also not a very commercial or pragmatic attitude.

Over the years, I’ve stopped doing front-end development for client projects at work. Partly that’s because I’m pretty slow; it makes more sense to give the work to a better, faster developer. But it’s also because of my aversion to React. Projects came in where usage of React was a foregone conclusion. I wouldn’t work on those projects.

I mention this to point out that you probably shouldn’t adopt my inflexible mistrustful attitude if you want a career in front-end development.

Fortunately for me, front-end development still exists outside of client work. I get to have fun with my own website and with The Session. Heck, they even let me build the occasional hand-crafted website for a Clearleft event. I get to do all that the long, hard stupid way.

Meanwhile in the real world, the abstractions are piling up. Developers can now use large language models to generate code. Sometimes the code is good. Sometimes its not. You should probably check it before using it. But some developers just YOLO it straight to production.

That gives me the heebie-jeebies, but then again, so did npm. Is it really all that different? With npm you dialled up other people’s code directly. With large language models, they first slurp up everyone’s code (like, the whole World Wide Web), run a computationally expensive process of tokenisation, and then give you the bit you need when you need it. In a way, large language model coding tools are like a turbo-charged npm with even more layers of abstraction.

It’s not for me but I absolutely understand why it can work in a pragmatic commercial environment. Like Alice said:

Knitting is the future of coding. Nobody knits because they want a quick or cheap jumper, they knit because they love the craft. This is the future of writing code by hand. You will do it because you find it satisfying but it will be neither the cheapest or quickest way to write software.

But as Dave points out:

And so now we have these “magic words” in our codebases. Spells, essentially. Spells that work sometimes. Spells that we cast with no practical way to measure their effectiveness. They are prayers as much as they are instructions.

I shudder!

But again, this too is nothing new. We’ve all seen those codebases that contain mysterious arcane parts that nobody dares touch. coughWebpackcough. The issue isn’t with the code itself, but with the understanding of the code. If the understanding of the code was in one developer’s head, and that person has since left, the code is dangerous and best left untouched.

This, as you can imagine, is a maintenance nightmare. That’s where I’ve seen the real cost of abstractions. Abstractions often really do speed up production, but you pay the price in maintenance later on. If you want to understand the codebase, you must first understand the abstractions used in the codebase. That’s a lot to document, and let’s face it, documentation is the first casuality of almost every project.

So perhaps my aversion to abstraction in general—and large language models in particular—is because I tend to work on long-term projects. This website and The Session have lifespans measured in decades. For these kinds of projects, maintenance is a top priority.

Large language model coding tools truly are magic.

I don’t like magic.