Using Sass Variables with CSS3 Media Queries
SassMedia QueriesSass Problem Overview
I'm trying to combine the use of a Sass variable with @media queries as follows:
$base_width:1160px;
@media screen and (max-width: 1170px) {$base_width: 960px;}
@media screen and (min-width: 1171px) {$base_width: 1160px;}
$base_width
is then defined at various points in the stylesheet width percentage-based measurements to produce fluid layouts.
When I do this, the variable seems to be recognized properly but the conditions for the media query are not. For example, the above code produces an 1160px layout regardless of screen width. If I flip-flop the @media statements like so:
@media screen and (min-width: 1171px) {$base_width: 1160px;}
@media screen and (max-width: 1170px) {$base_width: 960px;}
It produces a 960px layout, again regardless of screen width. Also note that if I remove the first line of $base_width: 1160px;
it returns an error for an undefined variable. Any ideas what I'm missing?
Sass Solutions
Solution 1 - Sass
This is simply not possible. Since the trigger @media screen and (max-width: 1170px)
happens on the client-side.
Achieving your expected result would only be possible if SASS grabbed all rules and properties in your stylesheet containing your $base_width
variable and copied/changed them accordingly.
Since it won't work automatically you could do it by hand like this:
@media screen and (max-width: 1170px)
$base_width: 960px // you need to indent it to (re)set it just within this media-query
// now you copy all the css rules/properties that contain or are relative to $base_width e.g.
#wrapper
width: $base_width
...
@media screen and (min-width: 1171px)
$base_width: 1160px
#wrapper
width: $base_width
...
This is not really DRY but the best you can do.
If the changes are the same every time you could also prepare a mixin containing all the changing values, so you wouldn't need to repeat it. Additionally you can try to combine the mixin with specific changes. Like:
@media screen and (min-width: 1171px)
+base_width_changes(1160px)
#width-1171-specific-element // additional specific changes, that aren't in the mixin
display: block
And the Mixin would look like this
=base_width_changes($base_width)
#wrapper
width: $base_width
Solution 2 - Sass
Similar to Philipp Zedler's answer, you can do it with a mixin. That lets you have everything in a single file if you want.
@mixin styling($base-width) {
// your SCSS here, e.g.
#Contents {
width: $base-width;
}
}
@media screen and (max-width: 1170px) {
@include styling($base-width: 960px);
}
@media screen and (min-width: 1171px) {
@include styling($base-width: 1160px);
}
Solution 3 - Sass
This isn't possible with SASS, but it is possible with CSS variables (or CSS custom properties). The only drawback is browser support – but there's actually a PostCSS plugin - postcss-css-variables - that "flattens" the use of CSS variables (which gives you support for older browsers, too).
The following example works great with SASS (and with postcss-css-variables you get support for older browsers too).
$mq-laptop: 1440px;
$mq-desktop: 1680px;
:root {
--font-size-regular: 14px;
--gutter: 1rem;
}
// The fact that we have to use a `max-width` media query here, so as to not
// overlap with the next media query, is a quirk of postcss-css-variables
@media (min-width: $mq-laptop) and (max-width: $mq-desktop - 1px) {
:root {
--font-size-regular: 16px;
--gutter: 1.5rem;
}
}
@media (min-width: $mq-desktop) {
:root {
--font-size-regular: 18px;
--gutter: 1.75rem;
}
}
.my-element {
font-size: var(--font-size-regular);
padding: 0 calc(var(--gutter) / 2);
}
This would result in the following CSS. The repetitive media queries will increase the file size, but I have found that the increase is usually negligible once the web server applies gzip
(which it will usually do automatically).
.my-element {
font-size: 14px;
padding: 0 calc(1rem / 2);
}
@media (min-width: 1680px) {
.my-element {
padding: 0 calc(1.75rem / 2);
}
}
@media (min-width: 1440px) and (max-width: 1679px) {
.my-element {
padding: 0 calc(1.5rem / 2);
}
}
@media (min-width: 1680px) {
.my-element {
font-size: 18px;
}
}
@media (min-width: 1440px) and (max-width: 1679px) {
.my-element {
font-size: 16px;
}
}
Solution 4 - Sass
Edit: Please do not use this solution. The answer by ronen is much better.
As a DRY solution, you can use the @import
statement inside a media query, e.g. like this.
@media screen and (max-width: 1170px) {
$base_width: 960px;
@import "responsive_elements";
}
@media screen and (min-width: 1171px) {
$base_width: 1160px;
@import "responsive_elements";
}
You define all responsive elements in the file included using the variables defined in the media query. So, all you need to repeat is the import statement.
Solution 5 - Sass
With @ronen's great answer and a map, there's some real power available:
@mixin styling($map) {
.myDiv {
background: map-get($map, 'foo');
font-size: map-get($map, 'bar');
}
}
@media (min-height: 500px) {
@include styling((
foo: green,
bar: 50px
));
}
@media (min-height: 1000px) {
@include styling((
foo: red,
bar: 100px
));
}
It's now possible to have lots more DRY media queries targeting .myDiv
with a bunch of different values.
Map docs: https://sass-lang.com/documentation/functions/map
Example map usage: https://www.sitepoint.com/using-sass-maps/
Solution 6 - Sass
I had the same problem.
The $menu-width
variable should be 240px on the mobile view @media only screen and (max-width : 768px)
and 340px on the desktop view.
So i have simply created two variables:
$menu-width: 340px;
$menu-mobile-width: 240px;
And here is how i have used it:
.menu {
width: $menu-width;
@media only screen and (max-width : 768px) {
width: $menu-mobile-width;
}
}
Solution 7 - Sass
Two recommendations
1
Write your "default" CSS statements to be for small screens and only use media queries for larger screens. There's usually no need for a max-width
media query.
Example (assuming the element has class "container")
@mixin min-width($width) {
@media screen and (max-width: $width) {
@content;
}
}
.container {
width: 960px;
@include min-width(1170px) {
width: 1160px;
}
}
2 Use CSS variables to solve the problem, if you can.
@mixin min-width($width) {
@media screen and (max-width: $width) {
@content;
}
}
:root {
--container-width: 960px;
@include min-width(1170px) {
--container-width: 1160px;
}
}
.container {
width: var(--container-width);
}
Note:
Since it will have the width of 1160px when the window has a width of 1170px, it may be better to use a width of 100% and max-width of 1160px, and the parent element might have a horizontal padding of 5px, as long as the box-sizing property is set to border-box. There are a lot of ways to solve the problem. If the parent is not a flex or grid container you might use .container { margin: auto }
.
Solution 8 - Sass
This is also possible with %placeholders.
%placeholders can be wrapped in media queries. So you could set up multiple variables to use at different screen sizes, and then the placeholders would automagically pre-process accordingly. I'm using some mixins to shorten my media query declarations here also.
In your _vars.scss file:
$width-1: 960px;
$width-2: 1160px;
In your _placeholders.scss file:
%variable-site-width {
@media screen and (max-width: 1170px) { width: $width-1; }
@media screen and (min-width: 1171px) { width: $width-2; }
}
In your page.scss file:
.wrapper. { @extend %variable-site-width; background: red; etc... }
And this will compile to something similar to:
@media screen and (max-width: 1170px) {
.wrapper { width: 960px; }
}
@media screen and (min-width: 1171px) {
.wrapper { width: 1160px; }
}
Voila!
I use this technique extensively for things like variable font sizes and a raft of other things.