Most touch and mobile devices have no easy way to hover and many offer no way at all. How can we solve hiding elements on the page but showing them on hover without requiring a mobile specific fix? How can we tell if a user can hover without JS?
Here’s an example of the requested functionality:
And on hover, our button appears:
The most basic solution might look something like this:
<div class="team-list">
<div class="team-list__header>
<h2 class="team-list__header__heading">Popular Soccer Teams</h2>
<button class="team-list__header__add-new">
...
</button>
</div>
...
</div>
.team-list__header__add-new {
pointer-events: none;
opacity: 0;
}
.team-list__header:hover {
.team-list__header__add-new {
pointer-events: auto;
opacity: 1;
}
}
But does this solve for all our use cases? No.
I’m sure most of us have been asked at one point or another to show content on hover. There are many use cases for this, but I wonder about keeping the integrity of the design without compromising the experience of users who are not able to hover.
Let’s Talk About Progressive Enhancement
Progressive enhancement (PE) is the idea that a website should work as well as it can for all users, while enhancements are added in a way that they do not detract from the lowest common denominator. As defined on CSS-Tricks:
There are a few core concepts of PE. Here’s the list on Wikipedia:
- basic content should be accessible to all web browsers
- basic functionality should be accessible to all web browsers
- sparse, semantic markup contains all content
- enhanced layout is provided by externally linked CSS
- enhanced behavior is provided by unobtrusive, externally linked JavaScript
- end-user web browser preferences are respected
Let’s grab hold of that second bullet point:
basic functionality should be accessible to all web browsers
I’d like to find a solution to our hover problem that allows us to solve for every device. Something that progressively enhances our code to work as design intended without having to worry about browser support limiting our options.
Interaction Media Features
Interaction media features are a way for your CSS to test the types of interaction a device supports and ensure they work exactly like any media query you’re used to.
For this example, we’re most interested in the hover feature. As defined in the W3C docs:
The hover media feature is used to query the user’s ability to hover over elements on the page with the primary pointing device. If a device has multiple pointing devices, the hover media feature must reflect the characteristics of the “primary” pointing device, as determined by the user agent.
This means that we can write a media query to check whether the current device supports hover with its primary pointing device:
@media (hover: none) {…}
@media (hover: hover) {…}
Back to Progressive Enhancement
Web developers love PE because we can confirm, beyond a shadow of a doubt, that we will not lose core functionality from using new features not widely supported in web browsers. As you can see from the Media Queries: interaction media features Can I Use page, the support for our interaction media feature is pretty good but not perfect. So let’s augment our code to allow us to use our hover media feature:
.team-list__header__add-new {
pointer-events: none;
opacity: 0;
}
@media (hover: none) {
.team-list__header__add-new {
pointer-events: auto;
opacity: 1;
}
}
.team-list__header:hover {
.team-list__header__add-new {
pointer-events: auto;
opacity: 1;
}
}
This gets us much closer. We now have a way to test whether our device allows for hover; if it doesn’t, we will show .team-list__header__add-new
as visible by default.
This is a vast improvement over what we were using before, but you may notice we’ve left out an important edge case. What if the mobile device doesn’t allow for hover but also doesn’t support interaction media features?
Here’s where we can use a specific approach to PE. I haven’t heard a term for it, so our team has come up with Fallback First CSS. It’s the concept that your CSS should begin with your fallback and enhance from there:
.team-list__header__add-new {
pointer-events: auto;
opacity: 1;
}
@media (hover: hover) {
.team-list__header__add-new {
pointer-events: none;
opacity: 0;
}
.team-list__header:hover {
.team-list__header__add-new {
pointer-events: auto;
opacity: 1;
}
}
}
By changing our code slightly, we put our Fallback First CSS approach into use. Always showing our .team-list__header__add-new
button, then changing our hover: none
in the previous block to hover: hover
, we can now test for devices that support both our media query and our hover enhancement.
This should ensure any web browsers without hover abilities, or any older browsers without interaction media features, ignore the interaction media query completely and see our button by default.
There you have it, our CSS has gone from 11 to 18 lines and we’ve accounted for all use cases in the process.
DockYard is a digital product agency offering exceptional user experience, design, full stack engineering, web app development, custom software, Ember, Elixir, and Phoenix services, consulting, and training.