61

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?

Blackbam
  • 17,496
  • 26
  • 97
  • 150
moogal
  • 1,599
  • 2
  • 11
  • 12
  • 4
    Please **don't** use the media query "hover". I'm working with a mouse on my 27" monitor, but all of my browsers have the setting "hover:none" although I like and am able to hover a lot. Maybe all my browser have hover:none because my laptop where my peripheral devices are connected to has a touch screen. Maybe you can try the any-hover media query https://developer.mozilla.org/en-US/docs/Web/CSS/@media/any-hover – Andreas Jul 05 '19 at 21:46
  • @Andreas Make this one an answer so it is prominent enough to be found. I was going crazy because of this. Everyone is talking about hover and neither of my browsers was capable of either detecting my mouse via `pointer:fine` nor accepting hover via `hover:hover`. But this hint was solving it ... – Thomas Urban Feb 26 '21 at 13:36
  • 1
    I've made a website where you can test the setting of your browser referring to this problem https://andreasburg.de/lenovo-browser-hover-check.html – Andreas Mar 23 '21 at 16:22
  • now in at least 2021, you have two keywords for `hover` as mentioned in [here](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/hover) in MDN docs, not sure you were looking for this implementation, but hope someone would find this useful in the future! – Hasintha Abeykoon Oct 06 '21 at 08:45
  • @Andreas could you add tests for the media query "pointer" to your test website? It would be convenient to have tests for the 2 related queries in 1 place. – Joshua Fan Oct 20 '21 at 16:29
  • 2
    Note that Samsung devices have a bug where they report `(hover:hover)` instead of `(hover:none)`! https://www.ctrl.blog/entry/css-media-hover-samsung.html – Daniel Apr 05 '22 at 14:34

7 Answers7

74

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.

artin
  • 1,764
  • 1
  • 17
  • 23
  • 1
    Upvote. Unlike the above, this works without any javascript. It is indeed counterintuitive. – gmdavisUX Jun 06 '17 at 13:25
  • 6
    Great trick! Just note that IE11 appears to ignore this rule altogether. Using another IE-specific hack, it appears the following works (in spite of it being the negated version of the workaround): `@media not all and (hover: none), (-ms-high-contrast: none) {` – Stiggler Jan 22 '18 at 03:58
  • 3
    Neither `@media (hover: none)` nor the negation `@media not all and (hover: none)` do anything in Firefox. That browser doesn't support hover https://bugzilla.mozilla.org/show_bug.cgi?id=1035774 but should there not be a way of writing the `not` such that if the subject of `not` is undefined/unrecognized then it evaluates to false (and the entire media query evaluates to true)? – EoghanM Aug 29 '18 at 15:05
  • @EoghanM did you ever figure that out? I was wondering the same thing. – Jake Wilson Sep 17 '18 at 22:36
  • 3
    `not (hover: none)` is not, or at least no longer, wrong. It's correct, according to Media Queries 4. It just isn't supported by any browser yet, frustratingly. See https://stackoverflow.com/questions/24455958/why-does-not-some-width-xem-media-query-never-fire/24456052#24456052 – BoltClock Oct 22 '18 at 14:35
  • @EoghanM: That's one of the frustrating limitations of `@media` rules. The only workaround is through [using the cascade](https://stackoverflow.com/questions/44244221/is-it-possible-to-do-a-css-supports-check-on-a-media-rule/44248682#44248682). – BoltClock Oct 22 '18 at 14:36
  • @Jake Wilson: See above. – BoltClock Oct 22 '18 at 14:37
  • @JakeWilson I've fleshed out what I finally went with (in terms of negation) in a full answer: https://stackoverflow.com/a/54147825/6691 TLDR: didn't find a way of writing the negation in FF yet. – EoghanM Jan 11 '19 at 13:49
  • 3
    Stupid question, why not using `@media (hover) {...desktop styles...}` ? – basZero Jun 25 '20 at 12:37
  • 2
    Actually I don't get why all are trying to negate the condition which can be achieved straight forward by the keyword `hover` instead of `none`. Maybe it's implemented now but not in the time the question was asked, refer [here](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/hover) for more info! – Hasintha Abeykoon Oct 06 '21 at 08:43
20

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 */
    }
}
Dekel
  • 60,707
  • 10
  • 101
  • 129
  • Thanks but this leaves me in the same situation where browsers not supporting the hover media query don't behave correctly. – moogal Nov 10 '16 at 17:22
  • why not? If they don't support the hove media they will get the background `green` (unless you add a new background color inside the `hover: hover` part). – Dekel Nov 10 '16 at 17:24
  • I'll add it to the answer, 1 minute – Dekel Nov 10 '16 at 17:24
  • I've added an example to the question to try to clarify. The idea would be that a hover state is only declared for browsers which support hover, so that mobile browsers don't try to emulate it. – moogal Nov 10 '16 at 17:33
  • It's the same issue, I'm afraid - Firefox users won't get the hover as in that browser the hover media query will return false. This is why I was trying to do it backwards (i.e. "if not unsupported") – moogal Nov 10 '16 at 17:39
  • Firefox desktop/mobile/other device? – Dekel Nov 10 '16 at 17:40
  • All of them - Mozilla haven't implemented it yet. It'll be the same for IE. – moogal Nov 10 '16 at 17:41
  • 3
    Ok, so this is a bit of a different question. It's like to ask "how can I use css4 tricks with browsers that only support css3?" :) well, mostly you can't. If Firefox ignores the `hover:hover` definition - you don't really have something you can do with it (in pure css). You can use javascript to add class to the `body` element and use css based on that class (is-firefox, is-support-hover, is-mobile, etc...) – Dekel Nov 10 '16 at 17:44
  • You're probably right - I was hoping to avoid having to do that but it might end up having to be done via script. Was hoping to just be able to treat a lack of support as a "false", but looks like that might not be possible. – moogal Nov 10 '16 at 17:47
  • You can always use the media query with specific width/height of mobiles, it should be pretty good... – Dekel Nov 10 '16 at 17:49
  • The hover media queries apparently do not work for some samsung devices. On a galaxy s10, it incorrectly shows the blue background in the first jsfiddle. :( – mls3590712 Jul 04 '19 at 17:16
  • I have noticed that the media query `@media(hover: hover)` does not work on Chrome, however it does on other browsers. – Charklewis Jul 01 '21 at 01:56
11

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.

moogal
  • 1,599
  • 2
  • 11
  • 12
7

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;
    }
}
Fabian von Ellerts
  • 4,763
  • 40
  • 35
1

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;
    }
}
Calsal
  • 1,375
  • 14
  • 25
0

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)

EoghanM
  • 25,161
  • 23
  • 90
  • 123
0

Please don't use the media query "hover". I'm working with a mouse on my 27" monitor, but all of my browsers have the setting "hover:none" although I like and am able to hover a lot. Maybe all my browser have hover:none because my laptop where my peripheral devices are connected to has a touch screen. Maybe you can try the any-hover media query developer mozilla.org/en-US/docs/Web/CSS/@media/any-hover

I've made a website where you can test the setting of your browser referring to this problem andreasburg.de/browser-hover-check.html

Andreas
  • 65
  • 6