Variables in Media Queries using LESS
LESS allows for variables. LESS allows for media queries. But how can you use the two together without a scope issue? I wanted to be able to define the variables for different sizes without having to mess up my code structure. I have discovered a way to do this using nested parametric mip, and the results are pretty great. I will talk about the implementation below.
First, I define my media sizes as variables, so I can change them later. These are just examples, and we can define as many or as few as we want here.
@m-small = ~"screen and (max-width: 799px)"; @m-medium = ~"screen and (min-width: 800px) and (max-width: 1299px)"; @m-large = ~"screen and (min-width: 1300px)";
I only have to call a single function in order to make this work. What it does is render one media query for each size I defined above, and then call some helper mixins for each size.
.media(@attr, @parameter) { @media @m-small { .small(@attr, @parameter); } @media @m-medium { .medium(@attr, @parameter); } @media @m-large { .large(@attr, @parameter); } }
@attr here is the CSS property I want to set, and @parameter is the variable that I want it set to.
Next up, we have the mixins where the variables are set (.small, .medium, .large). Here we see definitions for each variable, and then a call to the mixin that will ultimately render the line of code, .guards().
.small(@attr, @parameter) { @fs-small : 1.4rem; @fs-medium : 2.0rem; @fs-large : 3.4rem; @logo-width : 10rem; .guards(); } .medium(@attr, @parameter) { @fs-small : 1.4rem; @fs-medium : 2.4rem; @fs-large : 3.8rem; @logo-width : 12rem; .guards(); } .large(@attr, @parameter) { @fs-small : 1.4rem; @fs-medium : 1.8rem; @fs-large : 5rem; @logo-width : auto; .guards(); }
Unfortunately, LESS doesn't allow the dynamic definition of CSS properties. However, it does allow matching rules based on the values of parameters. Consequently, the guards have to look a little something like this: (and must be defined for each property you want rules about)
.guards() when (@attr = 'font-size') { font-size: @@parameter; } .guards() when (@attr = 'width') { width: @@parameter; }
So, now to put it all together and see how it works. To make a call to the function, I simply write:
nav { .media('font-size', 'fs-medium'); }
As seen in this example, I make a call to .media('font-size', 'fs-medium'). The result is like this:
@media screen and (max-width: 799px){ nav{ font-size:2.0rem; } } @media screen and (min-width: 800px) and (max-width: 1299px){ nav{ font-size:2.4rem; } } @media screen and (min-width: 1300px){ nav{ font-size:1.8rem; } }
So, this allows me to define all my variables in one place, and then, in a single line, include each media query with the desired variable value. But what are the limitations and drawbacks of this method?
First, the biggest drawback is that this will produce some messy CSS. Specifically, every single element you use .media() with will have media queries wrapped just around it. However, if you preprocess your CSS for production, and minify it, the effects of this are pretty negligible, for a small number of different media sizes.
Now onto some of the limitations of LESS that make this code the way it is:
No way to write variables in the @media scope and have them be useable outside of it.
No arrays: If there were arrays, I could just define one for each media type and pass them in as necessary.
No dynamic definitions of CSS properties means that I can't write the guards in a single line, and have to write one for every property I want to mess with.
This code can be easily modified to support more media sizes with different names, or to render more types of CSS properties. Hope this helps someone facing the same issue I did!