Concise Sass Mixin for Media Queries

Kevin Hamer
3 min readMay 19, 2021

--

If you want, jump to the codepen for this article first.

Using sass for media queries is pretty popular — there’s plenty of libraries just for this. Sass is a lot more powerful (especially with dart-sass) than people who have been using Sass for a long time might realize, and we can write some really expressive mixins and functions these days.

End result first:

$breakpoints: (md: 616px, lg: 1000px, xl: 1288px);h1 {
@include at-each(2rem, md 3rem, lg 4rem) using ($size) {
font-size: $size;
}
}

Code is meant to be read by developers first and foremost. Before I explain what the above snippet does — make a guess. Sass like this isn’t about saving characters for the code’s sake, it’s about writing code with clear intent.

For each argument to the at-each() mixin, it treats the first argument (if present) as a breakpoint and the second as a parameter to pass along to the mixin’s @content . That is, the above code compiles to

h1 {
font-size: 2rem;
}
@media screen and (min-width: 616px) {
h1 {
font-size: 3rem;
}
}
@media screen and (min-width: 1000px) {
h1 {
font-size: 4rem;
}
}

Using the mixin results in clearer code and no duplication (you only reference font-size in one place.) It lowers the difficulty in changing single CSS value per breakpoint down to only adding one more meaningful line.

Here’s the source.

@use "sass:map";$breakpoints: (md: 616px, lg: 1000px, xl: 1288px);@mixin at-each($map...) {
@each $bp, $arg in $map {
@if $arg {
@if map.has-key($breakpoints, $bp) {
$bp: map.get($breakpoints, $bp);
}
@media screen and (min-width: $bp) {
@content($arg);
}
} @else {
@content($bp);
}
}
}
  • First up, we’ll use the newer sass modules syntax and @use "sass:map"; . You could easily rewrite this to use the (deprecated) unnamespaced sass map functions instead.
  • Next, Sass supports arbitrary arguments via a spread-like operator. Since we’re going to loop over $map right away, it’s a great fit here.
  • We’re using map.has-key() and map.get() to look up breakpoints by key from our $breakpoints map. Note that, if there’s no match it’ll just use the first argument as the breakpoint, meaning something like @include at-each(400px purple, 500px gold) { will work exactly the way the developer might hope.
  • Next, while @content is certainly not a new feature, you might not know that it can take arguments. Arguments passed in get assigned to variables when you using the mixin by the using () syntax. @include at-each(...) using ($size) { assigns the first argument passed @content to $size .

Other examples of things you can write with this mixin:

.card {
border: 2px solid silver;
@include at-each(20px, md 30px, lg 40px) using ($padding) {
padding: $padding;
}
}
.cell {
@include at-each(750px 33.3%, lg 25%, 1250px 20%) using ($width) {
width: $width;
}
}

Here’s a codepen to play around with:

--

--

Kevin Hamer

The Principal Engineer at Imarc, Erratic Author on Medium. Writing about web development and being a better web developer.