{"id":191869,"date":"2014-12-30T15:02:34","date_gmt":"2014-12-30T22:02:34","guid":{"rendered":"http:\/\/css-tricks.com\/?p=191869"},"modified":"2021-08-03T13:02:51","modified_gmt":"2021-08-03T20:02:51","slug":"approaches-media-queries-sass","status":"publish","type":"post","link":"https:\/\/css-tricks.com\/approaches-media-queries-sass\/","title":{"rendered":"Approaches to Media Queries in Sass"},"content":{"rendered":"\n
Using media queries in CSS as part of responsive websites is bread and butter stuff to todays front-end developer. Using preprocessors to make them more comfortable to write and easier to maintain has become common practice as well.<\/p>\n\n\n\n
I spent a few months experimenting with a dozen different approaches to media queries in Sass and actually used a few in production. All of them eventually failed to cater for everything I needed to do in an elegant way. So I took what I liked about each of them and created a solution that covered all scenarios I came across.<\/p>\n\n\n\n\n\n\n
That’s a fair question. After all, what’s the point of doing all this if one can simply write media queries using pure CSS? Tidiness and maintainability.<\/p>\n\n\n\n
The most common use for media queries is the transformation of a layout based on the browser’s viewport width. You can make a layout adapt in such a way that multiple devices with different screen sizes can enjoy an optimal experience. As a consequence, the expressions used to define the media queries will make reference to the typical screen width of those devices.<\/p>\n\n\n\n
So if your code contains 5 media queries that target tablet devices with a width of 768px<\/em>, you will hardcode that number 5 times, which is something ugly that my OCD would never forgive. First of all, I want my code to be easy to read to the point that anyone understands instantly that a media query is targeting tablet devices just by looking at it \u2013 I reckon the word tablet<\/em> would do that better than 768px<\/em>.<\/p>\n\n\n\n Also, what if that reference width changes in the future? I hate the idea of replacing it in 5 instances around the code, especially when it’s scattered around multiple files.<\/p>\n\n\n\n A first step would be to store that breakpoint in a variable and use it to construct the media query.<\/p>\n\n\n\n Another reason to write media queries with a preprocessor like Sass is that it can sometimes provide some precious help with the syntax, in particular when writing an expression with a logical or<\/em> (represented with a comma in CSS).<\/p>\n\n\n\n For example, if you want to target retina devices<\/a>, the pure CSS syntax starts getting a bit verbose:<\/p>\n\n\n\n It does look nicer, but unfortunately it won’t work as expected.<\/p>\n\n\n Because of the way the CSS “or” operator works, I wouldn’t be able to mix the retina<\/em> conditions with other expressions since I realized I needed something more powerful, like a mixin or a function, to address this. I tried a few solutions.<\/p>\n\n\n One I tried was Dmitry Sheiko’s technique<\/a>, which had a nice syntax and includes Chris’ retina declaration.<\/p>\n\n\n\n But the problem with logical disjunction was still there.<\/p>\n\n\n\n Landon Schropp’s<\/a> was my next stop. Landon creates simple named mixins that do specific jobs. Like:<\/p>\n\n\n\n He has a single-responsibility retina version as well.<\/p>\n\n\n\n But another problem hit me when I was styling an element that required additional rules on intermediate breakpoints. I didn’t want to pollute my list of global breakpoints with case-specific values just so I could still use the mixin, but I definitely didn’t want to forgo the mixin and go back to using plain CSS and hardcoding things every time I had to use custom values.<\/p>\n\n\n\n Breakpoint-sass<\/a> was next on my list, as it supports both variables and custom values in its syntax (and, as a bonus, it’s really clever with pixel ratio media queries<\/a>).<\/p>\n\n\n\n I could write something like:<\/p>\n\n\n\n Things were looking better, but I personally think that Breakpoint-sass’ syntax feels less natural than Dmitry’s. You can give it a number and it assumes it’s a min-width<\/em> value, or a number and a string and it assumes a property\/value pair, to name just a few of the combinations it supports.<\/p>\n\n\n\n That’s fine and I’m sure it works great once you’re used to it, but I hadn’t given up on finding a syntax that was both simple and as close as possible to the way I orally describe what a media query must target.<\/p>\n\n\n\n Also, if you look at the example above you’ll see that a device with a width of exactly 768px<\/em> will trigger both media queries, which may not be exactly what we want. So I added the ability to write inclusive and exclusive breakpoints to my list of requirements.<\/p>\n\n\n\/* Using plain CSS *\/\n@media (min-width: 768px) {\n \n}\n\n\/* Using SCSS variables to store breakpoints *\/\n$breakpoint-tablet: 768px;\n@media (min-width: $breakpoint-tablet) {\n \n}<\/code><\/pre>\n\n\n\n
\/* Plain CSS *\/\n@media (min-width: 768px) and \n (-webkit-min-device-pixel-ratio: 2), \n (min-width: 768px) and \n (min-resolution: 192dpi) { \n}\n\n\/* Using variables? *\/\n@media (min-width: $bp-tablet) and ($retina) { \/\/ or #{$retina}\n\n}\n<\/code><\/pre>\n\n\n\n
A problem with logic<\/h3>\n\n\n
a (b or c)<\/code> would be compiled into
(a or b) c <\/code>and not
a b or a c<\/code>.<\/p>\n\n\n\n
$retina: \"(-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi)\";\n \n\/\/ This will generate unwanted results!\n@media (min-width: 480px) and #{$retina} {\n body {\n background-color: red;\n }\n}<\/code><\/pre>\n\n\n\n
\/* Not the logic we're looking for *\/\n@media (min-width: 480px) and (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {\n body {\n background-color: red;\n }\n}<\/code><\/pre>\n\n\n\n
Dmitry Sheiko’s technique<\/h3>\n\n\n
\/\/ Predefined Break-points\n$mediaMaxWidth: 1260px;\n$mediaBp1Width: 960px;\n$mediaMinWidth: 480px;\n\n@function translate-media-condition($c) {\n $condMap: (\n \"screen\": \"only screen\",\n \"print\": \"only print\",\n \"retina\": \"(-webkit-min-device-pixel-ratio: 1.5), (min--moz-device-pixel-ratio: 1.5), (-o-min-device-pixel-ratio: 3\/2), (min-device-pixel-ratio: 1.5), (min-resolution: 120dpi)\",\n \">maxWidth\": \"(min-width: #{$mediaMaxWidth + 1})\",\n \"<maxWidth\": \"(max-width: #{$mediaMaxWidth})\", \t\t\n \">bp1Width\": \"(min-width: #{$mediaBp1Width + 1})\",\n \"<bp1Width\": \"(max-width: #{$mediaBp1Width})\",\n \">minWidth\": \"(min-width: #{$mediaMinWidth + 1})\",\n \"<minWidth\": \"(max-width: #{$mediaMinWidth})\"\n );\n @return map-get( $condMap, $c );\n}\n\n\/\/ The mdia mixin\n@mixin media($args...) {\n $query: \"\";\n @each $arg in $args {\n $op: \"\";\n @if ( $query != \"\" ) {\n $op: \" and \";\n }\n $query: $query + $op + translate-media-condition($arg);\n }\n @media #{$query} { @content; }\n}<\/code><\/pre>\n\n\n\n
.section {\n @include media(\"retina\", \"<minWidth\") {\n color: white;\n };\n}<\/code><\/pre>\n\n\n\n
\/* Not the logic we're looking for *\/\n@media (-webkit-min-device-pixel-ratio: 1.5), (min--moz-device-pixel-ratio: 1.5), (-o-min-device-pixel-ratio: 3 \/ 2), (min-device-pixel-ratio: 1.5), (min-resolution: 120dpi) and (max-width: 480px) {\n .section {\n background: blue;\n color: white;\n }\n}<\/code><\/pre>\n\n\n
Landon Schropp’s technique<\/h3>\n\n\n
$tablet-width: 768px;\n$desktop-width: 1024px;\n\n@mixin tablet {\n @media (min-width: #{$tablet-width}) and (max-width: #{$desktop-width - 1px}) {\n @content;\n }\n}\n\n@mixin desktop {\n @media (min-width: #{$desktop-width}) {\n @content;\n }\n}<\/code><\/pre>\n\n\n\n
\/* I didn't want to sometimes have this *\/\n@include tablet {\n\n}\n\n\/* And other times this *\/\n@media (min-width: 768px) and (max-width: 950px) {\n\n}<\/code><\/pre>\n\n\n
Breakpoint technique<\/h3>\n\n\n
$breakpoint-tablet: 768px;\n\n@include breakpoint(453px $breakpoint-tablet) {\n\n}\n\n@include breakpoint($breakpoint-tablet 850px) {\n\n}\n\n\/* Compiles to: *\/\n@media (min-width: 453px) and (max-width: 768px) {\n\n}\n\n@media (min-width: 768px) and (max-width: 850px) {\n\n}<\/code><\/pre>\n\n\n\n
My (Eduardo Bou\u00e7as’s) technique<\/h3>\n\n\n