Creating a Dead Simple Sass Mixin to Handle Responsive Breakpoints

Simplifying syntax and responsive code organization


UPDATE: I’ve further outlined a refactoring of this concept in another post.

Mobile web use continues to grow and responsive design as a solution isn’t going anywhere any time soon. But I have a dark secret. While I love building responsive sites, I find remembering and typing the syntax for media queries frustrating. It’s not difficult really, it’s just tedious and it feels messy. Using Sass can simplify the process not only in making breakpoint management easier and abstracting syntax, but Sass has features to make the organization of your code better as well.

Building Off of Bootstrap

I’m a Bootstrap user so most of my sites focus on the four different breakpoints built into Bootstrap 3’s responsive framework.

  • 767px wide and below
  • 768px wide and up
  • 992px wide and up
  • 1200px wide and up

The Bootstrap framework references these with classes containing names of xs, sm, md, and lg when using grid columns or determining item visibility. Let’s start by using these breakpoints as the basis for our mixin.

@mixin breakpoint($class) {
@if $class == xs {
@media (max-width: 767px) { @content; }
}

@else if $class == sm {
@media (min-width: 768px) { @content; }
}

@else if $class == md {
@media (min-width: 992px) { @content; }
}

@else if $class == lg {
@media (min-width: 1200px) { @content; }
}

@else {
@warn "Breakpoint mixin supports: xs, sm, md, lg";
}
}

The mixin alone simplifies a lot for us. We can use the two letter class signifier we alright use throughout the whole framework to remember which breakpoint we’re targeting—making it much easier to use. If we forget what class names to use and accidentally use the wrong one, we’ll be reminded with a warning message from Sass as to which values are available.

Using the Mixin Within Your Code

Built into Sass is the ability to nest our media queries within the primary code of an area we are designing. With this technique, everything you’re doing to style a specific area can be done in the same place without separating the media query into a different file or a different region in the code. Using our mixin would allow us to do the following.

aside.primary {
float: right;
width: 350px;
@include breakpoint(sm) {
float: none;
width: 100%;
}
}

This would turn off our float and make the width 100% for both our sm and xs breakpoints. From a mobile-first perspective, we can make the same code even more concise since our mobile code version is the default behavior of the aside, a block-level tag.

aside.primary {
@include breakpoint(md) {
float: right;
width: 350px;
}
}

This version floats the aside with a set width only for the md breakpoint and above, leaving the xs and sm breakpoints alone.


Unfortunately, using a mixin does create some code duplication throughout our compiled CSS as the mixin surrounds the various content blocks. My perspective on this is that the benefit in simplifying syntax and organization is worth that duplication since the duplication doesn’t have to be managed manually. That being said, it’s still important to keep your CSS concise and to use this mixin in a way that supports as concise of an output as possible.

Taking It Even Further

The mixin as we’ve laid it out would be fine for most people, but what if there are conditions where I want to use these class name breakpoints as well as having the option of specifying a minimum pixel width, maximum pixel, or even both?

Creating a mixin that allows this flexibility isn’t difficult, but it requires that we check the attribute values we’re passing into the mixin before we take an action on our code. Let’s refactor the attributes we pass in a little bit.

@mixin breakpoint($min: 0, $max: 0) {
$type: type-of($min);

@if $type == string {
...
}

@else if $type == number {
...
}

}

There’s a lot happening here, so let me point out a few things.

  • The mixin now takes a $min and $max attribute, which both default to a value of zero.
  • We check the type of value of the first passed it attribute, which in this case in $min. This helps us identify if we’re passing in a Bootstrap class or a custom width.
  • If $min is a string the mixin assumes you’re passing in xs, sm, md, or lg as the attribute and it would execute the previous mixin code.

Since we know how to handle $min as a string, let’s take a look at how we would handle it as a number so we can start building custom breakpoints.

@else if $type == number {
$query: "all" !default;
@if $min != 0 and $max != 0 {
$query: "(min-width: #{$min}) and (max-width: #{$max})";
}
@else if $min != 0 and $max == 0 {
$query: "(min-width: #{$min})";
}
@else if $min == 0 and $max != 0 {
$query: "(max-width: #{$max})";
}
@media #{$query} {
@content;
}
}

One thing you might notice is that I’m setting the $query variable to a default in the beginning before any conditional checks. This seems to be required in Sass, because otherwise you’ll receive a “undefined variable: $query” error if it’s first set within your conditional.

Looking at our previous aside example, let’s add a few custom sizes.

aside.primary {
@include breakpoint(md) {
float: right;
width: 350px;
}
@include breakpoint(480px) {
display: none;
}
@include breakpoint($min: 640px, $max: 767px) {
text-align: center;
font-style: italic;
}
}

This compiles into…

@media (min-width: 992px) {
aside.primary {
float: right;
width: 350px;
}
}
@media (min-width: 480px) {
aside.primary {
display: none;
}
}
@media (min-width: 640px) and (max-width: 767px) {
aside.primary {
text-align: center;
font-style: italic;
}
}

The mixin provides the consistency to simplify responsive code to use the same classes so many people are familiar with in Bootstrap and the flexibility for custom widths. I’ve made the full mixin available as a gist, you’re welcome to try it, fork it, and generally just play with it.

To me Sass isn’t just about simplifying your CSS, it’s about creating a toolkit that improves the way you think about producing projects.

If you’ve enjoyed this post, I would be honored if you hit “recommend”. Feel free to introduce yourself and say hello on Twitter.