Skip to main content Accessibility Feedback

Progressively enhancing a link into a button with vanilla JavaScript

In yesterday’s article on loading HTML from another page, I talked about progressively enhancing a link into a button.

Let’s imagine you have a dialog modal that loads some HTML. You might keep all that HTML in a separate file, and by default display a link to it.

<a href="/terms">Read the Terms of Service</a>

When your JS loads, you can progressively enhance it into a modal toggle.

let toggle = document.querySelector('[href="/terms"]');
toggle.setAttribute('role', 'button');
toggle.addEventListener('click', handleModal);
<!-- After the JS loads -->
<a role="button" href="/terms">Read the Terms of Service</a>

Buttons and links do different things. They have different semantic connotations, and different interaction patterns with the keyboard.

My click event will also detect enter/return key presses, but not spacebar presses like a button normally would. For that, I would need to add a keydown event listener, and filter out keys that weren’t the spacebar.

The [role="button"] attribute tells screen readers “this is a button, not a link,” but doesn’t make the link behave exactly like a button.

As a few folks on Mastodon pointed out, an easier and more resilient approach might be to replace the link with a button entirely.

To do that, I would use the document.createElement() method to create a button element. Then I’d copy the textContent and className properties from the original link over to it.

let link = document.querySelector('[href="/terms"]');
let btn = document.createElement('button');
btn.textContent = link.textContent;
btn.className = link.className;

There are a lot of ways you can add and remove elements in the DOM, but for what we’re trying to do, the Element.replaceWith() method is the easiest and most straightforward.

let link = document.querySelector('[href="/terms"]');
let btn = document.createElement('button');
btn.textContent = link.textContent;
btn.className = link.className;
link.replaceWith(btn);

Now, I can use just my click event listener, and automatically detect spacebar presses, enter/return key presses, clicks, and taps.