Maintainable Media Queries in CSS

Writing Maintainable Media Queries in SCSS

Media queries are pretty great, enabling a single website to put its best foot forward no matter what size device the content is viewed on. Without media queries, Front End developers would likely still be relegated to serving up two separate websites for one entity: one for small, mobile devices, and one for larger desktop devices. I like to think of the time before media queries as a dark and scary place that we have mostly moved beyond.

For all of the amazing things that Media Queries allow developers to do, they can also greatly complicate maintenance and code readability. Let’s take the following dummy .SCSS code for example:

.Component{
  .Component-header{
    color: #000;
    font-size: 1.3rem;
    padding: 1rem 0.5rem;
  }
  .Component-body{
    border: 1px solid #efefef;
    padding-bottom: 2rem;
    margin: 1rem 0;
  }
  .Component-footer{
    background: #000;
    color: #fff;
    font-size: 0.8rem;
    text-align: center;
  }

  a.Component-link{
    background: #fff;
    border: 1px solid #000;
    color: #000;
    cursor: pointer;
    padding: 1rem 0.5rem;
    transition: all 0.2s;
    &:hover,
    &:active,
    &:focus{
      background: #000;
      color: #fff;
    }
  }
}

@media screen and (min-width: 480px){
  .Component{
    .Component-body{
      border-width: 2px solid;
      padding-bottom: 0;
    }
    .Component-footer{
      font-size: 2rem;
      text-align: left;
    }

    a.Component-link{
      display: inline-block;
      max-width: 300px;
    }
  }
}


@media screen and (min-width: 767px){
  .Component{
    .Component-header{
      color: #222;
      font-size: 1.3rem;
      padding: 1rem 0.5rem;
    }
    .Component-body{
      border: none;
      margin-bottom: 2rem;
    }
    .Component-footer{
      background: #fff;
      color: #222;
    }

    a.Component-link{
      background: #fff;
      border: none;
      color: #222;
      &:hover,
      &:active,
      &:focus{
        text-decoration: underline;
      }
    }
  }
}

In the dummy code block above we have a component with elements that have unique styles based on 3 different screen sizes.

Let’s say you want to add a new property to the .Component-footer selector: you would add that new CSS Declaration to the first instance of Component-footer in the .scss file. Next you need to tweak the display of the.Component-footer element at the 768px breakpoint – time to start scrolling through your .scss file.

If you’ve ever dived into the front end of a large application that has media queries structured this way, then you know how unwieldy things can get. Maintaining the styles of one component in multiple places is cumbersome, and will likely lead to issues at some point. How can we avoid this fragmented approach to writing CSS?

1. Standardize Media Queries with Mixins

When developing with a pre-processor, you have the power of mixins at your disposal. Let’s start to cleanup our media queries by writing some mixins. This will help us to create quick and easy media queries. For the purposes of this post, we’re creating mixins with SASS, but you can also accomplish this with LESS.

@mixin mobile-up {
  @media (min-width:481px) {
    @content;
  }
}

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

The mixins above are based on the two breakpoints that are referenced within the dummy code. I gave each mixin a name that seems to coincide with the breakpoint sizes, but these can be named anything that makes sense. These mixins will be used throughout our .scss files, eliminating the need to spell out traditional media query declarations.

2. Moving Media Queries Inline

At the very least we can replace the media queries previously written in our dummy code with the simple, yet descriptive mixins that we created above. We want to take things a step further and re-shape our .scss file in a way that keeps each element’s styles in one place. Nesting media queries inline within a component will enable us to keep better track of our presentation of an element across screens. It will also drastically enhance the speed of our CSS workflow over time.

Here’s the result:

.Component{
  .Component-header{
    color: #000;
    font-size: 1.3rem;
    padding: 1rem 0.5rem;

    @include mobile-up{
      color: #222;
      font-size: 1.3rem;
      padding: 1rem 0.5rem;
    }
  }
  .Component-body{
    border: 1px solid #efefef;
    padding-bottom: 2rem;
    margin: 1rem 0;

    @include mobile-up{
      border-width: 2px solid;
      padding-bottom: 0;
    }

    @include tablet-up{
      border: none;
      margin-bottom: 2rem;
    }
  }
  .Component-footer{
    background: #000;
    color: #fff;
    font-size: 0.8rem;
    text-align: center;

    @include mobile-up{
      font-size: 2rem;
      text-align: left;
    }

    @include tablet-up{
      background: #fff;
      color: #222;
    }
  }

  a.Component-link{
    background: #fff;
    border: 1px solid #000;
    color: #000;
    cursor: pointer;
    padding: 1rem 0.5rem;
    transition: all 0.2s;

    @include mobile-up{
      display: inline-block;
      max-width: 300px;
    }

    @include tablet-up{
      background: #fff;
      border: none;
      color: #222;
    }

    &:hover,
    &:active,
    &:focus{
      background: #000;
      color: #fff;
      @include tablet-up{
        text-decoration: underline;
      }
    }
  }
}

