Introduction

Media queries are the backbone of responsive design. We use them to control how a design should change based on the viewport size. But the min-width and max-width syntax can be confusing, and in some cases, cause layout bugs that take time to spot.

This article aims to convince you to use range queries, starting today.

The problem with media queries

In the following demo, we have a typical navigation pattern. On mobile, only the logo and the menu toggle are visible. At larger sizes, the navigation is visible and the menu toggle is hidden.

Take a look.

Logo

HomeAboutContact

😱 Ooops

And here are the media queries used to achieve this.

/* Hide the navigation on small sizes */
.nav {
  @media (max-width: 300px) {
    display: none;
  }
}

/* Hide the toggle on large sizes */
.toggle {
  @media (min-width: 300px) {
    display: none;
  }
}

The problem happens when both media queries meet at the same breakpoint value (300px). Both elements will be hidden at the same time.

max-width is equivalent to <=

min-width is equivalent to >=

In the demo below, increase the container width to 300px and notice how both the menu and the button are hidden at the same time.

Container width is 299px

Logo

HomeAboutContact

😱 Ooops

This is the confusion that happens when using min-width and max-width media queries with the same breakpoint value.

When you are too focused on your work, you might overlook this until you find yourself 45 minutes deep in debugging.

I have been there, and I know how frustrating it can be. To fix the problem, we need two different breakpoint values:

  • max-width: 299px: this means less than or equal to 299px
  • min-width: 300px: this means greater than or equal to 300px
/* Hide the navigation on small sizes */
.nav {
  @media (max-width: 299px) {
    display: none;
  }
}

/* Hide the toggle on large sizes */
.toggle {
  @media (min-width: 300px) {
    display: none;
  }
}

Now when the viewport width is 300px, the navigation will be visible, and the toggle will be hidden.

Container width is 300px

Logo

HomeAboutContact

😱 Ooops

How far can this solution take us? For a single use case like this, it’s okay. However, when we have multiple breakpoints, things start to get complicated.

Meet media query ranges

Media query ranges solve this easily without having to manually offset breakpoints. We can use comparison operators to achieve the same result in a more readable way.

/* Less than or equal to 300px */
.nav {
  @media (width <= 300px) {
    display: none;
  }
}

/* Greater than 300px */
.toggle {
  @media (width > 300px) {
    display: none;
  }
}

This is clearer because you can read the operators and understand the logic rather than trying to guess the min- or max- syntax.

Here is the same demo, but with range media queries.

Container width is 300px

Logo

HomeAboutContact

😱 Ooops

This is part of the Media Queries Level 4 module.

Why range media queries?

Readability

They are easier to read and understand. When looking at the CSS, you can read what it means without guessing. This will lead to easier debugging.

Well supported

If you are using container queries in production, then you should also use media query ranges.

Browser support has been great since March 2023, according to Baseline.

It makes ranges easier

If you want to limit a specific part of the design between two breakpoints, it becomes easier to write.

/* Before */
.section {
  @media (min-width: 300px)  and (max-width: 500px) {
    /* styles for the card */
  }
}

/* After */
.section {
  @media (300px <= width <= 500px) {
    /* styles for the card */
  }
}

In the following demo, see how the min/max prefix values translate to the range syntax.

@media () and ()

@media (300px <= width <= 500px)

Not only for media queries

The range syntax can also be used for container queries. For the page header at the top of the page, I used container queries with the range syntax.

Just swap @media with @container.

h1 {
  @container (width >= 300px) {
    color: #fff;
    background-color: var(--brand-1);
  }

  @container (width >= 500px) {
    background-color: hsl(from var(--brand-1) calc(h + 100) s l);
  }
}

Here is a video of it:

Further reading

You can learn more

Building layouts can be a challenging task, specially if you don’t know the core mental model of CSS layouts. You don’t have to worry about that anymore. I released an interactive CSS layout course, and I called it, The Layout Maestro.

A screenshot of the Layout Maestro course landing page

Check it out here.

Conclusion

Media query ranges are clearer, easier to debug, and well-supported. Give them a try in a project, I’m sure you will find it useful.