TL;DR: I propose to add a disabled
attribute to the <a>
element, which would work the same as it already does for the <button>
element. See also http://w3bug.com/28673
Background: Interchangeability
Lemma 1: <button>
and <a>
are largely interchangeable. Visually, <a>
s can be styled into buttons and <button>
s can be styled to look like links. They semantically indicate the opportunity for user interaction (as opposed to e.g. <div>
) and are typically focusable by default. Setting an appropriate click
event handler on a <button>
that sets window.location
approximates an <a>
. Setting an appropriate javascript:
URL on an <a>
, or setting an appropriate click
event handler and either omitting href
or setting a dummy href="#"
approximates a <button>
.
I readily agree that there are many cases where using an <a>
instead of a <button>
, or vice-versa, is suboptimal. Regardless, the fact remains that authors do interchange them for various reasons, some more justified than others. For example:
- "Itâs harder (or perhaps impossible) to get my framework/library/template/etc. to emit a
<button>
here instead of an<a>
" - "I need it to get focused when clicked and thatâs only possible in all environments when using an
<a>
, not when using a<button>
"
Alternatives to adding a disabled
attribute to <a>
So, weâve established that <a>
and <button>
are largely interchangeable. Since the designers of HTML saw fit to give <button>
a disabled
attribute that disables it, let us assume that, for any given button-ish widget, itâs reasonable to want some means to disable it. (See e.g. https://github.com/18F/web-design-standards/issues/493#issuecomment-139652953 . This also would have come in handy at Bootstrap.) Not necessarily the same means (this is HTML after all, king of needless inconsistency), but at least one means for each such widget. We now sadly observe that <a>
doesnât have a disabled
attribute or any other reasonably similar equivalent under some other name. We cry briefly, wipe our tears, pick ourselves up and say: âOkay, no big deal. This is the modern Webâ˘. Surely we can polyfill this?â Well, here are the possibilities I was able to come up with:
pointer-events: none
What if we did something like:
a[disabled] { pointer-events: none; }
a.disabled { pointer-events: none; }
Then the <a>
would no longer receive mouse events, just like a disabled <button>
! Unfortunately:
- This only affects pointing devices, not the keyboard. Users can still
Tab
to the input and emit aclick
on it by pressingEnter
/Spacebar
. And AFAIK, there is nokeyboard-events: none
property to remedy this. - Click events arenât fully suppressed, but rather will target the element behind the
<a>
instead, which could be problematic in some cases. - The
[disabled]
attribute version will break HTML validation. - This doesnât communicate semantic disabledness to users of assistive technology, unless the AT software resorts to using heuristics based on the class name, which can easily fail if the author opts to name the class something other than
disabled
(e.g. to comply with some CSS class naming convention or for namespacing purposes). Authors could use the[aria-disabled="true"]
attribute to remedy this, but this requires additional effort and knowledge on the part of authors, which cannot generally be relied upon.
Global event handler
What about:
document.addEventListener('click', function (e) {
if (this.classList.contains('disabled')) {
e.stopPropagation();
e.preventDefault();
}
}, true);
Downsides:
- Same accessibility concerns as
pointer-events: none
- This must run before any other JavaScript; otherwise, other event listeners could get setup before it.
- Third-party code that wants to ascertain or toggle the disabledness of an
<a>
has to âjust knowâ about this convention somehow. - Are folks still familiar with the Capture Phase these days? (I have no solid data here.)
- Is using the Capture Phase performant? (I have no idea.)
Status quo
At this point, Iâm out of ideas for drop-in polyfill-style solutions. My guess is that folks are currently coping via:
Explicit check in every click event handler
buttonIshElement.addEventListener('click', function (e) {
if (this.tagName === 'A' && this.classList.contains('disabled')) {
return;
}
// ...actual logic here...
}, true);
// ...repeat for ALL CLICK EVENT HANDLERS that could involve an <a>...
This âsolutionâ leaves much to be desired:
- This adds annoying boilerplate to all my event handlers. A decorator would decrease the number of repeated tokens, but thereâd still be some repetition, the extra layer of function call might negatively affect performance, and thereâs the danger of human error in forgetting to apply the decorator to every click handler.
- This doesnât work for event handlers in third-party code.
- Third-party code that wants to ascertain or toggle the disabledness of an
<a>
has to âjust knowâ about this convention somehow. - This doesnât solve the problem of âI have a normal
<a href='http://...'>
that I want to disable.â. You could remove thehref
and store its value into adata-*
attribute, but itâs a bit klunky and merely a convention. - Writing a generic âtoggle the disabledness of a form controlâ function becomes more painful (likewise for âget the current disabledness of a form controlâ):
function toggle(elem) {
if (elem.tagName === 'A') {
elem.classList.toggle('disabled');
} else { // input, button
if (elem.hasAttribute('disabled')) {
elem.removeAttribute('disabled');
} else {
elem.setAttribute('disabled', 'disabled');
}
}
}
And I want a generic function for this because I might switch back and forth between using an <a>
or a <button>
to maintain appropriate semantics as my UI evolves. For example: âYesterday, this was an <a>
that linked to a separate âeditâ page. Today, I made it a <button>
that activates a modal âeditâ dialog. I donât wanna rewrite the code that enables/disables the âeditâ button/link.â
Proposed solution
The proposed solution is to âsimplyâ add a disabled
attribute to <a>
which would have the same semantics as <button>
's existing disabled
attribute. Since itâs built-in, accessibility will also be built-in, third-party event handlers wonât need to be modified, the HTML validator wonât complain, and itâll work correctly for keyboard users. And no event handler boilerplate code or magic global event handler will be required.
Well, what do you think? Is there some existing technique Iâm unaware of that already solves this problem nicely?