Jon Combe logo

Ctrl-click with HTMX

October 20234 months ago

Since I discovered it in early 2021, I've been a big fan of HTMX.

It doesn't match every use case (what does?) but for the main project I use it on, it radically changed the game in terms of how quickly I could build my product and introduce new features. My app is lightning-fast for the user, too, both in terms of the relatively tiny JavaScript bundle when the initial page loads, and the speed at which page fragments are rendered on screen.

For those who don't know HTMX, here is a quick introduction(not my video).

For a bunch of good reasons, HTMX doesn't replicate the ctrl-click functionality you get with regular <a> tags. In plain HTML, if you click a link, the browser navigates to the given page, but if you hold down the Ctrl key while you click, it opens the given page in a new browser tab instead, leaving the one you are currently using untouched.

I really wanted this functionality in my project, but how to get the click / ctrl-click behaviour working on the same anchor using HTMX? There is the hx-trigger attribute, but as far as I am aware, you cannot set two different behaviours depending on whether a modifier key is being pressed or not.

Luckily for me, my requirements are quite simple. I want to be able to have this working for all HTMX anchors that:

  • are a GET request
  • have a hx-push-url attribute defined (which in my case is all of them)

To achieve this, I have function which defines a custom htmx:configRequestevent handler.

window.onload = function () {
  htmx.on{'htmx:configRequest', (evt) => {
    const requestType = evt.detail.verb;
    const ctrlKey = evt.detail.triggeringEvent?.ctrlKey;
    const pushUrl = evt.target.attributes['hx-push-url']?.value;

    if (requestType === 'get' && ctrlKey && pushUrl) {
      evt.preventDefault();
      window.open(pushUrl, '_blank');
    }
  }
}

The preventDefault call is the key part here. If we have an event that matches our criteria, preventDefault cancels the HTMX request (leaving the current tab untouched), and then the window.open call opens the given URL in a new tab.

This isn't a one-size-fits-all solution: if your requirements are different to mine, you'll need to tweak the above code. I hope this points you in the right direction.