Today I’m going to share some Sass (SCSS) mixins which I’ve found helpful when developing Bootstrap websites.

Whether you’re using full-blown Bootstrap or just leveraging the familiar grid they will save you time when writing repetitive media queries. Due to the similarities in naming conventions between Bootstrap 3 and 4, these mixins can be easily adapted for both versions.

They’re also versatile and work well even with your own custom naming conventions and breakpoints. If you want to see the full code snippet, jump straight to it.

What do the mixins do?

They provide a semantic media query mixin for the following:

  • Respond above XX.
  • Respond below XX.
  • Respond between XX and XX.

Where XX is the two letter Bootstrap breakpoint (i.e. sm, md).

What problems do the mixins solve?

While developing with Bootstrap there are a couple of things I find myself writing over and over again:

@media (min-width: 768px) {
  /* Target devices wider than 768px. */
}

@media (max-width: 767px) {
  /* Target devices narrower than 768px. */
}

In Bootstrap terms, I refer to the breakpoint above (768px) as sm.

I write my CSS mobile-first and try my best to limit max-width media queries but they often save time and space.

In additional to these two, I occasionally need to apply CSS between two specific breakpoints.

@media (min-width: 768px) and (max-width: 991px) {
  /* Target devices between 768px and 992px. */
}

Breakpoint variables

It’s best practice to use Bootstrap’s breakpoints in your own code but it’s painful to write them all the time. To get around that I use a Sass map of values:

// A map of breakpoints.
$breakpoints: (
  xs: 576px,
  sm: 768px,
  md: 992px,
  lg: 1200px
);

This means the breakpoint values are only ever written once. Map values like these can be accessed using the Sass' map-get function:

// Get the small breakpoint.
$breakpoint: map-get($breakpoints, sm);

Media query mixins

Respond above

Before going ahead and writing the media query it’s a good idea to ensure the key exists in the $breakpoints map in case you make a typo (i.e. @include respond-above(small)).

To do this we use Sass' map-has-key function. Check it out below:

// Respond above.
@mixin respond-above($breakpoint) {

  // If the breakpoint exists in the map.
  @if map-has-key($breakpoints, $breakpoint) {

    // Get the breakpoint value.
    $breakpoint-value: map-get($breakpoints, $breakpoint);

    // Write the media query.
    @media (min-width: $breakpoint-value) {
      @content;
    }

  // If the breakpoint doesn't exist in the map.
  } @else {

    // Log a warning.
    @warn 'Invalid breakpoint: #{$breakpoint}.';
  }
}

So we can pass a value to the respond-above mixin in the form of a Bootstrap breakpoint.

Note that we are also logging a @warning to the console if the breakpoint doesn’t exist in the map. Without this the media query won’t show up in your compiled CSS and you’ll have no idea.

@include respond-above(sm) {
  .element {
    font-weight: bold;
  }
}

Here’s the CSS output:

@media (min-width: 768px) {
  .element {
    font-weight: bold;
  }
}

Respond below

This mixin works in much the same way.

@mixin respond-below($breakpoint) {

  // If the breakpoint exists in the map.
  @if map-has-key($breakpoints, $breakpoint) {

    // Get the breakpoint value.
    $breakpoint-value: map-get($breakpoints, $breakpoint);

    // Write the media query.
    @media (max-width: ($breakpoint-value - 1)) {
      @content;
    }

  // If the breakpoint doesn't exist in the map.
  } @else {

    // Log a warning.
    @warn 'Invalid breakpoint: #{$breakpoint}.';
  }
}

Here’s how to use it:

@include respond-below(sm) {
  .element {
    font-weight: bold;
  }
}

And here’s the CSS output:

@media (max-width: 767px) {
  .element {
    font-weight: bold;
  }
}

Respond between

Here we want to check that both the $lower and $upper keys exist in the $breakpoints map before writing the media query.

@mixin respond-between($lower, $upper) {

  // If both the lower and upper breakpoints exist in the map.
  @if map-has-key($breakpoints, $lower) and map-has-key($breakpoints, $upper) {

    // Get the lower and upper breakpoints.
    $lower-breakpoint: map-get($breakpoints, $lower);
    $upper-breakpoint: map-get($breakpoints, $upper);

    // Write the media query.
    @media (min-width: $lower-breakpoint) and (max-width: ($upper-breakpoint - 1)) {
      @content;
    }
  
  // If one or both of the breakpoints don't exist.
  } @else {

    // If lower breakpoint is invalid.
    @if (map-has-key($breakpoints, $lower) == false) {

      // Log a warning.
      @warn 'Your lower breakpoint was invalid: #{$lower}.';
    }

    // If upper breakpoint is invalid.
    @if (map-has-key($breakpoints, $upper) == false) {

      // Log a warning.
      @warn 'Your upper breakpoint was invalid: #{$upper}.';
    }
  }
}

Note that for this mixin the @warning will tell you specifically which breakpoint is causing issues (or if it’s both).

I don’t bother coding in smarts to ensure that the first parameter is lower than the second one. I’ve never had any troubles with it the way it is.

@include respond-between(sm, md) {
  .element {
    font-weight: bold;
  }
}

Here’s the CSS output:

@media (min-width: 768px) and (max-width: 991px) {
  .element {
    font-weight: bold;
  }
}

The entire code

I find it handy to have an example usage directly above each mixin for ease of use in the future.

//
//  MEDIA QUERIES
//––––––––––––––––––––––––––––––––––––––––––––––––––

