Sass and Media Queries: What You Can and Can’t Do

by on 15th March 2012

screenshot

Preprocessors like Sass are helping us flex our development muscles in nearly every area of our CSS. Variables, mixins, inheritance and many more great features make coding easier and more concise than ever.

So what about leveraging Sass for responsive design, or more specifically, for media queries? Are there any features of Sass that pair particularly well with media queries? Is there anything you should avoid? Join me as I experiment and discover the answers.

How Do Basic Sass Features Work With Media Queries?

I’ve done a lot of experimentation lately with Sass and media queries to see what works and what doesn’t. The results are fairly mixed: some things work and others don’t. Overall, there’s really nothing impressive enough to completely change the way you use media queries, but it’s still worth it to go through a few examples that illustrate how the two work together.

Variables Inside of a Media Query: Yes

Our first test examines whether or not a variable set in Sass carries over to the body of a media query. The question is one of scope: Can I set a variable outside of a media query and then use it within a media query?

/*Example 1 Sass*/
$fontsize: 10px;

@media only screen and (max-width : 300px){
  .someclass {font-size: $fontsize;}
}

It turns out that this works just fine. Our generic fontsize variable effectively set the font-size to 10px inside of the media query.

/*Example 1 Compiled CSS*/
@media only screen and (max-width : 300px){
  .someclass {font-size: 10px;}
}

Variables As Breakpoints: No

My next question was whether or not I could use variables to set my breakpoints. I’m not sure that this would ever be a good idea but more basic than that question was whether or not it’s even possible. What happens when I try to do this?

/*Example 2 Sass*/
$breakpoint: 300px;

@media only screen and (max-width : $breakpoint){
  .someclass {font-size: 10px;}
}

This is apparently not a valid use of Sass variables as this code throws an error and will not compile. I tried the same thing in LESS and received equally useless results. This time it compiled without throwing an error but the value wasn’t carried over properly.

Extending Inside of a Media Query: Sort Of…

Extending works with media queries, but it’s important to know how it works because the results might not be what you think.

Our first example uses @extend like it’s meant to be used. Here I told the errorTwo class to inherit the styles of the errorOne class and set the color to blue.

/*Example 3 Sass*/
@media only screen and (max-width : 300px){  
  .errorOne {
  font-size: 10px;
  font-weight: 400;
  }
  
  .errorTwo {
    @extend .errorOne;
    color: blue;
  }
}

Predictably, this produces exactly the result that we want. The @extend doesn’t care that it’s inside a media query, it works the same as it does anywhere else.

/*Example 3 Compiled CSS*/
@media only screen and (max-width : 300px){  
  .errorOne, errorTwo {
  font-size: 10px;
  font-weight: 400;
  }
  
  .errorTwo {
    color: blue;
  }
}

So what happens when we fiddle with this structure a little bit and place part of our code outside of the media query? Here I’ve set the styles for errorOne and errorTwo outside the media query and then attempted to use some questionable inheritance practices by resetting the errorTwo styles inside of the media query via an extend of errorOne.

/*Example 4 Sass*/
.errorOne {
  font-size: 10px;
  font-weight: 400;
  }

.errorTwo {
  font-size: 20px;
  font-weight: 200;
}

@media only screen and (max-width : 300px){  
  .errorTwo {
    @extend .errorOne;
    color: blue;
  }
}

Here’s the compiled CSS from this code. The hope was that the errorOne styles outside of the media query would be applied to errorTwo inside of the media query, but instead @extend worked like it was designed to: it combined the selectors in the original location and applied the shared styles.

/*Example 4 Compiled CSS*/
.errorOne, errorTwo {
  font-size: 10px;
  font-weight: 400;
  }

.errorTwo {
  font-size: 20px;
  font-weight: 200;
}

@media only screen and (max-width : 300px){  
  .errorTwo {
    color: blue;
  }
}

It’s important to note that the code above is very different than the intended CSS, which is shown below. Here errorOne and errorTwo are individually defined with different styles, and then when the media query takes effect errorTwo is set to match errorOne with the addition of a color change.

/*Example 4 Intended CSS*/
.errorOne {
  font-size: 10px;
  font-weight: 400;
  }

.errorTwo {
  font-size: 20px;
  font-weight: 200;
}

