Using Sass Variables with CSS3 Media Queries
Asked Answered
G

9

168

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?

Gregoriogregorius answered 3/2, 2012 at 0:41 Comment(0)
A
118

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
Aceldama answered 3/2, 2012 at 15:17 Comment(5)
Is it still the case that SASS won't work with $variables inside @media queries?Pundit
@Pundit it will always be the case, until there is such a thing as a client-side Sass compiler (e.g. in JavaScript) that runs on viewport resize. Sass variables are compiled down to static values at build time; they no longer exist at runtime. Media queries are evaluated at runtime, when the media attributes in the query change.Inarch
@Inarch I don't see why that's a reason. SASS could easily(?) just track all the usages of the variable and insert media queries where they're used; $foo: 1rem; @media (min-width: 10rem) {$foo: 2rem} .class {font-size: $foo} .class2 {padding: $foo} would result in .class {font-size: 1rem} .class2 {padding: 1rem} @media (min-width: 10rem) (.class {font-size: 2rem} .class2 {padding: 2rem}}Tract
yep sass is a preprocessor, there is no reason for "This is simply not possible"... It could do it, it is just not implemented.Grams
I use :root{--blabla: 10px} at the top and then @media screen and (max-width: 480) {:root{blabla: 20px}}, use with var(--blabla)Flanagan
R
83

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);
    }
Renege answered 11/3, 2015 at 12:35 Comment(3)
sooner or later youll encounter this Problem, tho: sitepoint.com/cross-media-query-extend-sassProphetic
This one better than my answer!Foreignism
+1 love this solution. Combine this with a map (sass-lang.com/documentation/functions/map) and you've got some power - I'll make a separate solution here to explain. Edit: done. https://mcmap.net/q/143657/-using-sass-variables-with-css3-media-queriesPhosphine
E
31

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).

SCSS

$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).

CSS

.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;
  }
}
Eyrie answered 22/3, 2019 at 9:22 Comment(1)
This should be the top-voted answer as it effectively leverages cascading behavior while still allowing scss variable use.Principally
F
10

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.

Foreignism answered 3/3, 2015 at 18:35 Comment(0)
P
10

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/

Phosphine answered 4/7, 2019 at 22:11 Comment(1)
It took me a sec to see it, but this is the same technique as @ronen's answer, just using a map input to support multiple variables at once. I'm not discounting it's value by any means, but it was harder to understand until I made that connectionMccormick
M
7

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;
    }
}
Moderate answered 3/3, 2017 at 18:22 Comment(0)
E
3

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 }.

Eames answered 19/3, 2020 at 0:36 Comment(1)
standard CSS variables is the cleanest route.Eats
A
1

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.

Arum answered 27/7, 2021 at 6:51 Comment(1)
Thanks, that worked brilliantly! Idk why you had no upvotes.Kier
P
0

Put all your styles (i.e. SCSS rules) in a @mixin then use it:

// the mixin's declaration
@mixin someScssRules($base_width) {
    .title {
        width: $base_width;
    }
    ...
}

// the mixin's usage
@media (max-width: 1170px) {
  @include someScssRules(960px);
}

@media (min-width: 1171px) {
  @include someScssRules(1160px);
}

All the above could be placed in the same SCSS file.

Of topic: don't use 1170px and 1171px, use the same value for min and max, e.g. 1170px.

Parang answered 27/4 at 15:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.