mq-scss
An extremely powerful but easy to use Sass media query mixin that allows you to create almost any media query you can imagine.
This media query mixin is a powerful tool that lets you easily create far more complex media queries than you would have ever attempted to do with plain css. It also makes your code far easier to maintain through it's ability to take simple media query variables.
I've set up a test/demo site. It primarily exists for testing but it does a pretty decent job of demoing what mq-scss can do as well.
mq-scss family
If you enjoy using mq-scss, you may also enjoy using these:
- mq-js: Allows you to use mq-scss style media queries in your JavaScript files.
- query-list: An mq-scss style work around for the lack of container queries in CSS.
Contents
- Installation
- Basic usage
- MQ variables
- Combining media queries
- Defining media types
- em conversion
- Defining breakpoints
- Debug
- Bonus retina display mixin
- For contributors
- Change log
Installation
If you have never used npm before, install Node.js. Then set a command line interface to your project folder (cd C:/path/to/folder
). Then run npm init
and follow the prompts.
Everyone needs to do these next two steps:
npm install mq-scss --save
Import mq-scss at the top of your main Sass file (note that the exact path will differ depending on your folder structure).
;
Basic usage
Min/Max width
In this example we state that we want the background of the element to be red by default but change to blue if the screen is less than or equal to 600px wide.
// SCSS .element }
/* outputted css: */ {}
It's just as easy to state a minimum width:
// SCSS .element }
/* outputted css: */ {}
Note that in the sass, we state that the width is 600px but it gets outputted as 601px in the css. This makes the mixin more intuitive to use and it means you'll never have to worry about that ugly 1px cross over point where min
and max
ranges set to the same width display at the same time.
Min/Max width long hand
As of v1.3.1, if you prefer to use max-width
and min-width
instead of max
and min
you can.
// SCSS // Both this: .element background: green; }} // And This: .element background: green; }} // Produces this CSS: .element
Inside/Outside
What about those times when you only want a style to be effective within a given range? Perhaps you only want something to be blue if it's a tablet sized screen but not appear on mobile. That is when the inside
range type comes in handy.
// SCSS .element }
/* outputted css: */ {}
Again notice how min-width gets outputted as +1 the value given to avoid potential conflicts.
If you want something to be styled a certain way on mobiles and desktops but not tablets, we can use the outside
range type instead:
// SCSS .element }
/* outputted css: */ {}
Also, as of version 1.3.2, if you prefer to explicitly state width
when using inside
and outside
, you can use the alternate inside-width
and outside-width
syntax.
// SCSS // Both this: .element } // And this this: .element } // Produces this css:
Changing which value comes first
As of version 1.2.0, you no longer need to worry about which value you use first. Place the breakpoint values in any order and the mixin will figure it out from there.
Prior to v1.2.0 You needed to set an $mq-largest-first
global setting variable to false
if you wanted to place the smaller breakpoint before the larger breakpoint. That is no longer necessary.
Ratio based media queries
Ratio ranges must be a division in the form of a sting like '2 / 1'
(width / height). That example meaning that the screen width is 2 times the size of the screen height. Ratio ranges do not accept pixel values.
// SCSS .element }
/* outputted css: */ {}
It is easiest to think of ratio based media queries as always being based more on the width than the height. For example, mq('min-ratio', '2 / 1')
will be active at a ratio of 3 / 1
but not at a ratio of 1 / 1
or 2 / 3
. min-ratio
acts a little bit like min-width
in this sense. Since 3 / 1
is wider than 2 / 1
it will be active but both 1 / 1
and 2 / 3
are thinner than 2 / 1
so those are not active.
There are 2 types of ratio based media queries, "aspect-ratio" (shortened to just ratio
in the mq mixin) and "device-aspect-ratio" (shortened to device-ratio
in the mq mixin). It is generally best to stick with "aspect-ratio" rather than "device-aspect-ratio" since "aspect-ratio" is determined by the users browser window size. "device-aspect-ratio" is based on the physical screen size of the users device. With "aspect-ratio" you can see the effects by just resizing the browser window. With "device-aspect-ratio" you will physically have to look at the site on a different screen in order to see the effect... or look at the site with the Chrome dev tools screen emulator open (Chromes screen emulator obeys "device-aspect-ratio" media queries).
Ratio based media queries are mostly useful for when you have sites that have displays that take up the full screen. Displays like this tend to need media queries that understand both the users screen height and width at the same time. You may need to combine the ratio based media query with a more traditional pixel based media query for it to have the greatest effect. Read the Media Query plus
statements section for more details on how to do that.
Unlike min-width
and min-height
, min-ratio
will actually conflict with max-ratio
. To learn how to avoid this, read through the not
media type section.
Full list of media query ranges
@include mq([range], XXX, YYY){ /*styles*/ }
These examples are based on XXX being a larger value than YYY.
Note that orientation and ratio ranges do not accept pixel values.
Also, orientation
only accepts the strings 'portrait'
and 'landscape'
.
Single value ranges
-
min :
(min-width: XXX)
-
max :
(max-width: XXX)
-
min-width : (same as
min
) -
max-width : (same as
max
) -
min-height :
(min-height: XXX)
-
max-height :
(max-height: XXX)
-
ratio :
(aspect-ratio: XXX)
-
min-ratio :
(min-aspect-ratio: XXX)
-
max-ratio :
(max-aspect-ratio: XXX)
-
device-ratio :
(device-aspect-ratio: XXX)
-
min-device-ratio :
(min-device-aspect-ratio: XXX)
-
max-device-ratio :
(max-device-aspect-ratio: XXX)
-
orientation :
(orientation: XXX)
Double value ranges
-
inside :
(max-width: XXX) and (min-width: YYY)
-
outside :
(max-width: YYY), (min-width: XXX)
-
inside-width : (same as
inside
) -
outside-width : (same as
outside
) -
inside-height :
(max-height: XXX) and (min-height: YYY)
-
outside-height :
(max-height: YYY), (min-height: XXX)
-
inside-ratio :
(max-aspect-ratio: XXX) and (min-aspect-ratio: YYY)
-
outside-ratio :
(max-aspect-ratio: YYY), (min-aspect-ratio: XXX)
-
inside-device-ratio :
(max-device-aspect-ratio: XXX) and (min-device-aspect-ratio: YYY)
-
outside-device-ratio :
(max-device-aspect-ratio: YYY), (min-device-aspect-ratio: XXX)
MQ variables
There are two very strong reasons for using an MQ variable over writing the media query inline like I've been doing so far.
Why name your media queries?
Have you ever opened up a style sheet, seen a wall of media queries and not have a clue what any of them are for? Ok you probably understood that a certain block of styles was to make the site look good on mobile, but I'm not talking about that. I mean just from looking at the code, were you able to understand why the developer wrote those styles?
Styles in Media queries are always written with a certain objective in mind. Objectives like making the sidebar full width or making the text smaller. Most of the time, these individual objectives are all lumped under the same media query. To make things even more confusing, each of those little objectives may need to affect multiple elements to achieve the desired result. For example, making the sidebar full width might mean having to making other elements on the page wider as well.
What if you decide later on that you actually want the sidebar to go full width at a larger screen size? You don't want the text shrinking at that larger screen size though, that's good as it is. Well generally you would need to create a new media query and then scour the styles looking for the side bar related stuff, then cherry pick that stuff out. Often that leads to styles being missed and then you being left all dazed and confused about why your site is looking broken. Wouldn't it be easier if the styles were already broken down into the individual objectives that the styling was trying to achieve in the first place?
Enhanced maintainability
You state the media query once at the top of your Sass file and then you can re-use it as many times as you like. If you need to change it later on, you change it once and it updates across the entire style sheet. In combination with the ability to name the variables based on the objective that they are trying to achieve, MQ variables make working with media queries far easier to maintain.
How to use an MQ variable
Naming your MQ variables
I've come up with a bit of a naming convention for them based on BEM. If you don't agree with the naming convention, feel free not to use it. It's just a variable name and has no effect on the mixin itself.
This is how I write an Media Query variable:
$MQ-__--: (, , );
Here is the breakdown of what each part means. I tend to use camelCase for each group to keep the grouping clear.
$MQ - MQ at the start tells us that it's a media query variable (helps when scanning through the code).
[element] - The target element name. So for .car__door
[element] would be door
.
[property] - This one is optional. It represents the main css property that you are catering for in the media query.
[state] - A name for the state that the element is in when the media query is true. Try to keep it as short as possible but still clear.
([range], [breakpoint-1], [breakpoint-2]) - the exact same as what you would put between the brackets of the media query mixin if you were doing it inline.
Creating your MQ variables
Here is an example of how to use it:
// SCSS ;; .module &--green background: grey; } } }}
/* outputted css: */ {} {} {} {}
Ahhhhh!!! It's doubling up on Media queries!!! Think of all that extra weight you're adding!!!
Well actually after gzipping, all the repetitive media query declarations become quite negligible.
Combining media queries
or
statements
Media query Media Query or
statements are only possible using an MQ variable.
// SCSS ; .element }
/* outputted css: */ {}
This technique is most useful when you are targeting a module that is inside a container that is changing in width quite frequently. It's a bit harder to make a counter media query for these though since as long as just a single rule in the or statement is true, the styles will take effect. To effectively create a counter media query for one of these multi queries, you need to carefully target all the gaps in the original statement.
// SCSS ; ; .element background: blue; }}
/* outputted css: */ {} {}
plus
statements
Media Query So the scenario is that you have some styles you want to apply only when both the side bar is full width and the sub heading is hidden. This is the easiest way to do that:
// SCSS ;;; .module &__subHeading } &__mainHeading }}
/* outputted css: */ {} {} {}
This technique utilises the plus
keyword (introduced in version 1.3.0) to glue the two media queries together into a single, more easily transportable, combined MQ variable.
This can also be done inline as a one off like this:
// SCSS ;; .module &__subHeading } &__mainHeading }}
It will even work as part of an or
statement:
// SCSS ;; ; .module &__subHeading } &__mainHeading }}
/* outputted css: */ {} {} {}
You can also string multiple plus
statements together:
// SCSS ;; ; .module }
/* outputted css: */ {}
plus
!IMPORTANT! limitations of You should note that plus
does not work in all situations. There are some restrictions around using the plus
keyword that you should be aware of.
outside
range type
It can not be used in conjunction with any The following code will not work and will throw an error stating that all outside
range types (outside
, outside-height
, outside-ratio
, outside-device-ratio
) are incompatible with plus
statements.
// SCSS ;; ; .module }
plus
statements can not contain any or
statements
or
statements can contain plus
statements however plus
statements can not contain or
statements.
The following code will not work and will throw an error stating that or
statements can't be placed inside plus
statements.
// SCSS ;; ; .module }
Work arounds
You can generally get around these issues by placing the plus
statement inside or
statements.
So instead of this:
// SCSS ;; ; .module }
Do this:
// SCSS ;; ; .module }
/* outputted css */ {}
Alternatively, as of version 1.2.0, you can utilise the native media query merging feature in Sass to sort out the media queries for you (as long as you don't mind them being outside of variables).
// SCSS ;; .module }}
/* outputted css */ {}
Defining media types
By default, mq-scss doesn't apply a media type to the media-queries it generates. As of v2.0.0, you can now declare custom media types in your media queries.
Let's say that you are making a fancy print style sheet and only want to target this media query at printers. Here is how to go about it:
// SCSS .element }
/* outputted css */ {}
Note that if you get an error that looks like this:
Error: media query expression must begin with '('
It is most likely because you have entered a media type that sass doesn't recognize. I want to avoid having to maintain a list of valid media type expressions so I don't intend on clarifying this error.
Here are some more examples of how you can use the media type declaration:
// SCSS .element }
/* outputted css */ {}
This one is a bit ridiculous but it at least shows the level of freedom you have when declaring media types:
// SCSS ; }}
/* outputted css */ {}
An important thing to note about media types and plus
statements. Only the media type that is defined at the start of the plus statement will be honoured. All other media types will be ignored.
// SCSS // "screen" is ignored .media-ignored // "screen" is honoured .media-added
/* outputted css */ {} {}
Media only queries
This was introduced in version 2.1.0. This is now a completely valid mq-scss statement:
body }
/* output css */{}
Using it in that way is kind of counter productive since it takes more characters to type it out than the raw css version. Where the strength comes is from utilizing it in or
and plus
statements.
Here is a plus
statement example:
; body }
/* output css */{}
This lets you place 'screen'
at the start and join it to the rest of the query with a plus
statement. This makes it feel much closer to real css while still keeping all the advantages of the mq-scss syntax. Remember that plus
statements are not compatible with outside
range types though so those still need to use the v2.0.0 syntax.
Here is an or
example.
; body }
/* output css */{}
The use case in this example is that you always want the print styles to be based on the desktop version of the site even if printing from a mobile device. By placing 'print'
on it's own in the or statement, it will always print based on the desktop styles no matter what the current screen size is.
not
media type
The 'not'
is an interesting media type. It was introduced in v2.1.0 as a short-hand for not screen
(then updated in v2.1.3 to output not all
).
'not'
will essentially invert the media query. You could use this for helping you write counter queries however I would caution against this. In general, using 'not'
is fine. However, if you try and nest another media query inside of it, or you nest the 'not'
statement inside a different media query, Sass can output some very odd and unexpected results. This is the reason version 1.0.0 of mq-scss couldn't support nested outside
statements. It was essentially using not inside
to produce the media query.
There is at least one very good use case for using the 'not'
media type though. Take a look at the Ratio section of the mq-scss test site and resize your browser to exactly 800px wide by 400px tall. You will notice that they all return true
since your screen is at exactly a 2 / 1
ratio and they all share that 2 / 1
ratio in their media query. Increase or decrease the size of your window by just a single pixel and you will see some of them swap to returning false.
I couldn't build protection against this into the core mq-scss mixin since I couldn't just add +1 pixel to a ratio like I could with width and height. I also didn't want to use 'not'
in the core mixin since that would prevent all cases of min-ratio
from being compatible with media query nesting. There is nothing stopping you from using 'not'
to solve this issue yourself though.
In order to use a min-ratio
that works in the same sort of way that min-height
and min-width
work, you actually need to use 'not'
in combination with a max-ratio
statement.
//equivalent to (min-ratio, '2 / 1') but without the 1px sour spot //same as above but formatted to place the 'not' at the start
/* output css for both */{}
em conversion
Pixel based media queries can actually appear incorrectly when zooming on some browsers (it's particularly infamous in Safari on Mac).
There are 2 setting variables used to control the em conversion functionality. These settings are defined before the import statement.
; //*default: false*/ ; //*default: 16px*/ ;
$mq-ems defines if the media query mixin should bother doing conversions or not.
$mq-em-base defines the base value that the media query uses for doing it's em conversion calculations.
Defining breakpoints
This mixin does not contain any string to pixel value functionality. This is to keep the mixin modular allowing you to use your own code for defining what the breakpoints should be.
The easiest way to set up a batch of breakpoints is to save them all as Sass variables, then call on them when using the mixin.
// SCSS ;;;;;;;; .element }
Debug
As of version 2.1.0, you can now access some debug information. You have 2 options available to you; local, and global.
The easiest option is using the local method. Simply set the $debug
property of the mixin to true
and you will be given access to the debug information.
.element }
That example would produce a log in your console that looks like this:
mq-scss/_mq.scss:378 DEBUG: inside
mq-scss/_mq.scss:397 DEBUG: inline_mq_values (range: inside, breakpoint_1: 600px, breakpoint_2: 1000px, mediaType: false, mediaOnly: false)
mq-scss/_mq.scss:248 DEBUG: get_values_result (range: inside, breakpoint_1: 600px, breakpoint_2: 1000px, media: "")
mq-scss/_mq.scss:90 DEBUG: calculateMQ (range: inside, breakpoint_1: 1000px, breakpoint_2: 600px, mediaType: false)
mq-scss/_mq.scss:421 DEBUG: !!!!! FINAL OUTPUT: @media (max-width: 1000px) and (min-width: 601px)
The other option is to turn debugging on globally. Set $mq-debug
to true
before the mixin import statement and it will produce debug information for every media query you have created with mq-scss across the entire site. I don't find this as useful as debugging individual mq-scss media queries but it's there if you want it.
//Show debug info for all media-queries across the entire site ;;
Bonus retina display mixin
I've also added a retina display mixin for detecting retina display devices
It can be used like this:
// SCSS .element
To create this css:
/* outputted css */ { } { }
For contributors
If you wish to contribute to mq-scss, it now comes with a testing environment. To access the testing environment:
- Checking out the GitHub repository
- Set a command line interface current directory to the project folder (
cd C:/path/to/folder
) - Run
npm install
- If you have never used gulp before, run
npm install gulp-cli -g
- Run
gulp --open
It also comes with a batch of 46 unit tests that are used to ensure that all of the functionality remains intact. To run the unit tests:
- Run
gulp compile-tests
(refreshes the css files it tests against) - Run
npm test
Change log
The change log has been moved to the mq-scss GitHub releases page.