Talk notes: Let’s make a simpler, more accessible web
I am just on my way back home from presenting at the Typo3 Developer Days in Karlsruhe, Germany. I had a great time and met a lot of interesting people. I also had the opportunity to present my talk on making the web simpler. The talk was well received and there were some requests to share the slides. So here is a write-up of what I talked about:
An annoying, broken web
I traveled to the event by train and used the free Wifi services offered on German trains called WIFIonICE, which always sparks a stupid image in my head of an ice skating Wifi signal, but that’s not what I wanted to talk about.
I wanted to talk about the web still being a bad experience on patchy connections. It isn’t that things don’t load or show up. It was, for example, not a problem to read through my feeds using Feedly, check the conference web site or this blog. The problems I had were all on web applications that try to give me a native experience by loading and replacing content in-app.
I tried, for example, to buy a plane ticket and all I got was endless loading screens and ghost screens promising me interaction but failing to deliver. Instead, I looked at “please wait” interaction patterns that left me wondering if I already bought tickets or not. The promise of app-like convenience turned into frustration. Switching from my laptop to my phone also didn’t help as the native app also loads the same, flakily designed web solution.
The web is built on resilient technologies – we just don’t use them
The weird thing is that the web should not fail that easily. It is built using HTML to structure things and CSS to apply visuals. Both of these technologies have highly forgiving parsers. If I nest HTML wrongly it will show one element after the other. If I write elements that don’t exist, browsers show them much like they would do with a DIV. If I have a syntax error in CSS, browsers go to the next line and keep trying there. If a browser doesn’t support CSS I use, it doesn’t apply it – but it also doesn’t stop rendering.
Instead of building our products on these technologies, we create everything with JavaScript. A highly powerful technology, but also a brittle one. Of the things we use to build web apps, JavaScript is the one that throws in the towel and stops executing at the first syntax problem or inability to access and alter elements we tell it to. So why do we rely on it that much?
There are a few reasons. The first one is that JavaScript puts us into the driver’s seat. We control everything it does and write the instructions how to show and alter content. With CSS and HTML we need to rely on the browser to do things right and we don’t get a way to validate the success or find out what went wrong. With JavaScript, we have a full debugging environment and we can even halt and continue execution to inspect the current state. This gives us a feeling of control the other technologies don’t give us. And not having full control can feel strange.
Another thing JavaScript allows us to do is to use upcoming, standardised features of the web right now by simulating them in other ways. And as we are impatient or want to constantly match what other platforms offer, we keep doing that. We keep simulating interaction patterns or UI elements foreign to the web in less stable ways rather than waiting for the platform to provide them.
As JavaScript allows us to generate content or alter the look and feel in a programatic manner it gives us a feeling of more control. For example, many developers want to control the frames per seconds of animations instead of defining animations in CSS and letting the browser do the job for them. As you can generate HTML with JavaScript and alter styles it feels to some that learning HTML, CSS and JavaScript is not necessary when you can do it all with one technology.
The other reason people rely on JavaScript is that we used to fix faulty standard implementations in browsers with it. Internet Explorer was, of course, the biggest culprit there, but even now you often find patch code for Safari and other browsers. One of the biggest selling points of abstractions like jQuery was that it made browser support predictable. Often new standards go through a few rounds of ropey integration and many a “using X considered harmful” blog post has not helped inspiring trust in the platform for developers. Instead they know that by using JavaScript, they can make things happen without worrying about browser versions.
Remembering Unobtrusive JavaScript
Twenty years ago I published a course called Unobtrusive JavaScript. In it, I shared my experience moving from browser-specific script and markup in the DHTML days to web standards based development. Specifically I explained how we stopped hacking together web content display and moved on to separation of concerns, saving lots of not needed code in the process. Instead of using tables and font elements, we had semantic HTML and CSS.
I then proceeded to explain how to stop mixing HTML and JavaScript and how to not rely on the latter as it could fail in many different ways. I put it this way:
Javascript is an enhancement, not a secure functionality. We only use Javascript to enhance a functionality that is already given, we don’t rely on it. Javascript can be turned off or filtered out by proxies or firewalls of security aware companies. We can never take it for granted. This does not mean we cannot use Javascript, it only means we add it as an option rather than a requirement.
This has not changed. JavaScript still is an unreliable technology and yet the apps that frustrated me on my journey made the mistake of relying on it without providing a more stable fallback solution.
The concept of unobtrusive JavaScript is closely related to that of progressive enhancement. Another sensible idea that came out of fashion. Instead of enhancing working solutions to become better when and if certain criteria are met, we build solutions hoping that everything will be fine.
Instead of using JavaScript and things non-web-standard sparingly, we go full in. And by doing so we made Web Development much harder and complex than it needs to be.
Starting a new web project now
Back in the days, you started a web project with an index.html file and built on that. These days, things are different. You need to:
- Get the right editor with all the right extensions
- Set up your terminal with the right font and all the cool dotfiles
- Install framework flügelhorn.js with bundler wolperdinger.io
- Go to the terminal and run packagestuff –g install
- Look at all the fun warning messages and update dependencies
- Doesn’t work? Go SUDO, all the cool kids are …
- Don’t bother with the size of the modules folder
- Learn the abstraction windfarm.css – it does make you so much more effective
- Use the templating language funsocks – it is much smaller than HTML
- Check out the amazing hello world example an hour later…
Of course, this is glib, but there is a lot of truth to it.
A simpler web for developers…
I am constantly amazed how hard we made it for people to get started as web developers by advocating complex build processes and development tool chains. If you look at the state of the web though, we live in exciting times as developers and maybe many of these abstractions are not needed.
- Browsers are constantly updated.
- The web standardisation process is much faster than it used to be.
- We don’t all need to build the next killer app. Many a framework promises scaling to infinity and only a few of us will ever need that.
- Our goal should not be to optimise our developer experience.
- Our goal should be satisfied visitors using working, resilient products.
The current focus on making front end work highly architected frustrates seasoned developers and deters new ones. People use low code or non-code environments to build products instead of using web technologies. My current company uses one of these tools to build our marketing pages and the resulting product is pretty and well maintained, but also bloated and much more complex than it needs to.
A simpler web for users…
We shouldn’t deliver complex solutions because they are easier to maintain or develop. What ends up on our user’s devices is what’s important, and how easy it is to consume, regardless of setup, connectivity or – most importantly – physical ability.
Browsers are damn good at optimising the user experience. The reason is that browser makers are measured by how speedy the browser is and how resource hungry it gets. But it can only do so much. If we, for example, animate things in JavaScript instead of allowing the CSS engine to optimise animations under the hood, we miss out on some really convenient browser behaviour.
Operating systems allow users to customise the experience to their needs. People can use light or dark mode, quite a few people need to turn off animations as it would distract them and others even use their systems in high contrast modes.
Users spend a lot of time doing customising their operating systems and devices. We should always value that effort and build on top of it. And, our solutions should build on existing interaction patterns like loading pages and going back in the browser history instead of building a UI we need to explain to people.
So, how do we make a simpler web for developers and end users alike?
Optimise what you control
What our users end up getting is in your control. We own the server and basically the whole experience until it is delivered to the end user. From there on, it is theirs to customise the look and feel to their needs, translate our content in other languages, block certain content and many other things. But until then, we can do quite a few things to ensure a great basic experience:
- Send lots of semantic HTML – it can not break.
- Use newest server setups – servers can help auto-optimise a lot of things.
- Use the best formats – WebP, Avif, PNG and others. We should optimise them before integration or on the fly via a CDN service.
- Pick our servers where our users are – long travel through cables is still slow.
- If we don’t understand what that great helper library does or if we only use 10% of what it does, we should not use it.
Cache and offer offline content
Using Service Workers we can prevent our users from having to load content over and over again. This helps them get a snappier experience, and lowers our traffic bills. MDN has a great in-depth guide on Offline and Background Operation of web apps. This was hit and miss for a long time, but pretty solid to use now across browsers and devices. In any case, these are solutions designed to enhance, not to rely on.
Remove old band aids
There was a longer period in web development where browsers innovated faster than the standard bodies and other browsers refused to die. During that time we relied on polyfills and libraries to even the playing field and empower us to concentrate on developing our products rather than fixing cross-browser issues. These are a thing of the past now and the helper library of yesterday is the security, performance or maintenance issue of today. So let’s do some spring cleaning:
- If it fixes things that aren’t an issue anymore – bin it.
- If it makes things more convenient but has a web platform native equivalent – bin it.
- If it is only used in one interaction in a part of your app – load it on demand, don’t bundle it upfront.
Outdated versions of jQuery and the likes and plugins that automatically bundled and minified CSS and JavaScript are always showing up on top of attack reports. Don’t let an old helper become the door opener for evildoers in your products.
Don’t take on any responsibility you shouldn’t take on…
There is a fabulous saying: “not my circus, not my monkeys”. By leaving some things in the control of the browser and even more things for the end user to change to their needs, we give up control, but also responsibility. Some things you don’t want to be responsible for and browsers are great at are:
- Keeping the history state
- Allow for interception and reload
- Telling you when a connection fails
- Caching and preloading things
- Allowing for bookmarking and sharing
A really interesting new(ish) concept you can take a peek at right now are View transitions. For example, this video shows a seemingly in-app experience, but if you look at the URL bar you see that this is moving from document to document, thus allowing for history navigation, bookmarking and sharing. What the browser stopped doing is wipe the slate clean every time you go to another page.
Only deliver what is needed…
This is a big one. As we work on fast computers on fat connections, we tend to get overly excited about using packages and resources and bundle them in our products. We might need them later anyway, so why not have them delivered and cached? Well, because they clog up the internet and can cause a massive amount of unnecessary traffic.
Take is-number for example. This npm package had almost 68 million downloads this week and 2711 other packages depend on it. If you look at the source, it is this:
module.exports = function(num) { if (typeof num === 'number') { return num - num === 0; } if (typeof num === 'string' && num.trim() !== '') { return Number.isFinite ? Number.isFinite(+num) : isFinite(+num); } return false; }; |
One of the dependents is is-odd which returns if a number is odd.
This one requires is-number, checks things and throws errors if they fail, but eventually boils down to returning if the number’s 2 modulo result is 1.
const isNumber = require('is-number'); module.exports = function isOdd(value) { const n = Math.abs(value); if (!isNumber(n)) { throw new TypeError('expected a number'); } if (!Number.isInteger(n)) { throw new Error('expected an integer'); } if (!Number.isSafeInteger(n)) { throw new Error('value exceeds maximum safe integer'); } return (n % 2) === 1; }; |
module.exports = function isOdd(value) {
const n = Math.abs(value);
if (!isNumber(n)) {
throw new TypeError(‘expected a number’);
}
if (!Number.isInteger(n)) {
throw new Error(‘expected an integer’);
}
if (!Number.isSafeInteger(n)) {
throw new Error(‘value exceeds maximum safe integer’);
}
return (n % 2) === 1;
};
The `is-odd` package has 290k weekly downloads and 120 dependent packages. One of those is `is-even`:
var isOdd = require('is-odd'); module.exports = function isEven(i) { return !isOdd(i); }; |
module.exports = function isEven(i) {
return !isOdd(i);
};
From a package user point of view, this makes sense. But over time, dependencies can add up to a lot of data on the web for simple problems.
Other packages are even empty, like the – package-. This one has 48k weekly downloads and 382 dependencies though. The reason is most likely typos as people using `npm i – g package` instead of `npm i -g package` do install the package with the name `-` instead.
Luckily, this package does nothing, but it would be a great one for malware creators to take over, considering how many people unwittingly use it.
Andrey Akinshin showed the impact of the `package first` thinking on Twitter. By removing the `is-number` package and replacing it with its code, a product he worked on now saves 440GB traffic every week.
Let’s think about this when we build our products. For example, by using Media Queries in our link elements we only load the colour scheme CSS that is needed:
Users with a dark scheme never load CSS they don’t need. Same applies for those who have not set a preference or use a light theme. This is not hard to implement, but can have a huge impact.
Another important thing to remember is not to use resources like images in classes that get re-used. Currently we are investigating why the traffic on our site is causing tons of data being transferred and we found that a photo of mine is used as a background image in a CSS class that isn’t only used on the page the photo is shown, but all over the product. This causes the 400K image to be loaded 235k times, which means 92GB traffic per month.
Mind you, this doesn’t mean that people see it, it is simply requested every time the CSS class was applied as background images are not loaded on demand. Most likely someone in the low-code environment thought in the past that using it as a background image gives them more flexibility, but with object-fit, images inside our HTML are as flexible.
Even better, using the loading HTML attribute set to `lazy`, we can ensure that browsers only load images when and if they can be displayed and not cause unnecessary traffic beforehand.
The same applies to scripts, by using the defer attribute, we can make sure our scripts get loaded after the document has shown, thus not delaying things.
Keep up to date
The interesting thing about recent innovations on the web platform is that they are all about giving up control and moving away from pixel perfect layouts. Instead, CSS especially is embracing the concept of the web being an international platform and development not only happening on a page, but also on a component level. One could say that CSS is giving up control:
- From fixed container sizes to flexbox/grid growing and shrinking with the content
- From pixels to percentages to em/rem to viewport sizing to container sizing
- From setting width and height to auto to aspect ratio
- From box model definitions to independence of writing order
CSS and JavaScript also intersect, which means you can get some information via scripting and hand it over to the CSS engine for display.
- You can read and write CSS custom properties (“variables”) in JS – thus give, for example, the mouse position to CSS.
- CSS animations and transitions fire events for JS to use – and are part of the browser rendering.
- Media Queries can be used in both languages to detect if a user has dark mode, doesn’t want to see animations, uses a touch device…
On the HTML front, we also have a lot of great features that got promoted from a “nice in the future” to “safe to use now”.
Just consider how much you can achieve with form element attributes. In this form demo codepen you can try out the effects of the following HTML:
You can not send the form without entering a CMS name. You can not send any CMS name that isn’t a word followed by a number. And when you submit the form with a valid value you get an overlay message telling you that it is excellent. This is using native HTML form validation and the Popover API. Another thing to check out is the new(ish) Dialog element, which offers even more overlay options.
Another useful element group is details/summary. This allows you to create parts of the page that are hidden and can be shown by activating an arrow. All without JavaScript. On Chromium browsers you can even use Ctrl|CMD + F to search in the page and the browser will automatically expand sections that match. I’ve used this lately to build a browsing interface for the video metadata of all the talks at our conference and you can see it in action in the following video:
One thing that seems to become fashionable is that people add their own dark/light switches to web apps. This feels like back in the days when we built font resizing widgets for Internet Explorer users. As people use their devices in dark or light mode it makes a lot more sense to read out this setting and automatically change your design accordingly. You can do that with media queries, but there is even a handy new(ish) light-dark colour function in CSS to achieve the same functionality. Check out this Codepen to see it in action.
Forget browser delays having an impact
In general, there used to be a time where the argument of “browser X is still around” was a valid one when not embracing the web platform and – more importantly – taking on new functionality and taking part in the discussions around it. Browser makers are accessible to us and desperate for feedback. New browser versions come out all the time and even those tied to OS updates are picking up the pace. What’s “not supported right now” is often ready by the time your products is shipped. So let’s not hold back.
Spend more time testing, less time trying to invent the web
Browser developer tools do not only give you insights into your JavaScript, Network activity and DOM but are also chock-full of accessibility testing features. Instead of trying to shoe-horn the coolest, newest framework into your product, spending some time using these very early on in the process will pay massive dividends later.
Things we should aim for
In conclusion, here are some things we should aim for.
- Control over what ends up in our apps – code reviews should include dependencies.
- Contribution back to the community – let’s propose more web platform features to standards bodies and browser makers.
- Being diligent in what we use for what purpose – no need to add the kitchen sink every single time
- Finding joy in keeping things simple and using the platform…
Thanks!