Loading

Those 1px gaps between media queries can be a problem at non-100% zoom

There's a common responsive design pattern used in many places on the web, including Bootstrap, where both min-width and max-width media queries are used to define the CSS rules that are to be active on either side of a breakpoint.

@media (max-width: 599px) {
  /* rules in here will apply when the viewport width is 599px or less */
  .hide-when-small {
    display: none;
  }
}

@media (min-width: 600px) {
  /* rules in here will apply when the viewport width is 600px or more */
  .hide-when-big {
    display: none;
  }
}

Now if you had two divs, one with each of those classes, you might expect you could reliably hide one when the viewport is small, and one when the viewport is big. You might expect you would never see both divs at the same time.

<div class="hide-when-small">The screen's not small</div>
<div class="hide-when-big">The screen's not big</div>

But there's a problem - what if the viewport with is 599.5px? Then neither rule will take effect, and you'll see both at once which can often cause a big mess when it affects things like responsive navs or the layout for an entire site.

Half a pixel?

You might think "Half a pixel? That's not possible", and for the most part it's not. But if you use Ctrl+ or Ctrl- to change your browser zoom then you'll often end up with non-integer viewport sizes, and that non-integer viewport size can be used by the browser when working out which media queries to apply to the page. At the time of writing, all versions of Firefox (currently 44), Edge, and all versions of IE worth testing will use a decimal viewport width value when working out whether to apply the contents of a media query at a non-100% zoom, and will be tripped up by that code above. So 599.5px can happen, or 599.1px, or many others in that little gap.

Webkit browsers (Safari and Chrome) appear to internally round the viewport width before applying rules in media queries, so they don't have this issues. At least in their latest releases they never use the "599.5px" value in their breakpoint math.

Who changes browser zoom?

Personally I'm not sure, I assume it'd be the minority of web users. But unfortunately people often don't have to change the zoom for it to end up at a non-100% value. On Windows 7 and higher, there is a zoom level used by the operating system for things like text and icons, and on larger screens (1920px wide for example) this will automatically be set to a 125% zoom. Which is good because then the text and icons throughout Windows aren't squintingly small. But IE, Edge and Firefox all inherit this 125% value in their own way and end up applying it as browser zoom, creating the conditions for this bug to appear by default on most Windows machines with decent resolution screens in the past five or six years.

Under these conditions, Edge and IE will explictly start at 125% zoom. Ctrl+0 on these browsers will also take you to the 125% zoom level.

Firefox will say it's at 100%, but Firefox's 100% zoom setting will appear larger than other browsers. If Windows has a 125% zoom, then Firefox will actually display web pages at 125% zoom and merely call it "100%". I like this functionality, as web pages don't become squintingly small, but it also ends up having to deal with decimal viewport widths.

Test it out

Here's a codepen example of the issue

Or if you are on a Windows PC with a 125% zoom in Windows display settings, then open Firefox or Edge and slowly resize the width of this webpage. At one point the top nav on this page goes bananas. I'm just using Bootstrap's default scaffolding.

Avoiding the cause

An easy fix is to only ever use min-width or max-width media queries. This is often a healthy side effect from having a true mobile first approach to writing CSS rules.

Is it worth it?

I doubt many people will see this issue in the wild. So much has to happen at once to expose it.

  • Have used min-width and max-width media queries with a 1px gap
  • Have a non-100% browser zoom
  • Have IE, Edge or Firefox
  • Have resized the browser window and happened to land in a 1px gap at the edge of each breakpoint

Still, it's nice to have it documented.