14

The cascade is what makes CSS special and powerful. But in the case of media queries, overlap can seem problematic.

Consider the following CSS (continuing rules for CSS media query overlap):

/* Standard - for all screens below 20em */

body { color: black; font-size: 1em; }

/* Query A - slightly wider, mobile viewport */

@media (min-width: 20em) and (max-width: 45em) {
    body { color: red; } /* supposed to be unique for this width */
}

/* Query B - everything else */

@media (min-width: 45em) {
    body { font-size: larger; } /* because viewport is bigger */
}

So when the screen is exactly 45em wide, the overlap at 45em will be treated according to the standard CSS cascade:

  • All max-width: 45em definitions will be applied first,
  • and all min-width: 45em will be applied thereafter.

Consider these two conditions:

  • All text would normally be black, but Query A is unique and has color: red.
  • Since Query B is for larger viewports, it's text has the CSS font-size: larger.

Therefore, at a width of exactly 45em, we'd get big and red text. What would be the best solution to avoid this?


I see two possibilities:

  1. Re-declare the text to have color: black in Query B, but then you're managing two declarations if you choose to change the color in the future. (Of course, not such a problem with this single line of code, but imagine there's a lot of other declarations and selectors.)

  2. Avoid overlap by using pixel values like max-width: 799px and min-width: 800px, but then you're using pixels — I guess they could be 49.9375em and 50em, respectively. Though what if the default is no longer 16em and something gets rounded? And we're still not certain what happens at that gap. (A black hole that breaks the space-time continuum?)

Both have their drawbacks... any other ideas?

Community
  • 1
  • 1
Baumr
  • 6,124
  • 14
  • 37
  • 63
  • 1
    You're over-analyzing: you would never write styles like that. Media queries are typically used to massage the elements for the viewport in question. There's no reason to choose number like 49.9375em as your breakpoint over 49.99999em. – cimmanon Nov 29 '12 at 21:01
  • @cimmanon, I meant that more as a side point (feel free to ignore the weird `em` values, was just doing it to keep the question consistent — I considered writing the whole question in `px` but I prefer `em`) – Baumr Nov 29 '12 at 22:38
  • @Baumr--I think cimmanon's point was that you should use your #2 solution, but set the value to `49.99999em` on the `max-width` so that you should never have a "gap" to deal with (like you might with `49.9375em`). – ScottS Nov 30 '12 at 02:04
  • 1
    Browsers round that off when computing pixel values, so as I understand, at `50em` it'll still apply both. – Baumr Nov 30 '12 at 14:24

2 Answers2

13

The only reliable way to create two mutually exclusive @media blocks for any given media query is to use not to negate it in one of the blocks. Unfortunately, this means repeating your media query once for each @media block. So, instead of this for example:

@media (max-width: 49.9375em) {
    body {
        color: red;
    }
}

@media (min-width: 50em) {
    body {
        font-size: larger;
    }
}

You would have this:

/* 
 * Note: Media Queries 4 still requires 'not' to be followed by a
 * media type (e.g. 'all' or 'screen') for reasons I cannot comprehend.
 */
@media not all and (min-width: 50em) {
    body {
        color: red;
    }
}

@media (min-width: 50em) {
    body {
        font-size: larger;
    }
}

Interactive jsFiddle demo

This is very effective at closing the gap with range media features like width and height since it essentially turns this into an either-or scenario. But, like your first two options, it isn't perfect: as mentioned, you have to repeat the same media query twice, and add not to one of them. There is no if/else construct for @media as described in Conditional Rules 3.


Although I mention this in my answer to your previous question:

From my experiments it would seem Safari on iOS rounds all fractional pixel values to ensure that either one of max-width: 799px and min-width: 800px will match, even if the viewport is really 799.5px (which apparently matches the former).

