The Second Hardest Problem In Computer Science

Written by Zackary Frazier, posted on 2024-08-26

  • NEXT.JS
  • TAILWIND

Now of course, we all know, the hardest thing in computer science is getting the god damn footer to stay at the bottom of the screen, but what's the second hardest problem?

You know when you hover over a link on most websites, the link will light up or change colors to indicate that it's clickable. This is fantastic! I love knowing when things are clickable.

As any web developer would know, the link lights up usually (except in cases of JavaScript sorcery) because we're using the :hover selector in CSS.

.my-style {
  color: red;
}

.my-style:hover {
  color: blue;
}

How do you "hover" on a mobile device? You don't.

The Second Hardest Problem

On a mobile devices, as I understand it, the implementation of the :hover pseudo-selector is browser-dependent, but what I've observed is that typically after touching the screen with your finger, it triggers a "hover" event on the item you've touched. However it never triggers an "unhover" event until you touch something else on the screen.

Consequently, the hover effect gets stuck.

This is, frankly, annoying. 😓

It took me about 12 hours to figure out how to resolve this problem in Next.js, so I'm documenting the solution here - for posterity.😀

What Didn't Work

JavaScript

So I tried a couple approaches to handle this. The inuitive one was to manage this with JavaScript.

/** CSS **/
.my-class:hover {
  color: red;
}

/** React **/
const myElement = () => {
    let hoverClass = ''
    if("ontouchstart" in window) {
        hoverClass = 'my-class'
    }
    return (<div className={hoverClass}/>)
}

Intuitively I felt like this should have worked, but every attempt at something like this did not work. I also tried capturing the onTouchStart event and removing the CSS class when the element was touched. That didn't stick either.

This would work in the in-browser dev console however when my code was released to my staging environment and used on an actual mobile device, it didn't stick.

CSS, BetterHover

It was suggested in the GitHub discussion linked at the bottom that the following should work,

/** tailwind.config.js **/
module.exports = {
    theme: {
        extend: {
            screens: {
                'betterhover': {'raw': '(hover: hover)'},
            }
        }
    }
}

/** React **/
const myElement = () => {
        return (
          <div className="betterhover:hover:text-blue"/>
     )
}

With this approach, we use the modern CSS pseudo-selector to try and only apply the hover effect if hover is supported in the browser. For whatever reason, perhaps it's skill issues, or maybe the browser on my phone is a weird case, this did not work either at all. Again, it would work in the dev console in my desktop browser, but would fail in staging when run on my real phone's browser.

What Did Work

Fortunately, thank God, the Tailwind team has worked dilligently to deal with this issue. Adding the following code to your tailwind.config.js file will disable all hover effects on devices that are touch-enabled.

This should resolve the issue in 90% of cases. The only edge-case would be when you have effects that are hover-dependent. However in that scenario, you'd need to refactor out that functionality if you want your site to be mobile-friendly given you cannot hover on a mobile device.

This solution was discovered while digging through this GitHub discussion

module.exports = {
  future: {
    hoverOnlyWhenSupported: true,
  },
  // ...
}