@media Queries & Breakpoints in SCSS
Published
@media
queries at multiple breakpoints are very common in a typical web project. It’s very easy to start tossing @media
rules at the stylesheet but I find that scales poorly. I’ve personally been in the position of editing stylesheets with lots of @media
rules all working with various breakpoints and it quickly becomes cumbersome to manage and grok what’s going on.
The point of this post isn’t to tell you what units or values you should use for your breakpoints, there’s plenty of other articles for that; I just want to discuss how I prefer to handle it in my SCSS1 stylesheets.
Breakpoints with Sass Maps
Sass maps make it easy to abstract our breakpoint values into key/value pairs:
$breakpoints: (
xs: 250px,
sm: 500px,
md: 800px,
lg: 1100px,
xl: 1400px
);
Notice my naming convention: it’s purposely abstract and lacks any references to the values themselves or any device or resolution they may relate to. I prefer sticking to 2-character names. If I need to prepend a smaller value I would create an x2
key; likewise if I need to append a larger value I would create a 2x
key. For more info on naming your breakpoints, check out Chris Coyier’s article on CSS-Tricks.
To reference an element from our map, we would use the map-get
Sass builtin:
map-get($breakpoints, md); // 800px
Wrapping it in a @mixin
This is a bit unwieldly on it’s own, let’s create a mixin aptly named bp
:
@mixin bp ($point, $direction: max) {
@if map-has-key($breakpoints, $point) {
$point: map-get($breakpoints, $point);
}
@media (#{$direction}-width: #{$point}) {
@content;
}
}
If you’re unfamiliar, Sass mixins are like typical functions except they return CSS instead of a specific value. This mixin’s logic is straightfoward: get the element out of map if it exists and generate the correct @media
query. The @content
directive allows us to pass a content block into the mixin, making for very simple usage. The beauty is that we can specify the min
or max
value in the @media
query and use values that aren’t specified in our $breakpoints
map — convenient for those one-offs.
The mixin could be used like so:
.foo {
color: #f00;
@include bp(sm) { // (max-width: 500px)
color: #00f;
}
@include bp(1234px, min) { // (min-width: 1234px)
color: #0f0;
}
}
Honestly, sometimes that’s all you need. It’s still pretty minimal but gives much more flexibility & readability (both paramount to maintainable CSS). However, I’d like to expand it slightly more by dropping the 2nd parameter in favor of a more verbose 1st parameter:
@mixin bp ($expression) {
$direction: str-slice($expression, 0, 1);
$point: str-slice($expression, 2);
@if map-has-key($breakpoints, $point) {
$point: map-get($breakpoints, $point);
}
@if $direction == ">" {
$direction: min;
} @else if $direction == "<" {
$direction: max;
} @else {
@error "Expected < or >, got: #{$direction}";
}
@media (#{$direction}-width: #{$point}) {
@content;
}
}
Nearly identical to our previous implementation with the major addition being an @if
block checking the value of the first character on the input. Based on the first character, we determine the min
or max
value. If the first character is invalid, an @error
is thrown. Yay control flow.
With this new mixin we can write succinctly:
.foo {
color: #f00;
@include bp("<sm") { // (max-width: 500px)
color: #00f;
}
@include bp(">1234px") { // (min-width: 1234px)
color: #0f0;
}
}
Really, that’s all there is to it. Let’s recap:
- Create a map to hold your major breakpoints.
- Create a mixin to aid in writing your queries.
I think this is a great addition to any project and immensely aids in maintaining large CSS codebases, especially as the project iterates and changes ownership.
I’m sure this technique is portable to other preprocessors.↩︎
I love hearing from readers so please feel free to reach out.
Reply via email • Subscribe via RSS or email
Last modified #frontend #dev #sass