Skip to content

Media queries

Media Queries allow us to write CSS that only applies when a specific condition, or conditions, are met.

Media queries look like this:

@media () {
/* regular CSS goes here */
}

Inside the parenthesis, we write the condition that we need to match. For example, if I want to create columns, but only when the viewport is larger than 800px wide, I would do this:

.grid {
display: grid;
gap: 1rem;
}
@media (width > 800px) {
.grid {
grid-template-columns: repeat(3, 1fr);
}
}

The first .grid rule isn’t inside of a media query, so those styles always apply. The grid-template-columns declaration is inside of the media query, though, so it only applies when the viewport is larger than 800px in width.


Using modern CSS, we can also take advantage of nesting so as to not have to repeat our selector a second time.

.grid {
display: grid;
gap: 1rem;
@media (width > 800px) {
grid-template-columns: repeat(3, 1fr);
}
}

Even if you don’t particularly like nesting, in my opinion it’s hard to argue with this approach!

In the example above I’m using the range syntax, which is much newer, but has been supported by all browser engines since March 2023.

Using the range syntax, we can use < and >, as well as >= and <=. We can also chain them together in a single set of parenthesis, like this:

.grid {
display: grid;
gap: 1rem;
@media (500px < width <= 800px) {
grid-template-columns: repeat(3, 1fr);
}
@media (800px < width <= 1200px) {
grid-template-columns: repeat(5, 1fr);
}
@media (1200px < width) {
grid-template-columns: repeat(5, 1fr);
}
}

Personally, I find this a bit hard to wrap my mind around when trying to write it, as well as reading it. Luckily, we also have the and keyword that can be used to set multiple conditions.

.grid {
display: grid;
gap: 1rem;
@media (width > 500px) and (width <= 800px) {
grid-template-columns: repeat(3, 1fr);
}
@media (width > 800px) and (width <= 1200px) {
grid-template-columns: repeat(5, 1fr);
}
@media (width > 1200px) {
grid-template-columns: repeat(5, 1fr);
}
}

I find this much easier, but both are an option… however, we could simplify this, like so:

.grid {
display: grid;
gap: 1rem;
@media (width > 500px) {
grid-template-columns: repeat(3, 1fr);
}
@media (width > 800px) {
grid-template-columns: repeat(5, 1fr);
}
@media (width > 1200px) {
grid-template-columns: repeat(5, 1fr);
}
}

The reason this works is order is still at play, so if two conditions are met and the declarations in them conflict, the later one wins.

Because of this, it’s also very important to add media queries after your “global” declarations.

@media (width > 700px){
.flex-columns {
flex-direction: row;
}
}
.flex-columns {
display: flex;
gap: 1rem;
flex-direction: column;
}

Chances are you’ve seen media queries that don’t use the range syntax, and look like this instead:

.grid {
display: grid;
gap: 1rem;
@media (min-width: 800px) {
grid-template-columns: repeat(3, 1fr);
}
}

Most people aren’t aware of the range syntax, but it’s now baseline widely available, meaning all modern browsers have supported it for over 2.5 years.

Some people love media queries and use them all over the place, and others learn a few intrinsic patterns and feel like we should avoid media queries at all costs.

I think they’re a useful tool, and we should use them when it makes sense.

Media queries are for a lot more than viewport sizes

Section titled “Media queries are for a lot more than viewport sizes”

There are a lot of things you detect using media queries, such as:

  • The viewport size
  • System settings for prefers light or dark themes (prefers-color-scheme: dark)
  • The users input device (hover: hover), (pointer: fine) and a few others
  • The color gamut of their monitor (color-gamut: p3)
  • And many more