It should be noted, still, that I've noticed some quirks when it comes to rounding. That said, I haven't been able to find a fractional value that would evade both media queries and end up not receiving styles from either set of rules (which, by the way, is the worst that can happen, so don't worry about potentially creating a space-time rift). That must mean browsers — at least, Safari as I've tested — do a reasonable job of ensuring they satisfy media queries even if you have values that differ (by exactly 1 CSS pixel).

When it comes to units with larger gaps that can be observed on desktop browsers, though, like ems, there is a much larger margin of error. For example, one comment suggests using 49.99999em instead of something more arbitrary than 49.9375em, but apparently there is a difference, at least with a default font size of 16px.

I simplified your code, changed the media queries to use decimal values, and put the code in jsFiddle:

@media (max-width: 49.9375em) {
    body {
        color: red;
    }
}

@media (min-width: 50em) {
    body {
        font-size: larger;
    }
}

If you resize the Result pane to exactly 800 pixels (the text will update to guide you along), you actually end up with different results depending on whether @media (max-width: 49.9375em) is used, or @media (max-width: 49.99999em) is used (I was surprised by this too)...

Either way, you're right: option 2 has its drawbacks too. I'm not particularly fond of it, to be honest, because I wouldn't want to crack my head over device and user agent quirks which are out of my control. If you're like me, I suppose it would be better to go through the inconvenience of redeclaring your rules at the cost (?) of being more vigilant around your code, as that's at least still within your control as an author.

BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
  • Thank you! In your first paragraph, do I understand correctly: it should be fine to do what I've done in my question? Just to clarify. Also, your suggestions of re-declaring rules is an option in simple cases, no doubt, but if these queries get more complex it can be problematic for a large site (not my case at the moment). I am starting to feel that this is a limitation with the `min-width` and `max-width` usage in media queries. Perhaps a declaration like `below-width` and `above-width` might be useful *in addition* to this — allowing to select all values above a certain one, except it. – Baumr Nov 30 '12 at 17:05
  • 1
    @Baumr: Your suggestion of `below-width` and `above-width` gives me an idea, but I'm not sure if it goes with what exactly you're asking. Have a look at the edit once I'm done with it. – BoltClock Nov 30 '12 at 17:16
  • Wow, what a freakin' good answer! I'm considering making another account and up-voting it again ;D Thanks Bolt! – Baumr Nov 30 '12 at 17:43
  • @Baumr: Suspending over something like this would be extremely awkward :) You can always award a bounty instead! – BoltClock Nov 30 '12 at 17:50
  • Any news on this topic? Have browsers begun treating px and em media queries the same yet with regards to zoom? Have any solutions to the overlap problem been found? Thanks! – dalgard Sep 23 '13 at 20:31
  • If you are using CSS Media Queries 4, why bother with negation, and not simply use `width < 50em` and `width >= 50em`? However, if a solution with wide support is required, check [this answer on a duplicate question](https://stackoverflow.com/questions/41449476/media-queries-running-weird-because-of-non-integer-width/60311147#60311147). – Yeti Feb 20 '20 at 00:17
  • @Yeti: I don't remember if those operators were around at the time this was written, and just never got around to updating my answer to mention those. Those would definitely work better here. – BoltClock Mar 10 '20 at 03:19
  • @BoltClock it's worth noting that if the original query has `only screen and ...` for example, using `not screen and ...` in the inverted query doesn't exactly work as intended. It would **always** apply the CSS to every other media type (which is obviously the negation of _"never apply to other media types"_ in the original). We may have to go with `@media only screen and (max-width: 49.99999em)` in such a case. Shame that CSS media queries don't have an `or` operator. – ADTC Aug 01 '22 at 13:34
  • 1
    @ADTC: You can nest at-media rules, or use , to work around the media type limitation. For example, `@media screen { @media not (min-width: 50em) {...} }`, or `` I don't remember if at-media nesting was fully supported the last time I updated this answer, but I know [I updated another answer with this information in 2018](https://stackoverflow.com/questions/11746581/nesting-media-rules-in-css/11747166#11747166). – BoltClock Aug 01 '22 at 13:38
  • Thanks @BoltClock, I learnt something new :) [Support seems reasonable at 96.75%](https://caniuse.com/mdn-css_at-rules_media_nested-queries) – ADTC Aug 01 '22 at 13:44
  • 1
    @ADTC: I stopped being active a couple of years ago as a developer altogether, let alone on Stack, but I'm glad most of my CSS knowledge is still relevant and I can still teach people new things :) – BoltClock Aug 01 '22 at 13:47
  • @BoltClock btw we can't do `@media not (min-width: 50em)` in the second level, since `not` requires a media type. I'm supposing `@media only screen { @media not screen and (min-width: 50em) {` should work since the first level has excluded other types (second `screen` could be `all`). It appears so, based on [this truth table analysis](https://i.stack.imgur.com/8WItp.png). – ADTC Aug 01 '22 at 13:52
  • 1
    @ADTC: [That requirement was removed in Media Queries 4.](https://stackoverflow.com/questions/24455958/why-does-not-some-width-xem-media-query-never-fire/24456052#24456052) – BoltClock Aug 01 '22 at 13:53
1

For me, the best way is to keep a gap of 0.01em:

@media (min-width: 20em) and (max-width: 44.99em) {
    body { color: red; } /* supposed to be unique for this width */
}
@media (min-width: 45em) {
    body { font-size: larger; } /* because viewport is bigger */
}

I recommend you to read this article for the details and the comparison of the different solutions to prevent media query overlapping.

Cheers, Thomas.

tzi
  • 8,719
  • 2
  • 25
  • 45