@media only screen and (max-width : 300px){  
  .errorTwo {
    font-size: 10px;
    font-weight: 400;
    color: blue;
  }
}

Mixins Inside of a Media Query: Yes

Just like with variables, we can declare a mixin outside of a media query and apply it inside of a media query. For instance, here I set up a font mixin with some text styles and then called it inside of a media query.

/*Example 5 Sass*/
@mixin font {
  font-size: 20px;
  font-family: Helvetica, Verdana, sans-serif;
  text-transform: uppercase;
}

@media only screen and (max-width : 300px){  
  .someclass {@include font;}
}

/*Example 5 Compiled CSS*/
@media only screen and (max-width : 300px){  
  .someclass { 
    font-size: 20px;
    font-family: Helvetica, Verdana, sans-serif;
    text-transform: uppercase;}
}

Roll a Media Query Into a Mixin: No

From here you might be wondering if you can actually just roll a whole media query into a mixin and then pass in some arguments when you want to implement it. As far as I can tell, there’s no way to do this in Sass, LESS or Stylus.

Nesting Media Queries

Thus far we’ve run into no huge surprises. For the most part, everything works about like you’d expect with the possible exception of breakpoints not accepting variables. Our next topic of exploration is a feature that’s built into Sass that was specifically created for media queries: nesting.

Sass allows you to nest all kinds of code. It’s certainly not a requirement when coding with Sass but the feature is there if you want it. For instance, let’s say we wanted a more concise way to write the following CSS:

/*Example 6 Plain CSS*/
.button {
  width: 300px;
  height: 100px;
  background: #eee;
}

.button:hover {
  background: #aaa;
}

With Sass, we can save ourselves the trouble of creating a separate declaration block and just nest the hover styles inside of the button class.

/*Example 6 Sass*/
.button {
  width: 300px;
  height: 100px;
  background: #eee;
  
  :hover {
    background: #aaa;
  }
}

This same logic can be used to apply media queries to specific items. Instead of creating a dedicated section that includes all of your media queries, you could nest them all and thereby pair them with the styles that they’re modifying. Here’s an example:

/*Example 7 Sass*/
.button {
  width: 300px;
  height: 100px;
  background: #eee;
  
  :hover {
    background: #aaa;
  }
  
  @media only screen and (max-width : 300px){
    width: 100px;
    height: 100px;
  }
}

/*Example 7 Compiled CSS*/
.button {
  width: 300px;
  height: 100px;
  background: #eee;
}

.button:hover {
    background: #aaa;
  }
  
@media only screen and (max-width : 300px){
  .button {
    width: 100px;
    height: 100px;
  }
}

Pros and Cons of Nesting Media Queries

The idea behind nesting media queries is a decent one, at least on the surface. As I mentioned above, it sticks the media queries in with the original styles that they’re modifying, which is a decent organizational schema. It also cuts down on the necessary repetition of the selectors, which is a good thing.

Unfortunately, in my opinion, these benefits are outweighed by the fact that this produces some messy code. This issue clearly presents itself as your code becomes more complex. Here we have two different elements, each taking advantage of the nested media query feature.

/*Example 8 Sass*/
.button {
  width: 300px;
  height: 100px;
  background: #eee;
  
  @media only screen and (max-width : 300px){
    width: 100px;
    height: 100px;
  }
}

.nav {
  width: 960px;
  margin: 10px;
  padding: 5px;
  
  @media only screen and (max-width : 300px){
    width: 300px;
    margin: 5px;
  }
}

The Sass above will compile to the CSS below. As you can see, each media query is written out individually and appears right after the styles that it’s associated with.

/*Example 8 Compiled CSS*/
.button {
  width: 300px;
  height: 100px;
  background: #eee;
}

@media only screen and (max-width : 300px){
  .button {
    width: 100px;
    height: 100px;
  }
}

.nav {
  width: 960px;
  margin: 10px;
  padding: 5px;
}

@media only screen and (max-width : 300px){
  .nav {
    width: 300px;
    margin: 5px;
  }
}

If you’ve been working with responsive design, then you probably immediately have some problems with the way this code is compiled. To see what I mean, let’s look at how I would code this out by hand in pure CSS if I were doing it manually.

