One media query you didn't know about! + Comparison with similar queries
Tags: css, webdev, mediaquery
Published: 2021-06-16 14:40:28

Do you want to know when the user has an input device that can hover over elements? Want to display the hover state without hovering when there's no hovering device? Have you made an element invisible that shows up on hover, but you are stuck with it on a mobile device?

Do you know what? There's a CSS only solution for this!

Hi guys! I am [Gautam Tiwari](https://linkedin.com/in/gautamtiwari003/), from Uttarakhand, India. I am a front-end developer, learner, and dev blogger.
{% user gautamtiwari %}


## Understanding the problem
I made a card component for my portfolio website (working on it). It shows an image, and the text content shows up on top of it. I gave the text content an opacity of 0. Now the text content becomes visible only when the user either hover over it or focus on it.

I settled with that until I realised that a mobile user would have a poor UX as they can't hover over the element.

I thought there must be a media query for this, so I Googled it and WUOLAH! I find a media query to solve this kind of problem and found three more similar media queries (which I'll explain in a few).

Here's the HTML code for the card component:

```html
 <a
    rel="noopener noreferrer"
    target="_blank"
    href="https://bit.ly/35eecgK"
    class="recentlypublished__post"
>
    <img 
        src="https://bit.ly/2RUHcr6"
        alt="cover photo"
        class="recentlypublished__img"
    />
    <div class="recentlypublished__textbody">
        <p class="recentlypublished__date">May 30, 2021</p>
        <h3 class="recentlypublished__title">
            You are a developer just like me. No?
        </h3>
        <p class="recentlypublished__description">
            In the field of development, you will thrive if you keep
            learning, write good performing clean code, and stay away
            from...
        </p>
        <i
            class="
            fa fa-external-link-alt
            recentlypublished__externallinkicon
            "
        ></i>
    </div>
</a>
```

Here's the CSS code (without the media query), which you can safely ignore as it is just for styling it:
```css
*,
*::before,
*::after {
  box-sizing: border-box;
}

body {
  margin: 2rem;
  line-height: 1.6;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
 Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
}

h1,
h2,
h3,
h4,
h5,
h6,
p {
  margin: 0;
}

img {
  display: block;
  width: 100%;
}

.recentlypublished__post {
  display: block;
  width: 100%;
  min-width: 200px;
  max-width: 600px;
  text-decoration: none;
  color: inherit;
  border-radius: 2rem;
  transition: box-shadow 200ms linear, transform 200ms ease-in-out;
  position: relative;
  color: #fefefe;
}

.recentlypublished__post:hover {
  transform: scale(1.05);
  box-shadow: 0 20px 40px -10px rgba(0, 0, 0, 0.5);
}

.recentlypublished__post:focus {
  outline: 0;
  transform: scale(1.05);
  box-shadow: 0 0px 0 10px rgba(0, 0, 0, 0.19);
}

.recentlypublished__textbody {
  position: absolute;
  top: 0rem;
  left: 0rem;
  padding: 2rem 1.5rem;
  width: 100%;
  height: 100%;
  opacity: 0;
  transition: opacity 80ms linear;
  z-index: -1;
}

.recentlypublished__textbody > * {
  transition: transform 250ms ease-out;
  transform: translateY(2rem);
  text-shadow: 2px 0px 15px #00331c;
}

.recentlypublished__textbody::before,
.recentlypublished__textbody::after {
  content: "";
  position: absolute;
  border-radius: 2rem;
  z-index: -1;
  left: 0;
  width: 100%;
  height: 100%;
}

.recentlypublished__img {
  border-radius: 2rem;
}

.recentlypublished__title, 
.recentlypublished__description {
  line-height: 1;
}

.recentlypublished__description {
  position: absolute;
  bottom: 10%;
  width: 90%;
}

.recentlypublished__externallinkicon {
  position: absolute;
  top: 0;
  right: 0;
  padding: 1rem;
}

.recentlypublished__post:hover > .recentlypublished__textbody,
.recentlypublished__post:focus > .recentlypublished__textbody {
  z-index: 2;
  opacity: 1;
}

.recentlypublished__post:hover > .recentlypublished__textbody::before,
.recentlypublished__post:focus > .recentlypublished__textbody::before {
  top: 0;
  background-image: linear-gradient(
    180deg, 
    #232323cf, 
    #202020bf 20%, 
    transparent 60%
  );
}

.recentlypublished__post:hover > .recentlypublished__textbody::after,
.recentlypublished__post:focus > .recentlypublished__textbody::after {
  bottom: 0;
  background-image: linear-gradient(
    0deg, 
    #232323cf, 
    #202020bf 20%, 
    transparent 60%
  );
}

.recentlypublished__post:hover > .recentlypublished__textbody > *,
.recentlypublished__post:focus > .recentlypublished__textbody > * {
  transform: translateY(0rem);
}
```
We have built a card component that shows text content on the `:hover` and `:focus` state. Cool!

Here's a preview that shows `:hover` and `:focus` state:
![hover and focus demonstration](https://cdn.hashnode.com/res/hashnode/image/upload/v1623593146381/pZaMcDNDX.gif)

### But what happens on a mobile or tablet?
Most mobile or tablets have a primary input of **touch** and have no support for hovering. For those mobile users, it would **always** look like this:

![Mobile Preview](https://cdn.hashnode.com/res/hashnode/image/upload/v1623576551189/UQGcVDVXt.png)

THAT is a bad UX. The user would not know the context of that article or link. 

So, we shall now make the text visible to the user whenever the user has no hovering device.

> **Note:** Users can tap and hold the card to activate the hover state, but they have no idea about that invisible content.

## Introducing the `any-hover` media query
`any-hover` is the media query that checks if **ANY available input mechanism** allows the user to hover over the elements.

If they do allow hovering, then `any-hover: hover` holds true.

If they do **NOT** allow hovering, then `any-hover: none` holds true.

### Using `any-hover: none`
Just add this code at the **end** of the previous CSS code.
```css
/* Just for a responsive behaviour */
@media (max-width: 460px) {
  .recentlypublished__date {
    font-size: calc(0.3rem + 2vw);
  }
  .recentlypublished__title {
    font-size: calc(0.38rem + 2vw);
  }
  .recentlypublished__description {
    font-size: calc(0.2rem + 2vw);
  }
}

/* Just for a responsive behaviour */
@media (max-width: 280px) {
  .recentlypublished__description {
    display: none;
  }
}

/* -----any-hover implementation----- */
/* if any input device does not have
hover capability then: */
@media (any-hover: none) {
  .recentlypublished__textbody {
    opacity: 1;
    z-index: 2;
  }
  .recentlypublished__textbody::before {
    top: 0;
    background-image: linear-gradient(
      180deg,
      #232323cf,
      #202020bf 20%,
      transparent 60%
    );
  }
  .recentlypublished__textbody::after {
    bottom: 0;
    background-image: linear-gradient(
      0deg,
      #232323cf,
      #202020bf 20%,
      transparent 60%
    );
  }
  .recentlypublished__date,
  .recentlypublished__title,
  .recentlypublished__description {
    transform: translateY(0rem);
  }
}

```
Completed Codepen below:

{% codepen https://codepen.io/gautam-tiwari/pen/wvJQKzX default-tab=result %}


We added a media query `any-hover: none` to check if **ANY** of the available input mechanism does not support hovering (e.g. in a mobile phone there is no hover capable input device OR when a laptop has support for touch then `any-hover: none` and `any-hover: hover` both holds true). Then, we display the text content if it's true. 

THAT'S IT!

> **Remember**: In CSS, whatever comes later gets applied (if it has the same specificity). So, if you mess up with the order of media queries, you may get unexpected results.

## BONUS: Three similar queries and their explanation
`hover: hover|none`: Same as `any-hover` but inquiries only for primary input mechanism.

`pointer: fine|coarse|none`: Is the **primary input mechanism** (provided by the browser) a pointing device, and if so, how accurate is it? (Coarse corresponds to a pointing device with limited accuracy)

`any-pointer: fine|coarse|none`: Same as `pointer` but tests for all input devices and holds true if anyone device satisfies the query.

## Table that summarises it all

| property/situation | touch only | cursor | touch + cursor | cursor + touch |
| ------------ | ------------- | ------------- | ------------- | ------------- |
| `hover` | none  | hover | none  | hover |
|`any-hover` | none | hover | hover | hover |
| | | | |
| `pointer` | coarse | fine | coarse | fine |
| `any-pointer` | coarse | fine & coarse | coarse & fine | fine & coarse |

## Not for IE and Opera Mini
Browser support is a huge thing when talking about such features.
In 2021, interaction media features (of which `any-hover` is a part of) has fairly good support among the browsers.

![Browser support screenshot from caniuse dot com](https://cdn.hashnode.com/res/hashnode/image/upload/v1623608365464/ARlYuJ2Uq.png)


## Conclusion
Here's an [article that explains all this in more detail](https://css-tricks.com/interaction-media-features-and-their-potential-for-incorrect-assumptions/) by Patrick H. Lauke. It literally covers almost everything you need to know more about these queries.

Subscribe to my newsletter and get articles like this directly in your inbox every Sunday.

> Keep coding imperfectly. Keep coding experimentally.

Thanks for reading. I hope you learned something.