How to Write Media Queries in Sass

How to Write Media Queries in Sass

I recently started using Sass and can confidently say I don't think I'll ever go back to voluntarily using plain CSS again. The ease and speed at which I can style my code is too much of a convenience for me to go back. The power of mixins in particular have increased my workflow speed immensely. But before I jump into all that, let's go over a few things...

What is Sass?

Sass ('Syntactically Awesome Style Sheets - what a name!) is a piece of technology called a "CSS preprocessor". It's a way of writing styling rules that are then 'compiled' into plain CSS. It's much more powerful than CSS by itself because it adopts many features of programming languages such as variables, nesting, imports, inheritance and operators.

Sass's guides are super straight forward and I recommend anyone looking to up their frontend skills to spend a few days getting to grips with it. Once you start playing around with it you'll quickly realise it's power.

Why write media queries in Sass?

I think Sass has so many fantastic use cases, but my favourite so far is for media queries. Writing media queries in plain CSS is one of the more tedious tasks of frontend development, and often means battling to keep code DRY.

Here's an example of what media queries in CSS without frameworks may look like:

/* Sets the margin-top of <h1> elements to 15px on screens less than 1024px */
@media screen and (max-width: 1024px) {
  h1 {
    margin-top: 15px;
  }
}

/* Sets the margin-top of <h1> elements to 5px on screens less than 768px */
@media screen and (max-width: 768px) {
  h1 {
    margin-top: 5px;
  }
}

Immediately we can see some issues with the above method:

  • You have to remember all of the breakpoints you've defined
  • You end up grouping together component styling based on screen rules
  • You have to keep screen rules for the same components in different code blocks, which often leads to rapidly scrolling up and down your codebase to see what your components are doing at all breakpoints

No more! Let me introduce you to the wonderful world of Sass mixins. Sass put it best themselves when describing what mixins do:

Mixins allow you to define styles that can be re-used throughout your stylesheet. They make it easy to avoid using non-semantic classes like .float-left, and to distribute collections of styles in libraries.

Basically, mixins are snippets of reusable logic and code you can use throughout your stylesheets. They allow you to write a media rule (among many other things) once and reuse it on whatever components you like.

How to use mixins for media queries

This is currently my favourite way of setting up media queries:

1. Create your mixins

Create a _mixins.scss file which can hold all of our media rules. I'm going to use the standard device-based breakpoints here, but the wonderful thing about mixins is if you decide to change all of your media breakpoints half way through your project - you only have to do it once.

@mixin for-phone-only {
    @media (max-width: 768px) { @content; }
}

@mixin for-tablet-up {
    @media (min-width: 768px) { @content; }
}

@mixin for-desktop-up {
    @media (min-width: 1024px) { @content; }
}

@mixin for-large-screens-up {
    @media (min-width: 1024px) { @content; }
}

I was inspired to write media queries this way by Timothy Robards, who uses a similar system as mine but with variables instead of units directly (using something like max-width: $breakpoint-phone; and declaring the breakpoint-phone variable outside of the mixins). Indeed, variables and mixins coming together is like discovering a new favourite cocktail recipe. But I found that I never used the variables outside of the mixin declarations so I refactored them out.

2. Import your mixins

In whatever file you intend to use your newly created media rules, make sure to import your _mixins.scss file at the top:

@import "_mixins";
@import "_variables"; /* if using a _variables.scss file */

/* Write your styling rules here */

3. Use your mixin directly in your component styling block

The power of nesting in Sass means that you no longer have to separate your component styling and media rules - you can them all in one block with the include keyword.

h1 {
    font-size: 
    font-family: 'Montserrat', sans-serif;
   @include for-phone-only {
        margin-top: 5px;
    }
    @include for-tablet-up {
         margin-top: 15px;
    }
}

There are many other approaches you can take to creating media queries in Sass (Eduardo Boucas has outlined a few of the popular ones CSS Tricks) - but I believe the one above is the easiest, most elegant and beginner-friendly method. However, I don't think it's the best method for every professional developer...

Let's kick it up a notch

It's worth noting here that many frontend developers have moved away from defining media breakpoints by device sizes. As more phone, tablet, laptop and monitor manufacturers out-compete each other with the latest and greatest, dimension sizes are shifting from one brand to the next rapidly. I mean, who saw the return of the flip phone coming?! If you've moved away from device-based breakpoints (this is a philosophy I'm slowly working towards), there are a couple of options:

You can modify the above method to create more breakpoints and rename them, the Brad Frost method comes to mind where he defined media rules based on these variables:

$bp-small: 24em;
$bp-small-2: 29.75em;
$bp-small-3: 39.8em;
$bp-med: 46.8em;
$bp-med-2: 48em;
$bp-large: 50em;
$bp-large-2: 54.2em;
$bp-xl: 60em;
$bp-xl-2: 67em;

I personally don't find a system like the one above descriptive or semantic enough. Another method I do really like however is actually using the content iself to dictate the breakpoints, which goes something like this:

1. Have a mobile-first approach

Have your "mobile" rules just be global rules, and then have changes based on when the device size increases.

2. Create a single mixin

Create a single mixin that takes account of when a device size changes and becomes larger:

@mixin change-at($media) {
    @media only screen and (min-width: $media){ @content; }
}

3. Pass breakpoints from component styling block to mixin

You can see in point 2 above the variable $media, essentially we're going to use it like you would a parameter and pass it a breakpoint directly from a styling block:

h1 {
    font-size: 
    font-family: 'Montserrat', sans-serif;
    margin-top: 5px;
    @include change-at(768px) {
         margin-top: 15px;
    }
}

Already our code is hugely refactored having only used one mixin and still using in-block media queries. This method is also much more flexible than the one I first defined. However, you may have noticed we've run into one of the problems I mentioned with media queries in plain old CSS - we have to remember a bunch of breakpoints in units. An obvious answer to this problem would be to use predefined variables. If you've used something like this method I'd love to hear how you've found it.

Ciao for now

I hope you enjoyed reading this post and that it sparked some interest in trying media queries out in new and different ways. And if you've not used Sass before - I really cannot recommend it enough, even for beginners.

I'm sure the methods I've written about here can be improved upon, but I think they're a great place to start when getting to know how useful Sass can be.

Know any obvious improvements? Let me know in the comments! Let's chat!