/*Example 8 Manually Written CSS*/
.button {
  width: 300px;
  height: 100px;
  background: #eee;
}

.nav {
  width: 960px;
  margin: 10px;
  padding: 5px;
}

/*MEDIA QUERIES*/
@media only screen and (max-width : 300px){
  .button {
    width: 100px;
    height: 100px;
  }
  
  .nav {
    width: 300px;
    margin: 5px;
  }
}

Call me crazy, but I think this is superior to what Sass is spitting out. I have a real problem with the fact that identical media query declarations aren’t combined. We see here that there was no reason to write out the 300px query twice when we could simply write it out once and throw both declaration blocks inside.

Further, the organization here is cleaner and easier to follow. Typically, when you’re working with media queries you’re in for a lot of tweaking. With the Sass way, you’re forced to hunt through your code and find every piece that needs changing. If your SCSS file contains hundreds of lines of code, this could be brutal. With vanilla CSS, we can put all of our media queries in one place and tweak them together as a unit.

Control Directives

Though I’m not particularly thrilled with any of the results that I received from mixing Sass with media queries, one area that I do think merits some exploration is control directives. This is a touchy subject because, if you’re not a programmer, control directives seem way too complex for CSS. Just keep in mind before you leave me a nasty comment that I’m just playing with some possibilities here.

Given the nature of media queries, I think control directives could hold some interesting functionality. After all, a media query itself is a lot like an if statement: if the viewport = x, do y. Using some fancy Sass, we can take this much further. See if you can figure out what’s going on here.

/*Example 9 Sass*/
$thewidth: 500px;

@mixin changecolor {
  @if $thewidth < 400px {color:blue;}
 }
 
 .someclass {
   width: $thewidth;
 }
 
 @media only screen and (max-width : 900px){
   .someclass {
     $thewidth: $thewidth - 100px;
     width: $thewidth;
     @include changecolor;
   }
 }
 
 @media only screen and (max-width : 800px){
   .someclass {
     $thewidth: $thewidth - 100px;
     width: $thewidth;
     @include changecolor;
   }
 }
 

Basically, I set up an item with a width that reduces its width by 100px for every media query. Then I set up a mixin that changes the color of the element if its width gets below a certain point.

This technique could be useful for a lot of different things such as changing a script font to something standard when the size gets too small. Here's a slightly modified version that watches until the object is less than 400px wide and then sets the height to half the width.

/*Example 10 Sass*/
$thewidth: 500px;

@mixin changewidth {
  @if $thewidth < 400px {height: $thewidth / 2}
 }
 
 .someclass {
   width: $thewidth;
   height: 300px;
 }
 
 @media only screen and (max-width : 900px){
   .someclass {
     $thewidth: $thewidth - 100px;
     width: $thewidth;
     @include changewidth;
   }
 }
 
 @media only screen and (max-width : 800px){
   .someclass {
     $thewidth: $thewidth - 100px;
     width: $thewidth;
     @include changewidth;
   }
 }
 

Here's how this compiles. As you can see, the height wasn't changed on the first media query because the width isn't less than 400px. On the second media query though the width is 300px so the height was changed to 150px. The nice about this is that the compiled code is concise and doesn't have any of the crazy logic from above.

/*Example 10 Compiled CSS*/
.someclass {
  width: 500px;
  height: 500px;
}

@media only screen and (max-width : 900px) {
  .someclass {
    width: 400px;
  }
}

@media only screen and (max-width : 800px) {
  .someclass {
    width: 300px;
    height: 150px;
  }
}

Conclusion

I would've really loved to have presented a discussion about how Sass is a game changer when it comes to media queries, but at this point in time I think the opposite is true. It's probably best to steer clear of anything but the most basic Sass features in your implementation of media queries. Feel free to use variables and mixins just like always (accept in the breakpoint declaration), but watch out for inheritance working differently than you expect and think twice before nesting a media query because in the long term you're probably creating more work for yourself.

One exception that I can think of is control directives, which are fairly advanced and hold some fascinating possibilities when combined with media queries.

Have you tried implementing Sass in conjunction with media queries? Did you discovered any cool tricks or did you decide to avoid the practice altogether? Let us know in the comments.

Subscribe


Membership
About the Author