CSS media queries and the weird edge case of decimal place vw values

Published on 17/08/2022

I came across quite the weird edge case today. On a client site, a colleague noticed the header menu being visible alongside the mobile menu icon. That shouldn’t happen.

The menu was set up so a full dropdown menu would be visible on desktop / large screens and was supposed to move into a off-screen menu with a menu icon on mobile / smaller screens. That should be quite the usual scenario, so I was surprised to see that setup not working correctly. I started investigating.

The colleague noticed that this was happening when using the Windows snap-to-half-screen feature. I at first thought that this feature didn’t properly work and that the browser might not recognize it was resized.

Windows window snapping gone wrong?

So I did what everyone with a software bug would do: google the hell out of it.

Until I came across this bug report on a gap in media queries when using window snapping on Windows machines. Sounds familiar?

The mentioned bug report was refreshingly accurate and easy to reproduce:

  1. Use Windows 10/11, with a display of 1920px width
  2. Use 125% scaling, which is the default for this display size
  3. Use a Chromium-based browser, open the affected website
  4. Snap the browser window to one side of the screen
  5. Experience some weird CSS bugs

Here’s what was really going on

The mentioned bug report has a great explanation on the root cause of this:

If you snap a window to one of the sides of the screen, the width of the window without scaling will be 1920px/2=960px. However, with a 1px border on the right side for windows snapped to left (or vice versa), the inner width is 959px. With the 125% scaling, which does not affect the 1px border, the inner width is (959px)/(1.25)=767.2px, which is between 767px and 768px. Thus, in some browsers (tested in Chrome and Edge), snapping the window to the side of the screen on a 1920px-wide display with default settings causes neither media query to apply.

In my case, the menu styles were defined in @media queries like this:

// Define styles for mobile menu
@media ( max-width: 959px ) {}

// Define styles for desktop menu
@media ( min-width: 960px ) {}

That way, some styles would only apply for small screens (up to 959px; mobile), and some others would only apply on large screens (960px onwards; desktop).

By the way, we took this approach in this specific case so that we would have to overwrite less styles. Just going the classic “start small, enhance for bigger screens” with using min-width exclusively would have prevented that whole scenario.

What those definitions actually mean, is this:

// Define styles for mobile menu
@media ( max-width: 959.00px ) {}

// Define styles for desktop menu
@media ( min-width: 960.00px ) {}

See what’s wrong?

The gap between 959.00 and 960.00 is more obvious now.

The 125% scaling along with the exact browser size caused the viewport to be exactly between my definitions of 959px and 960px. The viewport was something like 959.18px.

I was able to reproduce this on my machine using Chrome, browser zoom and some manual resizing.

What’s the fix?

I applied the same fix that the bug report suggested and implemented:

// Old mobile directive
@media ( max-width: 959px ) {}

// New mobile directive
@media not all and ( min-width: 960px ) {}

This is basically a negated command. We’re saying “where min-width of 960px does not apply”.

That’s not the same as saying “only where max-width of 959px applies”. That doesn’t apply to 959.5px.

The negated command does account for decimal place values.

In that sense, it’s a true “smaller than” directive.

What did we learn?

Don’t trust your queries.

Simple fix: use max-width or min-width exclusively.

Is this nice to know though? Absolutely.

Leave a comment