Dear Web Devs, Please Use Anchors Instead of Divs for Buttons

Shane DuffyShane Duffy
3 min read

I get it, you're using React or Angular, and you don't need to use \ for your web app's buttons. But do it for us, the keyboard purists.

Since I'd like to eventually do the "digital nomad" thing for awhile, I made it a point to become accustomed to working on a laptop. In other words, becoming a "keyboard purist".

As far as development tools go, there is plenty of keyboard support, naturally. But desktop apps? No such luck. And web apps are even worse.

Enter Vimium, the amazing Chrome extension that makes it possible to traverse the web with nothing but your keyboard! Assuming the website you're on uses anchors for buttons...

My First Userscript

Standard Notes is an awesome open source, cross platform app for note taking. I love the simplicity of it. And I'm constantly switching to it to edit and lookup notes while I'm developing. However, they recently changed some of the buttons to be divs instead of anchors, and thus I have been quickly developing carpal tunnel from having to constantly switch to my mousepad to click around. It is terribly frustrating. I spent some time searching around, and posted on the subreddit about it, but it looks like the devs don't intend to implement keyboard support anytime soon.

Normally, I'd just avoid apps that don't have good keyboard shortcut (or Vimium) support, but in this case I was already a bit too invested into Standard Notes. So, I decided to write a Userscript to change the elements in question to be anchors again:

Vimium Standard

// ==UserScript==
// @name         Vimium Standard
// @namespace    https://shaneduffy.io
// @version      0.1
// @description  Standard Notes changed their UI, and now it doesn't work with Vimium. This makes things work with Vimium again.
// @author       Shane Duffy
// @match        https://app.standardnotes.com
// @icon         https://shaneduffy.io/assets/img/logo-circle-64.png
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    const observer = new MutationObserver(mutationList => {
        var applyChanges = true;

        for (const mutation of mutationList) {
            switch (mutation.type) {
                case 'childList':
                    for (const node of mutation.addedNodes) {
                        var classString;
                        if (node instanceof Element) {
                            classString = node.getAttribute("class");
                            if (classString?.includes("name") || classString?.includes("tag-icon")) {
                                if (node.tagName == "DIV") {
                                    node.remove();
                                }
                            }

                            if (classString.includes("standard-vimium")) {
                                applyChanges = false;
                            }
                        }
                    }
                    for (const node of mutation.removedNodes) {
                        if (node instanceof Element) {
                            classString = node.getAttribute("class");
                            if (classString?.includes("standard-vimium")) {
                                applyChanges = false;
                            }
                        }
                    }
                    break;
                case 'attributes':
                    break;
            }
        }

        if (applyChanges) {
            var notes = document.getElementsByClassName("name");
            for (const note of notes) {
                if (note != undefined && note.tagName != "a") {
                    var newNote = document.createElement("a");
                    newNote.setAttribute("class", "name standard-vimium");
                    newNote.innerHTML = note.innerHTML;

                    if (note.parentNode != undefined) {
                        note.parentNode.replaceChild(newNote, note);
                    }
                }
            }

            var tags = document.getElementsByClassName("tag-icon");
            for (const tag of tags) {
                if (tag != undefined && tag.tagName != "a") {
                    var newTag = document.createElement("a");
                    newTag.setAttribute("class", "tag-icon standard-vimium");
                    newTag.innerHTML = tag.innerHTML;

                    if (tag.parentNode != undefined) {
                        tag.parentNode.replaceChild(newTag, tag);
                    }
                }
            }
        }
    });

    observer.observe(document.body, {
        childList: true,
        attributes: true,
        subtree: true,
        characterData: true
    });
})();

In hindsight, Standard Notes is open source, so I probably could have just done a PR. Oops.

0
Subscribe to my newsletter

Read articles from Shane Duffy directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Shane Duffy
Shane Duffy

I'm a dev @docusign, specializing in .NET. I also have a B.S. in Computational Physics from UCSD, so I'm supposedly good at math (though that remains to be seen). Outside of coding I like reading, motorcycles, violin, anime and urban exploration.