The Boring Internet | Terry Godier
You cannot kill a federated thing by killing one node, the way you can kill a platform by changing one company.
You cannot kill a federated thing by killing one node, the way you can kill a platform by changing one company.
Fixed an issue on iOS and iPadOS where
datalistsuggestions were presented directly over the associated input, obscuring it.
This really gets to the heart of one of the biggest benefits of HTML web components: composability. You can nest your regular markup inside multiple custom elements; something that is can’t do.
The other exciting approach doesn’t exist yet: custom attributes. Again, they’d be a great way of using composability to turbo-charge your existing HTML in all sorts of ways.
Here’s another nice progressive web component for your forms, this time for showing error messages.
Here’s an excellent progressive web component from Aaron—wrap a custom element around your exising form and your good to go:
At its core,
form-saveris a small web component that wraps a form, keeps an eye on it, stores values inlocalStorage, and restores them when the page loads again. Better yet, it clears out saved data after a successful submission so you’re not accidentally resurrecting stale information the next time someone stops by.
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.
The choice isn’t between books and screens. The choice is between intentional design and profitable chaos. Between habitats that cultivate human potential and platforms that extract human attention.
I mean, I would ask for the country first (because not all countries have zip/postal codes), but the point stands…
The datalist element is all fucked up on iOS. Again.
I haven’t “upgraded” my iPhone to iOS 26 and I have no plans to. The whole Liquid Glass thing is literally offputting. So I wouldn’t have known about the latest regression in Safari if a friend hadn’t texted me about the problem.
He was trying to do a search on The Session. He was looking for the tune, The Road To Town. He started typing this into the form on the home page of the site. He got as far as “The Road To”. That’s when the entire input was obscured by a suggestion from the associated datalist.
This is incredibly annoying and seems to be a pattern of behaviour for Safari. Features are supported …technically. But the implementation is so buggy as to be unusable.
I’ll probably have to do some user-agent sniffing, which I hate. And it won’t be enough to just sniff for Safari on iOS 26. Remember that every browser on iOS is just Webkit in a trenchcoat.
Time to file a bug and then wait God knows how long for an update to get rolled out.
Update: I filed a bug, but in the meantime it looks like user-agent sniffing is going to be impossible.
I’m almost certainly preaching to the choir here because I bet you’re reading these very words in a feed reader, but what Molly White has written here is too good not to share:
RSS offers readers and writers a path away from unreliable, manipulative, and hostile platforms and intermediaries. In a media landscape dominated by algorithmic feeds that aim to manipulate and extract, sometimes the most radical thing you can do is choose to read what you want, when you want, without anyone watching over your shoulder.
Substack willingly platforms and allows bad actors to monetize, hate speech and misinformation.
Says who?
Here are some well-reasoned pieces on the subject for you to educate yourself and decide.
This looks like a really interesting proposal for allowing developers more control over styling inputs. Based on the work being done the customisable select element, it starts with a declaration of appearance: base.
Good news for the fediverse, the indie web, and community sites like The Session:
People are abandoning massive platforms in favor of tight-knit groups where trust and shared values flourish and content is at the core. The future of community building is in going back to the basics.
Every UI control you roll yourself is a liability. You have to design it, test it, ship it, document it, debug it, maintain it — the list goes on.
It makes you wonder why we insist on rolling (or styling) our own common UI controls so often. Perhaps we’d be better off asking: What are the fewest amount of components we have to build to deliver value to our users?
It’s great to see the evolution of HTML happening in response to real use-cases—the turbo-charging of the select element just gets better and better!
The Session goes through periods of getting spammed with automated sign-ups. I’m not sure why. It’s not like they do anything with the accounts. They’re just created and then they sit there (until I delete them).
In the past I’ve dealt with them in an ad-hoc way. If the sign-ups were all coming from the same IP addresses, I could block them. If the sign-ups showed some pattern in the usernames or emails, I could use that to block them.
Recently though, there was a spate of sign-ups that didn’t have any patterns, all coming from different IP addresses.
I decided it was time to knuckle down and figure out a way to prevent automated sign-ups.
I knew what I didn’t want to do. I didn’t want to put any obstacles in the way of genuine sign-ups. There’d be no CAPTCHAs or other “prove you’re a human” shite. That’s the airport security model: inconvenience everyone to stop a tiny number of bad actors.
The first step I took was the bare minimum. I added two form fields—called “wheat” and “chaff”—that are randomly generated every time the sign-up form is loaded. There’s a connection between those two fields that I can check on the server.
Here’s how I’m generating the fields in PHP:
$saltstring = 'A string known only to me.';
$wheat = base64_encode(openssl_random_pseudo_bytes(16));
$chaff = password_hash($saltstring.$wheat, PASSWORD_BCRYPT);
See how the fields are generated from a combination of random bytes and a string of characters never revealed on the client? To keep it from goint stale, this string—the salt—includes something related to the current date.
Now when the form is submitted, I can check to see if the relationship holds true:
if (!password_verify($saltstring.$_POST['wheat'], $_POST['chaff'])) {
// Spammer!
}
That’s just the first line of defence. After thinking about it for a while, I came to conclusion that it wasn’t enough to just generate some random form field values; I needed to generate random form field names.
Previously, the names for the form fields were easily-guessable: “username”, “password”, “email”. What I needed to do was generate unique form field names every time the sign-up page was loaded.
First of all, I create a one-time password:
$otp = base64_encode(openssl_random_pseudo_bytes(16));
Now I generate form field names by hashing that random value with known strings (“username”, “password”, “email”) together with a salt string known only to me.
$otp_hashed_for_username = md5($saltstring.'username'.$otp);
$otp_hashed_for_password = md5($saltstring.'password'.$otp);
$otp_hashed_for_email = md5($saltstring.'email'.$otp);
Those are all used for form field names on the client, like this:
<input type="text" name="<?php echo $otp_hashed_for_username; ?>">
<input type="password" name="<?php echo $otp_hashed_for_password; ?>">
<input type="email" name="<?php echo $otp_hashed_for_email; ?>">
(Remember, the name—or the ID—of the form field makes no difference to semantics or accessibility; the accessible name is derived from the associated label element.)
The one-time password also becomes a form field on the client:
<input type="hidden" name="otp" value="<?php echo $otp; ?>">
When the form is submitted, I use the value of that form field along with the salt string to recreate the field names:
$otp_hashed_for_username = md5($saltstring.'username'.$_POST['otp']);
$otp_hashed_for_password = md5($saltstring.'password'.$_POST['otp']);
$otp_hashed_for_email = md5($saltstring.'email'.$_POST['otp']);
If those form fields don’t exist, the sign-up is rejected.
As an added extra, I leave honeypot hidden forms named “username”, “password”, and “email”. If any of those fields are filled out, the sign-up is rejected.
I put that code live and the automated sign-ups stopped straight away.
It’s not entirely foolproof. It would be possible to create an automated sign-up system that grabs the names of the form fields from the sign-up form each time. But this puts enough friction in the way to make automated sign-ups a pain.
You can view source on the sign-up page to see what the form fields are like.
I used the same technique on the contact page to prevent automated spam there too.
The datalist element is good. It was a bit bumpy there for a while, but browser implementations have improved over time. Now it’s by far the simplest and most robust way to create an autocompleting combobox widget.
Hook up an input element with a datalist element using the list and id attributes and you’re done. You can even use a bit of Ajax to dynamically update the option elements inside the datalist in response to the user’s input. The browser takes care of all the interaction. If you try to roll your own combobox implementation, it’s almost certainly going to involve a lot of JavaScript and still probably won’t account for all use cases.
Safari on iOS—and therefore all browsers on iOS—didn’t support datalist for quite a while. But once it finally shipped, it worked really nicely. The options showed up just like automplete suggestions above the keyboard.
But that broke a while back.
The suggestions still appeared, but if you tapped on one of them, nothing happened. The input element didn’t get updated. You had to tap on a little downward arrow inside the input in order to see the list of options.
That was really frustrating for anybody on iOS using The Session. By far the most common task on the site is searching for a tune, something that’s greatly (progressively) enhanced with a dynamically-updating datalist.
I just updated to iOS 18 specifically to see if this bug has been fixed, and it has:
Fixed updating the input value when selecting an
optionfrom adatalistelement.
Hallelujah!
But now there’s some additional behaviour that’s a little weird.
As well as showing the options in the autocomplete list above the keyboard, Safari on iOS—and therefore all browsers on iOS—also pops up the options as a list (as if you had tapped on that downward arrow). If the list is more than a few options long, it completely obscures the input element you’re typing into!
I’m not sure if this is a bug or if it’s the intended behaviour. It feels like a bug, but I don’t know if I should file something.
For now, I’ve updated the datalist elements on The Session to only ever hold three option elements in order to minimise the problem. Seeing as the autosuggest list above the keyboard only ever shows a maximum of three suggestions anyway, this feels like a reasonable compromise.
I’m very glad to see that work has moved away from a separate selectmenu element to instead enhancing the existing select element—I could never see an upgrade path for selectmenu, but now there are plenty of opportunities for progressive enhancement.
Garrett talks through some handy HTML attributes: spellcheck, autofocus, autocapitalize, autocomplete, and autocorrect:
While they feel like small details, when we set these attributes on inputs, we streamline things for visitors while also guiding the browser on when it should just get out of the way.
I had the opportunity to trim some code from The Session recently. That’s always a good feeling.
In this case, it was a progressive enhancement pattern that was no longer needed. Kind of like removing a polyfill.
There are a couple of places on the site where you can input a date. This is exactly what input type="date" is for. But when I was making the interface, the support for this type of input was patchy.
So instead the interface used three select dropdowns: one for days, one for months, and one for years. Then I did a bit of feature detection and if the browser supported input type="date", I replaced the three selects with one date input.
It was a little fiddly but it worked.
Fast forward to today and input type="date" is supported across the board. So I threw away the JavaScript and updated the HTML to use date inputs by default. Nice!
I was discussing date inputs recently when I was talking to students in Amsterdam:
They’re given a PDF inheritance-tax form and told to convert it for the web.
That form included dates. The dates were all in the past so the students wanted to be able to set a max value on the datepicker. Ideally that should be done on the server, but it would be nice if you could easily do it in the browser too.
Wouldn’t it be nice if you could specify past dates like this?
<input type="date" max="today">
Or for future dates:
<input type="date" min="today">
Alas, no such syntactic sugar exists in HTML so we need to use JavaScript.
This seems like an ideal use-case for HTML web components:
Instead of all-singing, all-dancing web components, it feels a lot more elegant to use web components to augment your existing markup with just enough extra behaviour.
In this case, it would be nice to augment an existing input type="date" element. Something like this:
<input-date-past>
<input type="date">
</input-date-past>
Here’s the JavaScript that does the augmentation:
customElements.define('input-date-past', class extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
this.querySelector('input[type="date"]').setAttribute('max', new Date().toISOString().substring(0,10));
}
});
That’s it.
Here’s a CodePen where you can see it in action along with another HTML web component for future dates called, you guessed it, input-date-future.
See the Pen Date input HTML web components by Jeremy Keith (@adactio) on CodePen.