A very handy community project that documents support for ARIA and native HTML accessibility features in screen readers and browsers.
Thursday, October 22nd, 2020
Wednesday, October 21st, 2020
Accessibility on the web is easy. Accessibility on the web is also hard.
I think it’s one of those 80/20 situations. The most common accessibility problems turn out to be very low-hanging fruit. Take, for example, Holly Tuke’s list of the 5 most annoying website features she faces as a blind person every single day:
- Unlabelled links and buttons
- No image descriptions
- Poor use of headings
- Inaccessible web forms
- Auto-playing audio and video
None of those problems are hard to fix. That’s what I mean when I say that accessibility on the web is easy. As long as you’re providing a logical page structure with sensible headings, associating form fields with labels, and providing alt text for images, you’re at least 80% of the way there (you’re also doing way better than the majority of websites, sadly).
Ah, but that last 20% or so—that’s where things get tricky. Instead of easy-to-follow rules (“Always provide alt text”, “Always label form fields”, “Use sensible heading levels”), you enter an area of uncertainty and doubt where there are no clear answers. Different combinations of screen readers, browsers, and operating systems might yield very different results.
This is the domain of interaction design. Here be dragons. ARIA can help you …but if you overuse its power, it may cause more harm than good.
When I start to feel overwhelmed by this, I find it’s helpful to take a step back. Instead of trying to imagine all the possible permutations of screen readers and browsers, I start with a more straightforward use case: keyboard users. Keyboard users are (usually) a subset of screen reader users.
The pattern that comes up the most is to do with toggling content. I suppose you could categorise this as progressive disclosure, but I’m talking about quite a wide range of patterns:
- menus (including mega menu monstrosities),
- modal dialogs,
In each case, there’s some kind of “trigger” that toggles the appearance of a “target”—some chunk of content.
The first question I ask myself is whether the trigger should be a button or a link (at the very least you can narrow it down to that shortlist—you can discount
spans, and most other elements immediately; use a trigger that’s focusable and interactive by default).
As is so often the case, the answer is “it depends”, but generally you can’t go wrong with a button. It’s an element designed for general-purpose interactivity. It carries the expectation that when it’s activated, something somewhere happens. That’s certainly true in all the examples I’ve listed above.
That said, I think that links can also make sense in certain situations. It’s related to the second question I ask myself: should the target automatically receive focus?
Again, the answer is “it depends”, but here’s the litmus test I give myself: how far away from each other are the trigger and the target?
If the target content is right after the trigger in the DOM, then a button is almost certainly the right element to use for the trigger. And you probably don’t need to automatically focus the target when the trigger is activated: the content already flows nicely.
<button>Trigger Text</button> <div id="target"> <p>Target content.</p> </div>
But if the target is far away from the trigger in the DOM, I often find myself using a good old-fashioned hyperlink with a fragment identifier.
<a href="#target">Trigger Text</a> … <div id="target"> <p>Target content.</p> </div>
The expectation with links (as opposed to buttons) is that you will be taken somewhere. Let’s face it, modal dialogs are like fake web pages so following through on that expectation makes sense in this context.
So I can answer my first two questions:
- “Should the trigger be a link or button?” and
- “Should the target be automatically focused?”
…by answering a different question:
- “How far away from each other are the trigger and the target?”
It’s not a hard and fast rule, but it helps me out when I’m unsure.
tabindex action, and maybe a
Now I can start to think about making sure screen reader users aren’t getting left out. At the very least, I can toggle an
aria-expanded attribute on the trigger that corresponds to whether the target is being shown or not. I can also toggle an
aria-hidden attribute on the target.
When the target isn’t being shown:
- the trigger has
- the target has
When the target is shown:
- the trigger has
- the target has
There’s also an
aria-controls attribute that allows me to explicitly associate the trigger and the target:
<button aria-controls="target">Trigger Text</button> <div id="target"> <p>Target content.</p> </div>
But don’t assume that’s going to help you. As Heydon put it,
Here’s some example code I wrote a while back. And here are some old Codepens I made that use this pattern: one with a button and one with a link. See the difference? In the example with a link, the target automatically receives focus. But in this situation, I’d choose the example with a button because the trigger and target are close to each other in the DOM.
At this point, I’ve probably reached the limits of what can be abstracted into a single trigger/target pattern. Depending on the specific component, there might be much more work to do. If it’s a modal dialog, for example, you’ve got to figure out where to put the focus, how to trap the focus, and figure out where the focus should return to when the modal dialog is closed.
I’ve mostly been talking about websites that have some interactive components. If you’re building a single page app, then pretty much every single interaction needs to be made accessible. Good luck with that. (Pro tip: consider not building a single page app—let the browser do what it has been designed to do.)
Anyway, I hope this little stroll through my thought process is useful. If nothing else, it shows how I attempt to cope with an accessibility landscape that looks daunting and ever-changing. Remember though, the fact that you’re even considering this stuff means you care more than most web developers. And you are not alone. There are smart people out there sharing what they learn. The A11y Project is a great hub for finding resources.
And when it comes to interactive patterns like the trigger/target examples I’ve been talking about, there’s one more question I ask myself: what would Heydon do?
Van11y (for Vanilla-Accessibility) is a collection of accessible scripts for rich interfaces elements, built using progressive enhancement and customisable.
Collusion between three separate services owned by the same company: the Google search engine, the YouTube website, and the Chrome web browser.
Gosh, this kind of information could be really damaging if there were, say, antitrust proceedings initiated.
In the meantime, use Firefox
A devastating deep dive into the hype of blockchain, written by Jesse Frederik and translated by Hannah Kousbroek:
I’ve never seen so much incomprehensible jargon to describe so little. I’ve never seen so much bloated bombast fall so flat on closer inspection. And I’ve never seen so many people searching so hard for a problem to go with their solution.
Tuesday, October 20th, 2020
I’ve been like a dog with a bone the way I’ve been pushing for a declarative option for the Web Share API in the shape of
button type=“share”. It’s been an interesting window into the world of web standards.
The story so far…
- I blogged some half-formed thoughts.
- Then I opened an issue on the spec for the Web Share API.
- Meanwhile I wrote a polyfill, releasing the code and a test.
- I responded to pushback I was getting on the issue I filed.
- I also wrote up an explainer document so everything is gathered in one place.
- Finally I started soliciting feedback from people using the Web Share API in order to gather data.
That’s the situation currently. The general consensus seems to be that it’s probably too soon to be talking about implementation at this stage—the Web Share API itself is still pretty new—but gathering data to inform future work is good.
In planning for the next TPAC meeting (the big web standards gathering), Marcos summarised the situation like this:
Not blocking: but a proposal was made by @adactio to come up with a declarative solution, but at least two implementers have said that now is not the appropriate time to add such a thing to the spec (we need more implementation experience + and also to see how devs use the API) - but it would be great to see a proposal incubated at the WICG.
Like I said, I’m not expecting anything to happen anytime soon, but it would be really good to gather as much data as possible around existing usage of the Web Share API. If you’re using it, or you know anyone who’s using it, please, please, please take a moment to provide a quick description. And if you could help spread the word to get that issue in front of as many devs as possible, I’d be very grateful.
(Many thanks to everyone who’s already contributed to that issue—much appreciated!)
The upside to being a terrible procrastinator is that certain items on my to-do list, like, say, “build a chatbot”, will—given enough time—literally take care of themselves.
I ultimately feel like it has slowly turned into a fad. I got fooled by the trend, and as a by-product became part of the trend itself.
Monday, October 19th, 2020
Continuous partial browser support
That’s not what happened though. Developers used vendor-prefixed properties as though they were stable. Tutorials were published that basically said “Go ahead and use these vendor-prefixed properties and ship it!” There were even tools that would add the prefixes for you so you didn’t have to type them out for yourself.
Browsers weren’t completely blameless either. Long after features were standardised, they would only be supported in their prefixed form. Apple was and is the worst for this. To this day, if you want to use the
clip-path property in your CSS, you’ll need to duplicate your declaration with
-webkit-clip-path if you want to support Safari. It’s been like that for seven years and counting.
Like capitalism, vendor prefixes were one of those ideas that sounded great in theory but ended up being unworkable in practice.
Still, developers need some way to get their hands on experiment features. But we don’t want browsers to ship experimental features without some kind of safety mechanism.
- Developers are able to register for an experimental feature to be enabled on their origin for a fixed period of time measured in months. In exchange, they provide us their email address and agree to give feedback once the experiment ends.
- Usage of these experiments is constrained to remain below Chrome’s deprecation threshold (< 0.5% of all Chrome page loads) by a system which automatically disables the experiment on all origins if this threshold is exceeded.
I think it works pretty well. If you’re really interested in kicking the tyres on an experimental feature, you can opt in to the origin trial. But it’s very clear that you wouldn’t want to ship it to production.
You could ship something that’s behind an origin trial, but you’d have to make sure you’re putting safeguards in place. At the very least, you’d need to do feature detection. You certainly couldn’t use an experimental feature for anything mission critical …but you could use it as an enhancement.
And that is a pretty great way to think about all web features, experimental or otherwise. Don’t assume the feature will be supported. Use feature detection (or
@supports in the case of CSS). Try to use the feature as an enhancement rather than a dependency.
If you treat all browser features as though they’re behind an origin trial, then suddenly the landscape of browser support becomes more navigable. Instead of looking at the support table for something on caniuse.com and thinking, “I wish more browsers supported this feature so that I could use it!”, you can instead think “I’m going to use this feature today, but treat it as an experimental feature.”
You can also do it for well-established features like
geolocation. Instead of assuming that browser support is universal, it doesn’t hurt to take a more defensive approach. Assume nothing. Acknowledge and embrace unpredictability.
The debacle with vendor prefixes shows what happens if we treat experimental features as though they’re stable. So let’s flip that around. Let’s treat stable features as though they’re experimental. If you cultivate that mindset, your websites will be more robust and resilient.
More on battling entropy:
Ever needed to change “just a small thing” on an old page you build years ago? I recently had the pleasure and the simple task of changing some colors in CSS lead to a whole day of me wrangling with old deprecated Grunt tasks and trying to get the build task running.
I like this mindset:
Be boring by default and enhance on the way.
I like this idea for a minimum viable note-taking app:
data:text/html,<body contenteditable style="line-height:1.5;font-size:20px;">
I have added this to bookmarks and now my zero-weight text editor is one keypress away from me. You might also use it as a temporary clipboard to paste text or even pictures.
See also: a minimum viable code editor.
To be blunt, I feel we, the folks who have been involved with designing and developing for the web for a significant period of time–including me as I feel a strong sense of personal responsibility here–are in no small part responsible for it falling far short of its promise.
Sunday, October 18th, 2020
I’d maybe simplify this people problem a bit: the codebase is easy to change, but the incentives within a company are not. And yet it’s the incentives that drive what kind of code gets written — what is acceptable, what needs to get fixed, how people work together. In short, we cannot be expected to fix the code without fixing the organization, too.
A timeline showing the history of non-digital dataviz.
Friday, October 16th, 2020
Thursday, October 15th, 2020
Wednesday, October 14th, 2020
I added a long-overdue enhancement to The Session recently. Here’s the scenario…
You’re on a web page with a comment form. You type your well-considered thoughts into a
textarea field. But then something happens. Maybe you accidentally navigate away from the page or maybe your network connection goes down right when you try to submit the form.
This is a textbook case for storing data locally on the user’s device …at least until it has safely been transmitted to the server. So that’s what I set about doing.
My first decision was choosing how to store the data locally. There are multiple APIs available:
localStorage. It was clear that
sessionStorage wasn’t right for this particular use case: I needed the data to be saved across browser sessions. So it was down to
IndexedDB is the more versatile and powerful—because it’s asynchronous—but
localStorage is nice and straightforward so I decided on that. I’m not sure if that was the right decision though.
Alright, so I’m going to store the contents of a form in
localStorage. It accepts key/value pairs. I’ll make the key the current URL. The value will be the contents of that
textarea. I can store other form fields too. Even though
localStorage technically only stores one value, that value can be a JSON object so in reality you can store multiple values with one key (just remember to parse the JSON when you retrieve it).
Now I know what I’m going to store (the
textarea contents) and how I’m going to store it (
localStorage). The next question is when should I do it?
I could play it safe and store the comment whenever the user presses a key within the
textarea. But that seems like overkill. It would be more efficient to only save when the user leaves the current page for any reason.
Alright then, I’ll use the
unload event. No! Bad Jeremy! If I use that then the browser can’t reliably add the current page to the cache it uses for faster back-forwards navigations. The page life cycle is complicated.
In either case, just adding a listener for the event could screw up the caching of the page for back-forwards navigations. I should only listen for the event if I know that I need to store the contents of the
textarea. And in order to know if the user has interacted with the
textarea, I’m back to listening for key presses again.
But wait a minute! I don’t have to listen for every key press. If the user has typed anything, that’s enough for me. I only need to listen for the first key press in the
addEventListener accepts an object of options. One of those options is called “
once”. If I set that to
true, then the event listener is only fired once.
So I set up a cascade of event listeners. If the user types anything into the
textarea, that fires an event listener (just once) that then adds the event listener for when the page is unloaded—and that’s when the
textarea contents are put into
I’ve abstracted my code into a gist. Here’s what it does:
- Cut the mustard. If this browser doesn’t support
localStorage, bail out.
- Set the
localStoragekey to be the current URL.
- If there’s already an entry for the current URL, update the
textareawith the value in
- Write a function to store the contents of the
localStoragebut don’t call the function yet.
- The first time that a key is pressed inside the
textarea, start listening for the page being unloaded.
- When the page is being unloaded, invoke that function that stores the contents of the
- When the form is submitted, remove the entry in
localStoragefor the current URL.
That last step isn’t something I’m doing on The Session. Instead I’m relying on getting something back from the server to indicate that the form was successfully submitted. If you can do something like that, I’d recommend that instead of listening to the form submission event. After all, something could still go wrong between the form being submitted and the data being received by the server.
Still, this bit of code is better than nothing. Remember, it’s intended as an enhancement. You should be able to drop it into any project and improve the user experience a little bit. Ideally, no one will ever notice it’s there—it’s the kind of enhancement that only kicks in when something goes wrong. A little smidgen of resilient web design. A defensive enhancement.