Media query for devices supporting hover

CssHoverMedia QueriesTouchInteraction Media-Features

Css Problem Overview


I'd like to provide separate behaviour for browsers supporting hover (e.g. desktop browsers) and ones which don't (e.g. touchscreen devices). Specifically I want to declare a hover state on browsers that support it, but not for browsers that don't, so as to avoid having mobile browsers emulate it with extra taps, as this breaks other interactions on the page - by not defining a hover state for those browsers this is avoided.

I've read up on the Interaction Media Queries feature and it looks like it should do the trick. I'd be able to do something like:

@media (hover: none) {
  /* behaviour for touch browsers */
}

According to CanIUse it is available on all the browsers I need to support except IE11 and Firefox.

So I wondered if I could do it the other way around - since the main touch devices all support it, then negate it:

@media not (hover: none) {
  /* behaviour for desktop browsers */
}

However, this doesn't seem to work at all.

Pseudocode example of what I'm trying to do:

.myelement {
  /* some styling */
  /* note: no hover state here */
}
@media(this device supports hover) {
  .myelement:hover {
    /* more styling */
  }
}

So, is there a way to make this work in the way intended, or am I down the wrong track?

Css Solutions


Solution 1 - Css

not should prefix media type (screen, print, all, etc) and not media feature (hover, point, etc).

Wrong:

@media not (hover: none)

Correct:

@media not all and (hover: none)

Yes, its unintuitive and weird. Source (see comments).

So you do not need javascript to alternate result of media query.

Solution 2 - Css

From the specs:

> none
>> Indicates that the primary pointing system can’t hover, or there is no pointing system. Examples include touchscreens and screens that use a drawing stylus.
> Pointing systems that can hover, but for which doing so is inconvenient and not part of the normal way they are used, also match this value.

>>For example, a touchscreen where a long press is treated as hovering would match hover: none.

If your browser (mobile/touch) support long-press to simulate hover, the usage of hover: none will not work. What you can do is just use a default value and override it (with default css precedence):

body {
    background: red;
}

@media (hover: hover) {
  body {
    background: blue;
  }
}

Desktop will have blue background and mobile/touch will have red background

Check the following example:
https://jsfiddle.net/mcy60pvt/1/

To check the long-press option of the mobile you can use this example:
https://jsfiddle.net/mcy60pvt/3/

In the above example the green block has :hover definition for both desktop and mobile, however for desktop the background will change to yellow and for mobile (with long-press) it will change to white.

Here is the css for the last example:

body {
    background: red;
}
div.do-hover {
  border: 1px solid black;
  width: 250px;
  height: 250px;
  background: green;
}
div.do-hover:hover {
  background: white;
}

@media (hover: hover) {
  body {
    background: blue;
  }
  div.do-hover:hover {
    background: yellow;
  }
}

In this example - browsers that don't support :hover will view the hover me box with green background, and while "hover" (touch/long-press) - the background will change to white.

##update As for the added pseudo code:

.myelement {
    /* some styling #1 */
    /* note: no hover state here */
}
@media(hover: hover) {
    .myelement {
        /* some styling that override #1 styling in case this browser suppot the hover*/
    }
    .myelement:hover {
        /* what to do when hover */
    }
}

Solution 3 - Css

Thanks to Dekel's comments I solved this by running the logic in JS and applying a class instead:

e.g.

const canHover = !(matchMedia('(hover: none)').matches);
if(canHover) {
  document.body.classList.add('can-hover');
}

Then in the stylesheet:

.myElement {
  background: blue;
}
.can-hover .myElement:hover {
  background: red;
}

I've tested this on desktop Chrome, Safari and Firefox, and iOS Safari and it works as expected.

Solution 4 - Css

You can use this Sass mixin to style the hover, it will use the recommended substitute :active for touch devices. Works on all modern browsers and IE11.

/**
 Hover styling for all devices and browsers
 */
@mixin hover() {
    @media (hover: none) {
        -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
        &:active {
            @content;
        }
    }

    @media (hover: hover), all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
        &:hover {
            @content;
        }
    }
}

.element {
    @include hover {
         background: red;
    }
}

Solution 5 - Css

According to Artin´s answer we can address only devices that support hover with pure css, with @media not all and (hover: none). It looks weird but it works.

I made a Sass mixin out of this for easier use:

@mixin hover-supported {
    @media not all and (hover: none) {
	    &:hover {
		    @content;
	    }
    }
}

The following would change background-color of .container from red to blue on hover for devices that support hover, no change for touch devices:

.container {
    background-color: red;

    @include hover-supported() {
        background-color: blue;
    }
}

Solution 6 - Css

To support Firefox (see https://bugzilla.mozilla.org/show_bug.cgi?id=1035774 ), you need to potentially write some rules twice. Note, although not specified in the question I've added pointer: coarse on the assumption that the purpose of these rules is to target mobile screens:

/* BEGIN devices that DON'T pinch/zoom */
/* If https://bugzilla.mozilla.org/show_bug.cgi?id=1035774 
is fixed then we can wrap this section in a ...
@media not all and (pointer: coarse) and (hover: none) {
.. and do away with the 'NEGATE' items below */

.myelement {
  /* some styling that you want to be desktop only (style as an anchor on desktop) */
  font-size: 14px;
  text-decoration: underline;
  border: none;
}
/* END devices that DON'T pinch/zoom */

/* BEGIN devices that DO pinch/zoom */   
@media (pointer: coarse) and (hover: none) {
  .myelement {
    /* style as a large button on mobile */
    font-size: inherit;  /* maintain e.g. larger mobile font size */
    text-decoration: none;  /* NEGATE */
    border: 1px solid grey;
  }

  .myelement:hover {
    /* hover-only styling */
  }
}
/* END devices that DO pinch/zoom */

The combination of (pointer: coarse) and (hover: none) should become more useful to target mobile devices as mobile screens become larger and we lose the correlation between pixel dimensions and mobile vs. desktop (already the case when you wish to distinguish between tablet and desktop)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionmoogalView Question on Stackoverflow
Solution 1 - CssartinView Answer on Stackoverflow
Solution 2 - CssDekelView Answer on Stackoverflow
Solution 3 - CssmoogalView Answer on Stackoverflow
Solution 4 - CssFabian von EllertsView Answer on Stackoverflow
Solution 5 - CssCalsalView Answer on Stackoverflow
Solution 6 - CssEoghanMView Answer on Stackoverflow