With these nested media queries in place it’s quick and easy to see what is happening to an element across various breakpoints. This method also works well for developing with a mobile first, min-width media query approach. For those of you who have never seen CSS structured this way, it may look a bit odd, but coding responsive designs is a breeze once you become accustomed to this approach.

3. Combining Media Queries for Performance

When we run our SCSS through a basic build process or compiler, the output ends up looking something like this:

.Component .Component-header {
  color: #000;
  font-size: 1.3rem;
  padding: 1rem 0.5rem;
}
@media (min-width: 481px) {
  .Component .Component-header {
    color: #222;
    font-size: 1.3rem;
    padding: 1rem 0.5rem;
  }
}
.Component .Component-body {
  border: 1px solid #efefef;
  padding-bottom: 2rem;
  margin: 1rem 0;
}
@media (min-width: 481px) {
  .Component .Component-body {
    border-width: 2px solid;
    padding-bottom: 0;
  }
}
@media (min-width: 768px) {
  .Component .Component-body {
    border: none;
    margin-bottom: 2rem;
  }
}
.Component .Component-footer {
  background: #000;
  color: #fff;
  font-size: 0.8rem;
  text-align: center;
}
@media (min-width: 481px) {
  .Component .Component-footer {
    font-size: 2rem;
    text-align: left;
  }
}
@media (min-width: 768px) {
  .Component .Component-footer {
    background: #fff;
    color: #222;
  }
}
.Component a.Component-link {
  background: #fff;
  border: 1px solid #000;
  color: #000;
  cursor: pointer;
  padding: 1rem 0.5rem;
  transition: all 0.2s;
}
@media (min-width: 481px) {
  .Component a.Component-link {
    display: inline-block;
    max-width: 300px;
  }
}
@media (min-width: 768px) {
  .Component a.Component-link {
    background: #fff;
    border: none;
    color: #222;
  }
}
.Component a.Component-link:hover, .Component a.Component-link:active, .Component a.Component-link:focus {
  background: #000;
  color: #fff;
}
@media (min-width: 768px) {
  .Component a.Component-link:hover, .Component a.Component-link:active, .Component a.Component-link:focus {
    text-decoration: underline;
  }
}

The above compiled code contains Media Queries in eight places, yet there are only two breakpoints used. That’s six unnecessary lines of code. Six lines of code might not mean much here, but these unnecessary media query declarations can really add up in a large project. In an increasingly mobile world, web professionals should always minimize file sizes whenever possible.

Whatever your build process may be for compiling LESS or SASS, there are options that allow you to combine Media Queries while compiling your front end code. CodeKit does this automatically now, and there are a variety of plugins out there for combining Media Queries in task runners such as Grunt and Gulp.

Let’s take a look at the result of implementing a media query combiner:

.Component .Component-header {
  color: #000;
  font-size: 1.3rem;
  padding: 1rem 0.5rem;
}
.Component .Component-body {
  border: 1px solid #efefef;
  padding-bottom: 2rem;
  margin: 1rem 0;
}
.Component .Component-footer {
  background: #000;
  color: #fff;
  font-size: 0.8rem;
  text-align: center;
}
.Component a.Component-link {
  background: #fff;
  border: 1px solid #000;
  color: #000;
  cursor: pointer;
  padding: 1rem 0.5rem;
  transition: all 0.2s;
}
.Component a.Component-link:hover, .Component a.Component-link:active, .Component a.Component-link:focus {
  background: #000;
  color: #fff;
}

@media screen and (min-width: 480px) {
  .Component .Component-body {
    border-width: 2px solid;
    padding-bottom: 0;
  }
  .Component .Component-footer {
    font-size: 2rem;
    text-align: left;
  }
  .Component a.Component-link {
    display: inline-block;
    max-width: 300px;
  }
}
@media screen and (min-width: 767px) {
  .Component .Component-header {
    color: #222;
    font-size: 1.3rem;
    padding: 1rem 0.5rem;
  }
  .Component .Component-body {
    border: none;
    margin-bottom: 2rem;
  }
  .Component .Component-footer {
    background: #fff;
    color: #222;
  }
  .Component a.Component-link {
    background: #fff;
    border: none;
    color: #222;
  }
  .Component a.Component-link:hover, .Component a.Component-link:active, .Component a.Component-link:focus {
    text-decoration: underline;
  }
}

The compiled CSS now contains only two media queries – a great improvement. For those new to the world of Front End development, note that you would also want to minify your compiled output to create the smallest file size possible.

If you are still hand-coding Media Queries and structuring your CSS the old fashioned way, I highly recommend giving the approach outlined in this article a shot. Switching to this workflow can greatly increase your productivity and your ability to reuse code.