// A map of breakpoints.
$breakpoints: (
  xs: 576px,
  sm: 768px,
  md: 992px,
  lg: 1200px
);


//
//  RESPOND ABOVE
//––––––––––––––––––––––––––––––––––––––––––––––––––

// @include respond-above(sm) {}
@mixin respond-above($breakpoint) {

  // If the breakpoint exists in the map.
  @if map-has-key($breakpoints, $breakpoint) {

    // Get the breakpoint value.
    $breakpoint-value: map-get($breakpoints, $breakpoint);

    // Write the media query.
    @media (min-width: $breakpoint-value) {
      @content;
    }
  
  // If the breakpoint doesn't exist in the map.
  } @else {

    // Log a warning.
    @warn 'Invalid breakpoint: #{$breakpoint}.';
  }
}


//
//  RESPOND BELOW
//––––––––––––––––––––––––––––––––––––––––––––––––––

// @include respond-below(sm) {}
@mixin respond-below($breakpoint) {

  // If the breakpoint exists in the map.
  @if map-has-key($breakpoints, $breakpoint) {

    // Get the breakpoint value.
    $breakpoint-value: map-get($breakpoints, $breakpoint);

    // Write the media query.
    @media (max-width: ($breakpoint-value - 1)) {
      @content;
    }
  
  // If the breakpoint doesn't exist in the map.
  } @else {

    // Log a warning.
    @warn 'Invalid breakpoint: #{$breakpoint}.';
  }
}


//
//  RESPOND BETWEEN
//––––––––––––––––––––––––––––––––––––––––––––––––––

// @include respond-between(sm, md) {}
@mixin respond-between($lower, $upper) {

  // If both the lower and upper breakpoints exist in the map.
  @if map-has-key($breakpoints, $lower) and map-has-key($breakpoints, $upper) {

    // Get the lower and upper breakpoints.
    $lower-breakpoint: map-get($breakpoints, $lower);
    $upper-breakpoint: map-get($breakpoints, $upper);

    // Write the media query.
    @media (min-width: $lower-breakpoint) and (max-width: ($upper-breakpoint - 1)) {
      @content;
    }
  
  // If one or both of the breakpoints don't exist.
  } @else {

    // If lower breakpoint is invalid.
    @if (map-has-key($breakpoints, $lower) == false) {

      // Log a warning.
      @warn 'Your lower breakpoint was invalid: #{$lower}.';
    }

    // If upper breakpoint is invalid.
    @if (map-has-key($breakpoints, $upper) == false) {

      // Log a warning.
      @warn 'Your upper breakpoint was invalid: #{$upper}.';
    }
  }
}

I recommend putting this in a _media-queries.scss partial (or similar) near the top of your SCSS index file.

What next?

I found these mixins super handy in my day-to-day work but they were still somewhat laborious to write. So I recently wrote some custom Sublime Text snippets which autocomplete based on a keyword such as rasm for @include respond-above(sm) {} when you press tab.

Sublime Text snippets

Sublime Text snippets in action.

Sublime Text snippets in action.

Snippets are easy to add to Sublime Text, although the syntax can be a little tricky at first. They can be added via Tools » Developer » New Snippet.

Here’s how my rasm snippet looks:

<snippet>
  <content><![CDATA[
@include respond-above(sm) {
  $1
}
]]></content>
  <!-- Optional: Set a tabTrigger to define how to trigger the snippet -->
  <tabTrigger>rasm</tabTrigger>
  <!-- Optional: Set a scope to limit where the snippet will trigger -->
  <scope>source.scss</scope>
</snippet>

The $1 tells the snippet where the cursor / caret should appear.

All of my snippets are on GitHub and you’re welcome to use them.

I wrote individual snippets for each breakpoint:

Above: raxs, rasm, ramd, ralg

Below: rbxs, rbsm, rbmd, rblg

Between: rbtw

There’s only one “respond between” snippet which takes in lower and upper parameters. Press tab to cycle between values and tab again to start writing SCSS declarations.

Here’s how the respond between snippet looks:

<snippet>
  <content><![CDATA[
@include respond-between($1, $2) {
  $3
}
]]></content>
  <!-- Optional: Set a tabTrigger to define how to trigger the snippet -->
  <tabTrigger>rbtw</tabTrigger>
  <!-- Optional: Set a scope to limit where the snippet will trigger -->
  <scope>source.scss</scope>
</snippet>

Note that $1, $2 and $3 tell Sublime where the caret should appear with each successive tap of the tab key.

Get the snippets

Adding / removing snippets

On macOS you can find Sublime snippets here:

/Users/username/Library/Application Support/Sublime Text 3/Packages

I’m not sure where they live on a Windows machine but Google is your friend.

I recommend creating a /snippets sub-directory so you can easily find them in future.

Note that the Library directory is a hidden file so you’ll need to show hidden files. If you’re on macOS Sierra or newer you can show hidden files via CMD + SHIFT + . otherwise you’ll need to load up terminal and type the following to add.

# Show hidden files
defaults write com.apple.finder AppleShowAllFiles YES

$ Hide hidden files
defaults write com.apple.finder AppleShowAllFiles NO

You will need to re-launch Finder before these changes take effect. For more info on showing / hiding hidden files I recommend Ian Lunn’s article.

Wrapping up

In my own personal fork of Bootstrap 4’s grid I’ve added an additional xl breakpoint which kicks in at 1500px but using these mixins it’s very easy to add this functionality. I did need to add some additional CSS to my _grid.scss partial but that was relatively simple.

I hope these are useful. Let me know in the comments if you have any feedback or suggestions.