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, from Uttarakhand, India. I am a front-end developer, learner, and dev blogger.
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:
<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:
*,
*::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:
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:
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.
/* 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:
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.
Conclusion
Here's an article that explains all this in more detail 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.
Top comments (0)