🧐 Can I use hover on Touch Devices? Here's CSS & JS Hack!

Web dev supports multiple form factors with responsiveness. Let's see how NOT to handle touch devices & have better UX with pointer media queries

🧐 Can I use hover on Touch Devices? Here's CSS & JS Hack!

Current web development revolves around responsiveness regarding supporting multiple form factors.

Though the hardware tech is also pacing itself forward, we can see a wide variety of handheld devices as they are portable and powerful.


Current Challenge

But this brings a considerable dilemma in front of Frontend developers (a.k.a web developers):

How did we NOT handle hover in mobile/handheld/touch-enabled devices?

With Responsive Media Queries and adding touch event handlers. On smaller devices, don't add hover CSS and add touch handlers to see additional content.

And it worked great for the following reasons:

  • Small form factor meant touch
  • Bigger screens always come with mouse support

But nowadays, we have:

  • Huge tablets in both categories of portable & large screen.
    Screens are like desktops, but you can use these like tablets, which means you won't have access to a Mouse or precise control of the pointer.
  • Hover on tablet-y desktops (or desktop-y tablets ?‍)

The good old mouse interactions are no longer available on such devices. Interactions like:

  • hover
  • right-click

Solution

CSS3 has now covered this corner with pointer media queries. It allows you to write CSS for the following values:

  • pointer: coarse
  • pointer: fine
  • pointer: none

Let's take a look at an example use of the above media queries:

<button
  data-title="Get the best offers!"
  data-help="Call 1800-00-00-123456!"
>
  Subscribe
</button>
button {
  color: #333;
  font-size: 1.3rem;
  padding: 2rem;
  background-color: #eee;
  border: 1px solid #ddd;
  position: relative;
  transition: all ease 200ms;
}

@media (pointer: fine) {
  button:hover {
    color: #fff;
    border-color: #000;
    background-color: #333;
  }
  button:hover:after {
    top: 90%;
  	background: #aaa;
    border-radius: 0.25rem;
    content: attr(data-title);
    position: absolute;
    font-size: 0.7rem;
    padding: 0.5rem 0.8rem;
    width: max(100%, 200px);
    max-width: max-content;
  }
}

@media (pointer: coarse) {
  button:after {
    content: attr(data-title);
    display: block;
    font-size: 0.75rem;
  }
  button:hover {
    color: #ddd;
    border-color: #aaa;
    background-color: #999;
  }
}

You can also detect if no pointing is present with the device, like in the following example:

@media (pointer: none) {
  button:before, button:after {
    display: block;
    font-size: 0.75rem;
  }
  button:after {
    content: attr(data-title);
  }
  button:before {
    content: attr(data-help);
  }
}

Hover in JS?

In our Desktop WebApps, we tend to add much interaction on Hover, But if we want the same apps to be usable on Touch devices, our JS-based interactions seem to have been rendered useless.

We usually leave the hover events in place and attach extra handlers like touch-&-hold or similar.

But it is not the best way; we should add hover interactions only when we know that hover is possible, and the same for touch events.

For this, we have a comprehensive solution that allows us to run a CSS media query and see if the operation matches.

Yes, we are talking about window.matchMedia

You can use matchMedia function to see if the window is under any responsive breakpoint or not:

console.log(
  window.matchMedia('(max-width: 768px)')
);
// { matches: false, media: "(max-width: 768px)", ... }

Now we can use a similar matchMedia query to determine the pointer support. For example:

document.addEventListener('DOMContentLoaded', () => {
  const pointerFineSupported = window.matchMedia('(pointer: fine)').matches

  if (pointerFineSupported) {
    document.querySelector('a')?.addEventListener('hover', (e) => {
      // load the linked content optimistically before click
	})
	document.querySelector('image')?.addEventListener('mouseenter', (e) => {
      // show the related caption
	})
    document.querySelector('image')?.addEventListener('mouseleave', (e) => {
      // hide the related caption
	})
  }
});

Similarly, we can attach Touch Events like touchstart and touchend when the pointer: coarse is available

document.addEventListener('DOMContentLoaded', () => {
  const touchSupported = window.matchMedia('(pointer: coarse)').matches

  if (touchSupported) {
    document.querySelector('image')?.addEventListener('touchstart', (e) => {
      // show the related caption
	})
    document.querySelector('image')?.addEventListener('touchend', (e) => {
      // hide the related caption
	})
  }
});

Let's use the above JS snippet and make UX improvements where we have used the pointer media queries to optimize the page navigation.

The prime difference between the two is:

const pointerFineSupported = window.matchMedia('(pointer: fine)').matches;

if (pointerFineSupported) {
  document.querySelectorAll('[data-prefetch]').forEach(el => {
	el.addEventListener('mouseenter', (e) => {
	  const routeName = el.getAttribute('href').replace('#', '');
	  !routes[routeName].content && fetchTemplateForRoute(routes[routeName]);
	})
  })
}

Repo: https://github.com/time2hack/pointer-events
Demo without optimization: https://time2hack.github.io/pointer-events/
Demo with optimization: https://time2hack.github.io/pointer-events/index2.html


In my portfolio website, I have also used the pointer media queries in CSS to show some information visible on hover if you use a desktop with a mouse to visit it.

Here is the rough outline of the Styled Component for the card-handing pointer media query:

import styled from "styled-components";

export const ImageContainer = styled.div`
  ...
  & img { ... }
`;

export const Description = styled.div` ... `;

export const Article = styled.article`
  ...

  ${ImageContainer} { ... }

  &:hover {
    & ${Description} { ... }
  }

  @media (pointer: coarse) {
    ...

    ${Description} { ... }
  }
`;

You can see the entire file here: https://github.com/pankajpatel/pankajpatel.github.io/blob/master/components%2Fstyled%2FArticle.ts


Conclusion

Pointer media queries can help you tailor the UX for devices that have capabilities, like

  • Prefetch content on hover for the mouse
  • Slide interaction for list items on touch devices
  • etc
What are you going to optimize?

Let me know through comments. or on Twitter at @heypankaj_ and/or @time2hack

If you find this article helpful, please share it with others.

Subscribe to the blog to receive new posts right to your inbox.