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.
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.
Why use a preprocessor at all?
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.
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.
So if your code contains 5 media queries that target tablet devices with a width of 768px, 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 – I reckon the word tablet would do that better than 768px.
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.
A first step would be to store that breakpoint in a variable and use it to construct the media query.
/* Using plain CSS */
@media (min-width: 768px) {
}
/* Using SCSS variables to store breakpoints */
$breakpoint-tablet: 768px;
@media (min-width: $breakpoint-tablet) {
}
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 (represented with a comma in CSS).
For example, if you want to target retina devices, the pure CSS syntax starts getting a bit verbose:
/* Plain CSS */
@media (min-width: 768px) and
(-webkit-min-device-pixel-ratio: 2),
(min-width: 768px) and
(min-resolution: 192dpi) {
}
/* Using variables? */
@media (min-width: $bp-tablet) and ($retina) { // or #{$retina}
}
It does look nicer, but unfortunately it won’t work as expected.
A problem with logic
Because of the way the CSS “or” operator works, I wouldn’t be able to mix the retina conditions with other expressions since a (b or c)
would be compiled into (a or b) c
and not a b or a c
.
$retina: "(-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi)";
// This will generate unwanted results!
@media (min-width: 480px) and #{$retina} {
body {
background-color: red;
}
}
/* Not the logic we're looking for */
@media (min-width: 480px) and (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
body {
background-color: red;
}
}
I realized I needed something more powerful, like a mixin or a function, to address this. I tried a few solutions.
Dmitry Sheiko’s technique
One I tried was Dmitry Sheiko’s technique, which had a nice syntax and includes Chris’ retina declaration.
// Predefined Break-points
$mediaMaxWidth: 1260px;
$mediaBp1Width: 960px;
$mediaMinWidth: 480px;
@function translate-media-condition($c) {
$condMap: (
"screen": "only screen",
"print": "only print",
"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)",
">maxWidth": "(min-width: #{$mediaMaxWidth + 1})",
"<maxWidth": "(max-width: #{$mediaMaxWidth})",
">bp1Width": "(min-width: #{$mediaBp1Width + 1})",
"<bp1Width": "(max-width: #{$mediaBp1Width})",
">minWidth": "(min-width: #{$mediaMinWidth + 1})",
"<minWidth": "(max-width: #{$mediaMinWidth})"
);
@return map-get( $condMap, $c );
}
// The mdia mixin
@mixin media($args...) {
$query: "";
@each $arg in $args {
$op: "";
@if ( $query != "" ) {
$op: " and ";
}
$query: $query + $op + translate-media-condition($arg);
}
@media #{$query} { @content; }
}
But the problem with logical disjunction was still there.
.section {
@include media("retina", "<minWidth") {
color: white;
};
}
/* Not the logic we're looking for */
@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) {
.section {
background: blue;
color: white;
}
}
Landon Schropp’s technique
Landon Schropp’s was my next stop. Landon creates simple named mixins that do specific jobs. Like:
$tablet-width: 768px;
$desktop-width: 1024px;
@mixin tablet {
@media (min-width: #{$tablet-width}) and (max-width: #{$desktop-width - 1px}) {
@content;
}
}
@mixin desktop {
@media (min-width: #{$desktop-width}) {
@content;
}
}
He has a single-responsibility retina version as well.
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.
/* I didn't want to sometimes have this */
@include tablet {
}
/* And other times this */
@media (min-width: 768px) and (max-width: 950px) {
}
Breakpoint technique
Breakpoint-sass 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).
I could write something like:
$breakpoint-tablet: 768px;
@include breakpoint(453px $breakpoint-tablet) {
}
@include breakpoint($breakpoint-tablet 850px) {
}
/* Compiles to: */
@media (min-width: 453px) and (max-width: 768px) {
}
@media (min-width: 768px) and (max-width: 850px) {
}
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 value, or a number and a string and it assumes a property/value pair, to name just a few of the combinations it supports.
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.
Also, if you look at the example above you’ll see that a device with a width of exactly 768px 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.
My (Eduardo Bouças’s) technique
This is my take on it.
Clean syntax, dynamic declaration
I’m a fan of Dmitry’s syntax, so my solution was inspired by it. However, I’d like some more flexibility in the way breakpoints are created. Instead of hardcoding the names of the breakpoints in the mixin, I used a multidimensional map to declare and label them.
$breakpoints: (phone: 640px,
tablet: 768px,
desktop: 1024px) !default;
@include media(">phone", "<tablet") {
}
@include media(">tablet", "<950px") {
}
The mixin comes with a set of default breakpoints, which you can override anywhere in the code by re-declaring the variable $breakpoints
.
Inclusive and exclusive breakpoints
I wanted to have a finer control over the intervals in the expressions, so I included support for the less-than-or-equal-to and greater-than-or-equal-to operators. This way I can use the same breakpoint declaration in two mutually exclusive media queries.
@include media(">=phone", "<tablet") {
}
@include media(">=tablet", "<=950px") {
}
/* Compiles to */
@media (min-width: 640px) and (max-width: 767px) {
}
@media (min-width: 768px) and (max-width: 950px) {
}
Infer media types and handle logic disjunction
Similarly to the breakpoints, there’s a list for media types and other static expressions declared by default (which you can override by setting the variable $media-expressions
). This adds support for optional media types, such as screen or handheld, but it’s also capable of correctly handling expressions with logical disjunctions, such as the retina media query we saw before. The disjunctions are declared as nested lists of strings.
$media-expressions: (screen: "screen",
handheld: "handheld",
retina2x:
("(-webkit-min-device-pixel-ratio: 2)",
"(min-resolution: 192dpi)")) !default;
@include media("screen", ">=tablet") {
}
@include media(">tablet", "<=desktop", "retina2x") {
}
/* Compiles to */
@media screen and (min-width: 768px) {
}
@media (min-width: 769px) and
(max-width: 1024px) and
(-webkit-min-device-pixel-ratio: 2),
(min-width: 769px) and
(max-width: 1024px) and
(min-resolution: 192dpi) {
}
There’s no rocket science under the hood, but the full implementation of the mixin isn’t something I could show in just a few lines of code. Instead of boring you with huge code snippets and neverending comments, I included a Pen with everything working and I’ll briefly describe the process it goes through to construct the media queries.
How it works
- The mixin receives multiple arguments as strings and starts by going through each one to figure out if it represents a breakpoint, a custom width, or one of the static media expressions.
- If an operator is found, it is extracted and any matching breakpoint will be returned, or else we assume it’s a custom value and cast it to a number (using SassyCast).
- If it’s a static media expression, it checks for any
or
operators and generates all the combinations necessary to represent the disjunction. - The process is repeated for all the arguments and the results will by glued together by the
and
connector to form the media query expression.
If you’d like to look at the complete Sass for it, it’s here. It’s called include-media on GitHub.
Final thoughts
- I’m a big fan of this technique to make Sass talk to JavaScript. Because we declare breakpoints as a multidimensional list with their names as keys, exporting them in bulk to JavaScript becomes really straightforward and can be done automatically with just a few lines of code.
- I’m not trying to put down other people’s solutions and I’m definitely not saying this one is better. I mentioned them to show some of the obstacles I found along the way to my ideal solution, as well as some great things they introduced that inspired my own solution.
- You might have some concerns about the length and complexity of this implementation. While I understand, the idea behind it is that you download one single file,
@import
it into your project and start using it without having to touch the source code. Ping me on Twitter though if you have any questions. - You can get it from GitHub and you are very welcome to contribute with issues/code/love. I’m sure there’s still a lot we can do to make it better.
Update!
Eduardo made a website for his approach: @include-media.
That is a monster of a mixin. Nicely done.
It’s probably worth doing a post or two to teach folks how to go about thinking about SASS/SCSS mixins as thoroughly as this one has been thought out. By far my favorite part of this post was reading through that mixin.
Great solution to media query syntax, too! All around, good stuff.
How does this differ from bootstraps simple
…/* Extra small devices (phones, less than 768px) /
/ No media query since this is the default in Bootstrap */
/* Small devices (tablets, 768px and up) */
@media (min-width: @screen-sm-min) { … }
/* Medium devices (desktops, 992px and up) */
@media (min-width: @screen-md-min) { … }
/* Large devices (large desktops, 1200px and up) */
@media (min-width: @screen-lg-min) { … }…
Real front-end developers usually don’t want to rely on a framework like bootstrap all the time. It allows you load up unnecessary stylesheet. The whole point of this is creating a better media query from the stylesheet rather than having some ready classes from a bunch of rules in a framework
This is how I do it. You don’t have to go loading in a huge bloated framework like BS. There’s nothing wrong with taking the good parts and putting them in your own.
I do the same also with the Grid. You can reduce bloat in the grid too by making use with @if and creating an options file where you can easily add or remove parts of the grid.
@wiziwiz, because it’s not bloated with a million different things you are probably not going to use.
In general when I use something like Bootstrap or Foundation it’s for a quick prototype or a click through ( to demo a user story or whatever).
Then I build my own (mobile first) stylesheet. Susy or zen grids are great tools, that make frameworks like Bootstrap and Foundation not needed.
Are em-based MQ now a thing of the past?
px’s are a better way for media-queries and em’s are better for typography and block level/ inline elements. Just my 2 cents
I prefer em-based media queries because if the user decides to zoom-in their browser will pick up the media queries and see the responsive site, thus the website never breaks.
Also, some devices report a higher font size than 16px and if that’s the case they will also see the responsive site because of the em-based MQs.
http://bradfrost.com/blog/post/7-habits-of-highly-effective-media-queries/#relative
http://blog.cloudfour.com/the-ems-have-it-proportional-media-queries-ftw/
Some even suggest usin REMs:
But, https://twitter.com/brad_frost/status/335259353466691584
The zoom issue with px-based media queries problem was a problem in Chrome that’s long been fixed. And since old version of Chrome don’t stick around, I wouldn’t worry about it much. It’s funny how ideas like that can stick in our heads.
I agree with Tony. EMs / REMs for typographical elements or wrappers or those you want related to the typography. Things that influence the structure should either get % or px. That is the philosophy that has served me well so far.
Em isn’t precise enough, I’ve ran into problems where there’s an 1px gap between breakpoints (if you are defining ranged bpts instead of overlapping bpts). Px is the smallest unit, it’s exact.
@Ricardo it’s a conditional statement
@mike mai This is exactly what I’ve experienced while setting breakpoints with ems, I had to fiddle and fuss to get the breakpoints to fire, the math just didn’t work with a base font-size of 16px or whatever you set it to, the formula doesn’t work as expected. ( sorry I’m not going to make an example, just try it yourselves)
@rxrrctct absolutely not. I’m not sure we should be using px for anything these days.
@Tony I’d disagree with you re: px media queries. Using em based media queries can help make your site accessible. Highly recommend reading this blog post – it’ll probably change your mind entirely.
http://blog.cloudfour.com/the-ems-have-it-proportional-media-queries-ftw/
@Alistar, I love using ems, I use them and rems.
I find it funny that the article you linked to the author states that
I just had a few unexpected results using em’s and breakpoints in my ScSS files
Not sure if I’m ready to abstract the elegance of CSS.
It’s kind of a strange way to maintain media queries. I bet that the ordinary CSS syntax is better in this case.
Massive work, obviously. One question though, why not taking advantage of the fact Sass combines media queries rather than moving all the logic and (de)structuration to SassScript?
Using Landon Schropp’s technique, you could have something like this (although I don’t like having a mixin per breakpoint, it is definitely not scalable):
This syntax not only clearly defines its intent to style the component for “tablet” breakpoint, but also adds an extra requirement at the same time.
Over things you might want to try:
– Sass-MQ by @Kaelig, used at The Guardian and The Financial Times (among others): https://github.com/sass-mq/sass-mq
– Breakpoint-slicer by @lolmaus, which is syntactic sugar over Breakpoint: https://github.com/lolmaus/breakpoint-slicer
For people interested with the topic, I have written an article about Responsive Breakpoints Management at SitePoint: http://www.sitepoint.com/managing-responsive-breakpoints-sass/. In case that helps.
I like how your solution works with SCSS
Impressive work!
But what happened to letting content define the breakpoints?
http://bradfrost.com/blog/post/7-habits-of-highly-effective-media-queries/#content
http://www.smashingmagazine.com/2013/03/01/logical-breakpoints-responsive-design/
That was my very first thought when I read the article.
Obviously a tremendous work has been put into this—kudos to Eduardo! But I would never “pre-specify” breakpoints and style content based on them.
The content should define the breakpoints, not the other way around.
but it does allow you to let the content define the breakpoint like this:
I actually think its very flexible and intuitive(to me anyway).
I started using this mixin right away. Thank you so much for this work Eduardo.
Sometimes you don’t have the luxury of knowing the content. Especially when designing web software that is configurable and custom per client. You would need to know the content and not really change it.
Personally i’m not a big fan of breakpoints named after device categories (tablet, phone,…) for obvious reasons. (e.g. a device with a screen width of 1024px can literally be everything from phone to desktop)
I think the brad frost approach to name things is the least bad solution: http://bradfrost.com/blog/post/7-habits-of-highly-effective-media-queries/#content
($bp-small, $bp-small-2, $bp-med, $bp-med-2,…)
I appreciate that, but the good thing about this solution is that it’s flexible enough to do whatever you want. You can redefine
$breakpoints
to use that naming convention instead of the device names it comes with by default.I really think that the content should define the breakpoints and not some predetermined device width that will change. Devices will continue to vary in size so why not have the elements in the design respond individually as the width changes? You can still do this with a mixin very simply…
use:
I must agree. As newer versions of phones and tablets come out (far too quickly nowadays), it seems that it would only be logical to implement breakpoints based on content. It seems to be that by using this method, less time can be spent on site maintenance.
This is a technique I have been using as well, blended with landon schropps technique. Working mobile first also simplifies this for me immensely. I have used a break-one, break-two, etc (similar to schropps approach) as a general guideline and then a break-any as you described (which is probably used most often along with break-one) and generally avoid any max-width media quries.
@Jason, dude, this mixin it’s awesome.
Good bye naming media queries!
Re: px vs em-based media queries, while browser “zoom” in Chrome now treats both the same (eg. using ctrl/cmd +/-), if users choose instead to up their text-size inside browser settings, px-based media queries will not kick in. For that reason I still prefer em-based, although I’m not sure what the more common scenario is among users who zoom (browser or text-based)
http://codepen.io/mpiotrowicz/pen/pvNNXM/
Here you go: http://bit.ly/1xD75tZ
Confirmed! Monika’s demo works as described.
Granted, it was easier to change only the text size in Firefox than in Chrome and see the results.
Thanks for sharing.
I generally prevent them tinkering in browser settings by doing a reset stylesheet. You have more control over presentation and are taking the default text size out of the equation.
I do this. It’s got the benefits of using common breakpoints and using custom ones based on the content. Learned from Tim Knight:
And how do we use it? :)
@Ricardo it’s a conditional statement
sorry meant for the comment to go here for @Ricardo
http://bit.ly/1xD75tZ
Maybe I am the odd one out here, but I use a system as follow…
In main.scss, I load general.scss which has styles required in all cases. Next, also in main.scss, but below the general import I have all breakpoints listed, where each criteria imports the “sub” SCSS file for that specific breakpoint.
So general.scss has the generic (S)CSS, typically the mobile-first styles, which are then extended where needed in larger breakpoints. I love this clean separation. There’s no breakpoint code repeated anywhere and there is a clean distinction between base styles and extended styles. In fact, just from the size of those “sub” SCSS files you can see the quality of your design. The bigger they are, the less responsive in fact the design is.
To each their own, but I’m curious why people use all of the above methods, to me they seem unneeded? The only disadvantage of my approach is needing to open multiple files if you want get a full grasp of a selector, but in practice this does not pose as an issue to me.
You mind sharing the files so we can see your structure?
@Ricardo: Sure, here goes, this is the content of projectname.scss, which is my main SCSS file:
As you can see, I first load base styles, which are divided in separate files per feature. All of those base styles combined essentially is the mobile-first state.
Next, the breakpoints come, where based on the viewport, progressively extra SCSS files are imported. The files basically add up, so if we’re on a “M” breakpoint, it will load the base + XXS + XS + S + M in this case.
These breakpoint files only contain CSS overrides, no base styles, and of course only overrides to that relevant breakpoint.
As said, I like this approach because I never see media queries anywhere, I never duplicate them. I just work in the correct file. The other main benefit is that it gives a very good overview on the health of a project. For example, if I need to strongly override a single class in every single breakpoint, that class may not be very well designed from the start. Also, if one breakpoint SCSS files becomes overly large, it also smells like trouble.
In the (very large) project that I am using this, most of these breakpoint files contain at most a few dozen lines of overrides, the exception being the breakpoint L, which brings radical layout changes and thus many overrides.
The only downside I see with this approach is that some people may prefer to see all of a class (including breakpoint overrides) in one file. I don’t. I cleanly separate the base from the override.
Does this help?
@Ferdy, yeah, that’s pretty cool.
I have to admit I’d be one of those that would like to see a class and all its breakpoints in the same file :p
The way you do this is pretty similar to how Chris here has shown before how he does it, where the main file is only a bunch of
@import
s. I know I’ve seen something similar here on CSS-Tricks.com but can’t find the page.OFFTOPIC –
I am by no means a Sass expert and maybe you know this one already, but for those who don’t, you can concatenate all imported files in with single
@import
directive:Or stacked for readability:
Thanks for sharing and for the detailed explanation.
Sorry I formatted the markdown wrong
Here is the Gruntfile I use:
Off topic:
chris I’m really digging the new look of css-tricks, it’s also loading a lot faster
This is a great read, Media Queries is something that is just second nature now but now using it with SASS creates a little more complex code. I am very similar with my OCD with my CSS, so I want it to be clean and easy for people to understand.
Would love to see this super-mixin translated to Stylus, given that it’s better than Sass in every way.
I’m a big fan of Stylus myself. Except I use it for my node applications, how do you use it in a non node project?
I just use gulp-stylus to compile it to css
I’m interested in your claim that Stylus is better than Sass in every way. Can you expand on that a bit. What makes Stylus better? I haven’t ever used Stylus – I had a brief time with LESS but went back to Sass (SCSS).
doh >_< , can’t believe I didn’t even think of that lol, I didn’tthink of using it outside of a Node project until now Stylus is something that, I personally have used with express projects, it has a lot of the same syntax as old school SaSS. I love it’s syntax because it’s very rubesque. As far as being better, that’s a matter of opinion, I personally love it.
Stylus can do everything that Sass can. The only reason to choose Sass over Stylus is if you reeeeeeaaaally love writing loads of extra curly braces, colons, semi-colons and the word “@mixin”.
I like the SCSS syntax, mainly because it’s familiar and consistent with the CSS I’ve been writing since around 1998! Old habits die hard :) And besides, my text editor does most of the work for me.
@Darryl Use SASS syntax then.
I tend to use more
@include
than@mixin
:]I like to use JS to add a “lg, md, sm, or xs” class to my tag. Then I just nest that into my css rules so all my styling is together rather that broken up into a whole separate media query area.