Skip to main content

Inverted Media Queries and Breakpoints

Posted on Jul 5, 2022

Takes about 3 minutes to read

The occasional breakpoint

Nowadays I lean on modern CSS solutions, fluid layout patterns, and intrinsic sizing over viewport dimension-based media queries – typically referred to as breakpoints – that adapt a design at particular screen sizes. Let's not forget that container queries will soon join our CSS toolset, expanding the exciting universe of parent context styling.

However, I do still find the occasional place to apply adaptive styles. Common example: a "desktop" menu (imagine a horizontal list of navigation items) that converts into its "mobile" counterpart (imagine a hamburger button that toggles a vertically stacked menu's visibility). After building out the foundational styles, I often prefer separating adaptive CSS properties versus having to override or unset them. This means I end up with rulesets that might look like this:

.menu {
/* base styles */
}

/* below 600px */
@media (max-width: 599px) {
.menu {
/* narrow viewport styles */
}
}

/* 600px and above */
@media (min-width: 600px) {
.menu {
/* wide viewport styles */
}
}

Notice the single pixel difference in the two values, 599px and 600px. If these min- and max-width queries shared the same value, then there would be a single pixel overlap where both styles would apply. Not ideal!

Invert the media query

One way around this is to negate the media query. The not keyword in a media query will invert the query's meaning. To let both values in the previous example be the same, I could instead reuse a query and then invert it:

/* below 600px */
@media not all and (min-width: 600px) {
/* "... */
}

/* 600px and above */
@media (min-width: 600px) {
/* "... */
}

The not keyword only seems to apply when first defining a media type and then the media feature as I have above. Something like @media not (min-width: 600px) won't work.

If your current browser window is large enough, you can resize the CodePen result window below to see the text and background color change based on their respective media query declarations:

See the pen (@hexagoncircle) on CodePen.

Future CSS solutions

Level 4 media query range contexts, which are getting closer to full modern browser support, will allow use of the same value:

/* @media (max-width: 599px) becomes */
@media (width < 600px) {
/* ... */
}

/* @media (min-width: 600px) becomes */
@media (width >= 600px) {
/* ... */
}

I really dig that syntax. I personally find it easier to understand and maintain.

Another exciting solution involves custom media queries, which would allow us to store the media feature to a variable:

@custom-media --breakpoint (min-width: 600px);

/* below 600px */
@media not all and (--breakpoint) {
/* ... */
}

/* 600px and above */
@media (--breakpoint) {
/* ... */
}

It seems that this won't be available for a while as it's in a draft of the level 5 media queries spec, but the good news is that there's a PostCSS plugin that give us this power today. 👏

Helpful resources