Avoid hover for touch devices and compact media queries in less

06.08.2014 Bild

TL;DR Summary: Always set a hover class on the html or body tag and use .hover &:hover for all hover styles. Use the power of the & combinator in less.

A couple of times the hover states of elements have been a problem in mobile sites. Sometimes they trigger, sometimes they don't and they are almost never wanted. There is no central switch to turn them off, so that's how they can be set without interference.

 

Code for this post can be found at https://github.com/dynamogold/blog-20140806-hover-media

Standard CSS for :hover

Let's say we have a website with a list. The list elements might have click handlers and as usual a hover style. That's our HTML:

  <!DOCTYPE>
<html><head>
<link rel="stylesheet" href="main.css">
</head><body>
<ul>
<li>1</li><li>2</li><li>3</li><li>4</li>
<li>5</li><li>6</li><li>7</li><li>8</li>
<li>9</li><li>10</li><li>11</li><li>12</li>
<li>13</li><li>14</li><li>15</li><li>16</li>
</ul>
</body></html>

 

Using less for writing CSS we style the content like this:

  ul {
list-style: none;
li {
text-align:center;
padding: 1em;
margin-bottom: 1%;
border: 1px solid black;
background: transparent;
&:hover {
border-color: #f0f;
background: rgba(0, 0, 0, 0.05);
}
}
}

 

To become touch aware we add the following snippet to the end of the HTML body:

  <script>
if (!('ontouchstart' in document.documentElement)) {
document.documentElement.className += ' no-touch hover';
} else {
document.documentElement.className += ' touch no-hover';
}
</script>

Modernizer might be used as well, which currently has a no-touch class and is discussing a hover class. We'll set both for compatiblity and use the hover class for examples.


To avoid hover styles on touch devices we'd need to turn them off, maybe in a seperate stylesheet, that has all touch or mobile related styles:

  .no-hover {
ul li:hover {
border-color: black;
background: transparent;
}
}

Great! No more hover on mobile devices.

More compact element style definition

Now the style for border-color and background is duplicated in two separated blocks. To avoid this we should have hover only enabled on devices, where it makes sense and at the same time make use of & combinator, which doesn't have to be at the beginning of its rule. Instead of all previous styles we replace the first block with:

  ul {
list-style: none;
li {
text-align:center;
padding: 1em;
margin-bottom: 1%;
border: 1px solid black;
background: transparent;
.hover &:hover {
border-color: #f0f;
background: rgba(0, 0, 0, 0.05);
}
}
}

Everything in one place and only defined once instead of having to reset styles on touch devices.

This pattern can be used always, even for pages that aren't mobile yet. Just add a static hover class to the html or body element of the page. Later changes just need to add some javascript to set this class condtionally.

 

Even more inline style definitions

Having columns for the list is as easy as changing the style to:

  ul {
list-style: none;
li {
width: 22%;
text-align:center;
min-height: 2em;
padding: 1em;
margin-left: 0.8%;
margin-bottom: 1%;
display: block;
float: left;
border: 1px solid black;
background: transparent;
&:hover {
border-color: #f0f;
background: rgba(0, 0, 0, 0.05);
}
}
}

 

border-width doesn't allow percentages so will have to add some media queries for a more responsive layout - which we would anyway as four columns don't always fit. So we add this to the responsive stylesheet:

  @media (max-width: 1600px) {
ul li {
width: 21%;
}
}
@media (max-width: 1120px) {
ul li {
width: 29%;
}
}
@media (max-width: 1020px) {
ul li {
width: 28%;
}
}
@media (max-width: 810px) {
ul li {
width: 44%;
}
}
@media (max-width: 710px) {
ul li {
width: 43%;
}
}
@media (max-width: 610px) {
ul li {
width: 100%;
float: none;
}
}

 

Again we are seperating styles for an element and any changes need to be made in several places. Inline and more complex & combinator usage to the rescue:

  ul {
list-style: none;
li {
width: 22%;
text-align:center;
min-height: 2em;
padding: 1em;
margin-left: 0.8%;
margin-bottom: 1%;
display: block;
float: left;
border: 1px solid black;
background: transparent;
.hover &:hover {
border-color: #f0f;
background: rgba(0, 0, 0, 0.05);
}
@media (max-width: 1600px) {
& {
width: 21%;
}
}
@media (max-width: 1120px) {
& {
width: 29%;
}
}
@media (max-width: 1020px) {
& {
width: 28%;
}
}
@media (max-width: 810px) {
& {
width: 44%;
}
}
@media (max-width: 710px) {
& {
width: 43%;
}
}
@media (max-width: 610px) {
& {
width: 100%;
float: none;
}
}
}
}

Everything in one place. Done.