Solving Sass’s media query duplication problem and enhancing your workflow

My colleagues keep on telling me this is a great solution and asking ‘where did you get it from?’. Actually I came up with this solution myself and since they keep telling me it’s a good idea I thought I would write a post to share it. I’m referring to solving the problem of redundant and repetitive media queries generated by Sass.

There are many different approaches to dealing with media queries in Sass, here are a few and here but all of them suffer the same problem. I’ll first summarise this problem and then offer my solution and highlight the workflow benefits.

The problem

Assuming you break up your website Sass into components, such as header.scss, hero.scss, or cards.scss etc, then the cleanest solution for adding responsive styling is to keep each components responsive styling in each of their respective component files. The simplest way of doing this appears to be defining a few screen size mixins using the @content keyword.

@mixin tablet {
@media (min-width: 768px) and (max-width: 1024px) {
@content;
}
}

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

These mixins will take a list of style declarations and place them inside the media query where the @content symbol is used. Used like this:

p {
font-size: 16px;

@include tablet {
font-size: 18px;
}

@include desktop {
font-size: 20px;
}
}

It’s nice and tidy. It says ‘I’m a component, I respond to this’. The issue is that Sass will duplicate the media query for every include of the mixin in the compiled CSS:

p {
font-style: 18px;
}
@media (min-width: 768px) and (max-width: 1023px) {
p {
font-style: 20px;
}
}
@media (min-width: 1024px) {
p {
font-style: 25px;
}
}

Code examples taken from https://davidwalsh.name/write-media-queries-sass just to illustrate the problem.

Our examples are just covering basic styling with a paragraph tag, but when you have lots of components with responsive styling that’s a lot of repetition.

For small sites this is passable as the price to pay for a clean solution, but for larger sites or web applications it can make your CSS bloated pretty quickly and it just doesn’t feel nice. It would be great if the Sass team were to solve this but I’m sure it’s a pretty complex compile time problem!


The solution in three simple steps

This solution allows you to define all your responsive CSS in each component file where it belongs without duplicating media queries, keeping everything nice, tidy and maintainable.

Step 1. Make a media-queries.scss file

Attempting to avoid the duplicated media queries and keeping things tidy we define our media queries in only one Sass file and call it 
media-queries.scss (or .sass):

// small screen size (sm)
@media (min-width: 801px) {
}
// medium screen size (md)
@media (min-width: 992px) {
}

In this example for simplicity, we’re defining media queries in two main break points for small and medium sized screens.

Step 2. Create a responsive mixin for each component

Now when you’re making a component, let’s say banner.scss, taking the mobile first approach, you define a set of responsive mixins always at the end of the file. These mixins will be called inside the break points defined in your media-queries.scss file.

Make sure to add the suffix of break point name that the mixin will be included inside of. In the example above we gave them names in comments, small (sm) and medium (md). For example, if the css is responding to small screen size break point then give the mixin name the component name plus the suffix of sm for small.

Include any responsive styling inside the mixin matching the screen size you intend to respond to.

Example:

.banner {
text-align: center;
font-size: 14px;
}
// called in media-queries.scss
@mixin banner--sm() {
.banner {
font-size: 20px;
}
}
@mixin banner--md() {
.banner {
text-align: left;
font-size: 25px;
}
}

Step 3. Call the mixins in your media-queries.scss

Going back to our media-queries.scss file we call our responsive mixins inside the designated media queries.

// small screen size (sm)
@media (min-width: 801px) {
@include banner--sm();
}
// medium screen size (md)
@media (min-width: 992px) {
@include banner--md()
}

Now you have all your responsive CSS where it belongs in it’s component file and any responsive CSS is called within their respective break points using the mixins without the compiled CSS containing tonnes of duplicated and redundant media queries.

Outputted CSS of media-queries.scss:

@media (min-width: 801px) {
.banner {
font-size: 20px;
}
}
@media (min-width: 992px) {
.banner {
text-align: left;
font-size: 25px;
}
}

Eventually you will have many mixins being called inside your break points in safe knowledge that you’re not bloating your compiled CSS and you know exactly where to find all your responsive CSS:

@media (min-width: 801px) {
@include home-cta--sm();
@include twitter-testimonials--sm();
@include site-footer--sm();
@include feature-section--sm();
@include feature-item--sm();
@include post-meta--sm();
@include social-share--sm();
@include hero--sm();
@include feature-page--sm();
}

(Edit) The benefits

After reading some of your comments to this post I realised there were benefits we found using this approach that I didn’t communicate in the initial draft which are in fact the best thing about this method.

Sure in terms of file size you’re not saving much by reducing the duplication of media queries, but if I can reduce the file size even a little I will. And performance isn’t really affected by repeating media queries. See: https://helloanselm.com/2014/web-performance-one-or-thousand-media-queries/ and http://aaronjensen.github.io/media_query_test/ (thanks Andrei Fedosjeenko).

The larger benefits were found in our workflow.

Team legibility and standard practise

Going back to the nested media query approach, I like this approach, but when you have a large component file adding another level to the nesting can be untidy. We’ve adopted the BEM approach so nesting is only ever two levels deep on rare occasions and this proposed solution works really well alongside BEM.

Also you or your colleagues must scroll around a component file looking for which parts of the component are responsive. Sure your well written component files shouldn’t be too long, but without searching you aren’t sure initially which parts respond. Here with my proposed solution you and your team know exactly where your component media queries are in any given file. Reading the media query area you can quickly see which parts of the component respond and if there’s nothing there you know immediately the component doesn’t respond to any break points.

Example component file layout

You also have the benefit of just visiting the media-queries.scss file to know which components are responsive. We’ve used this method for quite a large site with 30 or so components and it scaled well.

Works well with mobile first approach

Finally if you’re working to the mobile first approach, as you scroll down the component file you follow a path to the highest screen size. This brings a clear conceptual order of screen size progression through the file making you think less about what parts of the file are working with what screen sizes.

Using the mobile first approach, moving down the file brings you to larger screen sizes

Some drawbacks

There is the draw back that when you delete a component you have to remember to remove it’s mixin call from the media-queries.scss file if the component was responsive. However you would get a compile error anyway if you forgot to remove the mixin call, so you don’t have to do much thinking around that. There have been occasions when I forgot to add a components mixin to the media-queries.scss file when I needed them, but they have been rare.

Refactoring can also be a bit of fiddle, more so than the nested approach.

Having a structured and disciplined approach to media queries in our development really helped simplify things for us and faster to debug, especially when revisiting work.

Worth a try?

We’ve been using this approach in our team for sometime now and we’ve found it simple and easily maintainable. I hope you might give it a try and see if you feel the same.

I’d love to hear feedback from you guys, if you think it’s great or terrible, or another solution using Sass, any comments are welcome!

Thanks for reading!