Hawk's By Design

I recently had a request at work that I hadn’t ever really thought about before. My boss wanted our link’s outline, which happens on focus, to not happen if it was a mouse click, or touch. Essentially, he wanted me to determine if the user was using the keyboard for navigation.

Focus Outline Example – RandomAnime.org

After this was brought up to me, I was working on my own site and the focus outline just kept sticking out to me. Finally, when a disguised button’s outline was wonky, I decided that this is actually something I want to do on my sites as well. It’s pretty simple with a little bit of CSS and JS.

Full disclosure, if you are going to take away the default outline on interactable elements, you sure as hell better have some other means of signifying they are focus/click-able.

A Couple Event Listeners (JS)

We can actually keep this pretty bare bones with only three event listeners. I’m even going to write this in vanilla JS, cause why the hell not!

var el = document.querySelector("#Element");
el.addEventListener("keyup", function() {
    // Keyboard User!
}, false);
el.addEventListener("click", function() {
    // Mouse User!
}, false);
el.addEventListener("touchstart", function() {
    // Touch Device User!
}, false);

Keyup, click, and touchstart. Those are the three events that we are looking for to do our styling “toggle”. We could also listen for something like wheel, but since we are defaulting to a mouse user, click and touchstart will do just fine. If you want to add it, feel free!

Now that we have our events, we need to do something with them. We could also clean up some lines by creating a single callback function to handle all of the events. That way, we only have one function that handles the dirty work. Here is what the revised JS looks like:

function determineUserNavigation(e) {
    // DO AWESOME THINGS
}

var el = document.querySelector("#Element");
el.addEventListener("keyup", determineUserNavigation, false);
el.addEventListener("click", determineUserNavigation, false);
el.addEventListener("touchstart", determineUserNavigation, false);

Cool! We have one function to handle it all now. First, we need to figure out what event fired, and we can easily get that out of the “type” key off the passed event object (passed as “e” in our function). After we know that, we are going to add, or remove, a class on the body element which will tell our styles what kind of user navigation is happening.

function determineUserNavigation(e) {
    var m = "remove";
    if(e.type == "click" || e.type == "touchstart") {
        m = "add";
    }
    document.body.classList[m]("is-keyboard-navigation");
}

var el = document.querySelector("#Element");
el.addEventListener("keyup", determineUserNavigation, false);
el.addEventListener("click", determineUserNavigation, false);
el.addEventListener("touchstart", determineUserNavigation, false);

If our event is a click or touchstart, the class will be added to the body; if not, it will be removed. Simple. You could add some extra logic to check if the last exists, but since this is a very simple class toggle, I’m going to leave it be.

The Simple Style Change

Don’t blink, you’ll miss it.

body:not(.is-keyboard-navigation) *:focus {
    outline: 0 none;
}

Simply put, if the body does NOT have the class “is-keyboard-navigation”, remove the outline styling on all elements that are focused. At this point, someone is going to be foaming at the mouth about using the asterisk, or all, selector. Who am I kidding, no one reads these.

If you are concerned, though, you can always call out individual elements (a, button, etc.), or class names. Whatever you want to do, you can put underneath the body umbrella.

If you are curious, the :not() selector in CSS has pretty extensive coverage now. At the time of writing, it has a global support of 96%;

Room For Improvement

There is always room for improvement, right? So here, I’ll list out ways to improve this:

  • Toggle Event Listeners – As it stands, you have three events being listened for at all times. Toggle them based off which one is active, and you knock that down to 1 or 2.
  • Check Before Change – As I said earlier, you can check if the class exists, or not, before making the “change”.
  • More Specific Styling – Instead of using the asterisk, or all selector, call out individual elements or class names
  • Flip Priority – Lastly, you can start by assuming keyboard and change if otherwise, this will make the outline shown by default, instead of hidden by default.

I hope this helps someone out there. If not completely, at least a start.

Thanks for reading.

Some more articles for you

Coding

February 5, 2020

Super Simple PHP File Cache System

Caching an API request with PHP is...actually super simple. Not only is it simple, but it's also a great way to boost performance.

View post

Coding

August 27, 2019

Iframe Lazy Loading – Srcdoc to the rescue?

Iframes can be an expensive resource, especially for videos. Lazy loading iframes isn't as easy as it should be, but maybe the srcdoc attirbute can help.

View post