Less.js: Group media queries

Created on 11 Sep 2012  ·  64Comments  ·  Source: less/less.js

While the media query bubbling is great:

header {
    color: green;

    @media only screen (max-width: 500px) { color: red; }
}

footer {
    color: green;

    @media only screen (max-width: 500px) { color: red; }
}

Less generates fairly bloated code because it repeats the mediaquery selector each time its declared in the less file:

header {
  color: green;
}
@media only screen (max-width: 500px) {
  header {
    color: red;
  }
}
footer {
  color: green;
}
@media only screen (max-width: 500px) {
  footer {
    color: red;
  }
}

It would be nice if media queries could be grouped if they're identical:

header {
  color: green;
}
footer {
  color: green;
}

@media only screen (max-width: 500px) {
  header {
    color: red;
  }
  footer {
    color: red;
  }
}
feature request medium priority up-for-grabs

All 64 comments

How do you do that without potentially altering the meaning by having the statements re-organised?

I don't think the meanings could be altered. This would be exactly the same as collapsing normal redundant tags. For example:

body {
    background: white;
}

body {
    padding: 0;
    margin: 0;
}

Would be collapsed to:

body {
    background: white;
    padding: 0;
    margin: 0;
}

This is even more the case with media queries because they're special top level selectors that act as an control layer on top of normal element selectors. Basically, they only have a single meaning and aren't affected by the rest of the css within the file.

Further, less already maintains the order of the bubbled media queries, it just creates a lot of redundant selectors for the exact same query. If they could be buffered and written to a single query at the end of processing it would be much neater and producer smaller css output.

What are the rules around selector complexity inside media queries? Do the
queries bump complexity up and override any ordering? Can you point at any
specs?

Essentially, yes. Media selectors are like IF statements that wrap around normal style rules and only apply if the condition of the query is met. For example, if the width of the browser is less than a certain number of pixels then the rules within the query are turned on and override the existing rules.

So having lots of identical queries with a single style each would be functionally identical to one query with all the styles inside it. As long as the query is the same.

Here's some documentation from Mozilla

https://developer.mozilla.org/en-US/docs/CSS/Media_queries

what I mean is in this example.. the div goes red - meaning re-ordering the media queries (both for screen) would change the meaning of the css

@media screen {
    div {
        background-color: green;
    }
}

div {
     background-color: red;
}

@media screen {
    div.pink {
        background-color: pink;
    }
}

It should only combine if the rulesets follow each other directly.

which they don't in @Soviut original example, making this feature request of limited use in IMO

I agree, but I don't see how this would apply to bubbled media queries? Remember, bubbled queries are a bit of syntactic sugar; You can't normally embed a media query inside another selector. So it could safely be assumed that any time you encounter a bubbling query to group it with identical bubbling queries in the order they arrive in.

If you have two bubbled media queries next to each other that can be combined I think it would be very obvious to do that in the less.. can you give an actual example where it would be safe to combine the media queries and it makes sense to keep them separate in the less?

When dealing with retina images, we've wrapped the complex media query inside a mixin and created sprite mixins, so we have this all over the place... it increases the output CSS but it's more maintainable.

For example, here's our sprite mixin:

.sprite(@spritePath, @hdpiPath, @x, @y, @size: auto) {
    background-image: url(@spritePath);  
    background-repeat: no-repeat;
    background-position: -1 * @x -1 * @y; // Negativize the value
    .background-size(@size);
    @media @mediaRetina {
        background-image: url(@hdpiPath);
    }
}

Where @mediaRetina is:

@mediaRetina: ~"only screen and (-webkit-min-device-pixel-ratio: 1.3), only screen and (min--moz-device-pixel-ratio: 1.3), only screen and (-o-min-device-pixel-ratio: 4/3), only screen and (min-device-pixel-ratio: 1.3), only screen and (min-resolution: 124dpi), only screen and (min-resolution: 1.3dppx)";

Then below, we create more mixins to wrap each sprite element like this:

#sprite
{
    .header-logo()
    {
        .sprite(@globalSpritePath, @global2XSpritePath, 22px, 0, 384px 288px);
        width: 94px;
        height: 59px;
    }
}

And use it in other LESS file(s) like this:

h1 {
    width: 94px;
    height: 59px;

    a {
        #sprite > .header-logo();
    }

}

In this case, the generated CSS looks like:

h1 a {
  background-image: url("/images/sprite-global.png");
  background-repeat: no-repeat;
  background-position: -22px 0;
  -webkit-background-size: 384px 288px;
  -moz-background-size: 384px 288px;
  -o-background-size: 384px 288px;
  background-size: 384px 288px;
  width: 94px;
  height: 59px;
}
@media only screen and (-webkit-min-device-pixel-ratio: 1.3), 
       only screen and (min--moz-device-pixel-ratio: 1.3), 
       only screen and (-o-min-device-pixel-ratio: 4/3), 
       only screen and (min-device-pixel-ratio: 1.3), 
       only screen and (min-resolution: 124dpi), 
       only screen and (min-resolution: 1.3dppx) {
  h1 a {
    background-image: url("/images/[email protected]");
  }
}

Now imagine this case being repeated many times. Without grouping, the only way to alleviate this extra weight is to move every retina style into a dedicated LESS file, which may work for small sites, but is unrealistic for a large site.

This is where it makes sense to group them and keep them separated, especially if you have a big site with a lot of modules (like ours).

I know what you mean about overriding styles, though, and I'm not sure you could safely implement this exact feature without potentially messing with the cascading a designer might want.

To me this sounds more like being able to define a "section" (a placeholder) and then defining styles to be placed in it, in whatever order they're written. This is pretty common in server-side templates, where you have a "scripts" or "head" section and then your pages can inject content into them when they're loaded.

So, I'd like to be able to do this in a LESS file essentially:

@media { // retina query
    @renderSection("retina")
}

// in another file
h1 {
    @section retina {
        // retina styles
    }
}

The @section would be replaced on compile with the current selector.

So, my sprite mixin would just become:

.sprite(@spritePath, @hdpiPath, @x, @y, @size: auto) {
    background-image: url(@spritePath);  
    background-repeat: no-repeat;
    background-position: -1 * @x -1 * @y; // Negativize the value
    .background-size(@size);
    @section retina {
        background-image: url(@hdpiPath);
    }
}

It doesn't need to be this syntax (or implementation), I'm basing it off the ASP.NET Razor syntax since that's what I'm familiar with and I like the syntax.

Its a good example.. and a good use-case.. You could do

@media-section

(or media-group) which would then tell less to group the media together (optionally merging it into the media query if it already existed which would allow you to pull the rules to where-ever you wanted to put them).. maybe thats what you mean?

I'm tempted to think its a good idea (and not too difficult to implement)

+1 for @media-group

+1 @media-group

The other option would be to have a global option for grouping true/false.

Hmm, this is probably a good idea. Would there be any case where selective grouping would be necessary?

I think in most cases, people just want their media queries more compact so a global option for now might be the way to go. Should any one claim to need selective grouping, new keywords could be added later.

+1 for a global grouping option.

yes.. because we saw with @import that adding multiple keywords was not the way to go and adding an option is less controversial.

I'd say add the option since it would be useful immediately and would be able to co-exist as an override with a selective import should it ever be created.

Hello, I just saw this issue and wanted to share a little app I made when I needed to group media queries. This is not a finished app. More like some research for a future tool. So I just want to show you the idea, because I really think it's something that must be implemented. (there can be bugs and other problems) but I've tested with my last project which includes twitter bootstrap and it work properly. (no problem with the order of rules) let me know ;)

http://mqhelper.nokturnalapp.com

Hello!

Is someone assigned to this? It's a great feature which could be very useful to decrease css file when created with less. And in this way, It will be easier for developers to work with all these @mqs.

@AoDev mqhelper is definitely a step in the right direction, I could see it being useful as part of a CSS linting process for now. I think it just needs the core functionality extracting from the front-end stuff in your demo.

Yes but the goal of my app is not to be part of some "real tool". I just saw a lot of people here wondering if grouping media queries could be a problem and wanted to show that it can be done. The code is there for the devs of less if they want to have an idea and use parts of it. But I can make it a "real" module if you want :)

I already managed it, it's a neat piece of work, thanks.

@Soviut @lukeapage I think selective grouping makes the most sense. All or nothing wrecks havoc on trying to maintain the cascade. Should be something similar to extend, or maybe literally adding a group keyword. Like this syntax would be f'ing great:

@tablet: (max-width: 979px);

.a {
  color: yellow;
  @media @tablet group {
    color: red;
  }
}
.b {
  background: black;
  @media @tablet group {
    background: white;
  }
}

//output
.a { color: yellow; }
.b { background: black; }
@media (max-width: 979px) {
  .a { color: red; }
  .b { background: white; }
}

Hello, the media-queries grouping would be a really nice addition. In the meantime I've come up with this idea, I wonder if it could be used with the upcoming :extend feature to avoid duplicated properties:

// Define the media queries
@step1: ~"only screen and (max-width: 960px)";
@step2: ~"only screen and (min-width: 961px)";

// Default empty mixins
.step1() {}
.step2() {}

// Custom styles
.test { color: blue; }

// Change the style in the media queries
.step1() {
  .test { color: red; }
}

.step2() {
  .test { color: green; }
}

// Finally render the media queries
@media @step1 { .step1(); }
@media @step2 { .step2(); }

The tool created by @AoDev is great for showcasing how the "all or nothing" approach shouldn't really matter. The grouping of media queries does not suffer the same side effects of grouping/reordering styles. Can anybody provide a real-world example (tested against the tool created by @AoDev) which shows how the all or nothing method is flawed?

@kamranayub perfectly explained the exact situation that I'm dealing with. Personally, I would love to see some form of this implementation. @DesignByOnyx , the test code below doesn't compile properly with @AoDev's tool. I would prefer to do this kind of styling like kamranayub mentioned. It's much cleaner when dealing with multiple less files on larger sites.

.footer ul {

  margin: 10px;

  @media @layout-tablet, @layout-full {
    font-size: 13px;
    font-weight: bold;
  }
  @media @layout-mobile {
    font-size: 10px;
    padding-left: 10px;
  }

  li {

    background: black;
    color: white;
    padding: 10px;

    @media @layout-tablet, @layout-full { .border-radius(@radius) }
    @media @layout-mobile { .border-radius(@radius-mobile) }   
  }

}

It does not work because you have used less syntax. It is meant to work
with CSS.
On Jul 2, 2013 9:19 PM, "P Macom" [email protected] wrote:

@kamranayub https://github.com/kamranayub perfectly explained the exact
situation that I'm dealing with. Personally, I would love to see some form
of this implementation. @DesignByOnyx https://github.com/DesignByOnyx ,
the test code below doesn't compile properly with @AoDevhttps://github.com/AoDev's
tool. I would prefer to do this kind of styling like kamranayub mentioned.
It's much cleaner when dealing with multiple less files on larger sites.

.footer ul {

margin: 10px;

@media @layout-tablet, @layout-full {
font-size: 13px;
font-weight: bold;
}
@media @layout-mobile {
font-size: 10px;
padding-left: 10px;
}

li {

background: black;
color: white;
padding: 10px;

@media @layout-tablet, @layout-full { .border-radius(@radius) }
@media @layout-mobile { .border-radius(@radius-mobile) }

}
}


Reply to this email directly or view it on GitHubhttps://github.com/less/less.js/issues/950#issuecomment-20369038
.

Is the proposal to run the logic used by @AoDev's tool after the LESS is processed? I can't think of a case where that wouldn't be what I want off the top of my head.

Is the proposal to run the logic used by @AoDev's tool after the LESS is processed? I can't think of a case where that wouldn't be what I want off the top of my head.

I think that's an option for the interim, I would consider wrapping @AoDev 's solution in a grunt.js module so that it can be automatically run after the LESS is processed (this is based on the assumption that the pre-processing is being done prior to deployment, not on the fly)

A grunt task would certainly be nice in the interim and useful universally for raw CSS. However, considering how much @media magic LESS is already doing, a grouping option seems logical.

Then again, said Grunt task might be able to fully separate the media queries into their own files, for those who like to load mobile stylesheets on the fly.

Now that you mention it, separating the stylesheets is a useful option to have - currently you would need to build your source files specifically to do that.

Maybe there is scope here for a separate media-query optimiser tool to group and optionally split the media queries?

Absolutely. I'm not sure if CSSO does this already, being the only CSS rewriter I know of that also has a Grunt task associated with it. But even it can't split things like media queries into separate files. Similarly, its rewriting is very basic, based on identical selectors and attributes, as opposed to DOM structure.

Mqhelper already gives you back individual media queries check the github
page for more details. separated css files can be built from there.
On Jul 5, 2013 10:08 AM, "Soviut" [email protected] wrote:

Absolutely. I'm not sure if CSSO does this already, being the only CSS
rewriter I know of that also has a Grunt task associated with it. But even
it can't split things like media queries into separate files. Similarly,
its rewriting is very basic, based on identical selectors and attributes,
as opposed to DOM structure.


Reply to this email directly or view it on GitHubhttps://github.com/less/less.js/issues/950#issuecomment-20505834
.

It sounds like something useful for less long term and not too difficult
(both a grouping option and a seperate file option) if one of you would
like to implement I can give you help...

I don't want to discourage moving towards some type of "section" or "group" idea for implementing this (I think that is good). However, if someone desires the ability to define the @media property info in the code where it is defined for the regular css, but have it grouped in a single @media query elsewhere in the code, there are at least two ways that can currently be done with LESS functionality, giving good control of placement, but admittedly with some extra coding above what the proposed solution would require (so by all means, continue working on that). Consider:

@media screen {
  .my-class > .mediaScreen();
  #screenSpace1(screen);
  #screenSpace2(screen);
}

//technique #1 only works for a top level class or id that can act as a namespace
//and would not handle a deep nesting very well
.my-class {
  regular-property: value;

  .mediaScreen(@selectorString: ~'.my-class') { //full path needs repeat here if deeply nested
    @{selectorString} {
      media-screen-property: value;
    }
  }
}

//technique #2 allows for tag selectors and easier deeper nesting 
#screenSpace1(@place: noMedia) {
  div > ul {
    .setProps() when (@place = noMedia) {
       regular-property: value;
    }
    .setProps() when (@place = screen) {
       screen-property: value;
    }
    .setProps();

    li {
       .setProps() when (@place = noMedia) {
          regular-property: value;
          &:hover { regular-property: value; }
       }
       .setProps() when (@place = screen) {
          screen-property: value;
          &:hover { screen-property: value; }
        }
       .setProps();
    }
  }
}
#screenSpace1();

.more-classes-not-needing-media {property: value;}

#screenSpace2(@place: noMedia) {
  .someClass {
    .setProps() when (@place = noMedia) {
       regular-property: value;
    }
    .setProps() when (@place = screen) {
       screen-property: value;
    }
    .setProps();

    > a {
       .setProps() when (@place = noMedia) {
          regular-property: value;
          &:hover { regular-property: value; }
       }
       .setProps() when (@place = screen) {
          screen-property: value;
          &:hover { screen-property: value; }
        }
       .setProps();
    }
  }
}
#screenSpace2();

Which produces this css:

@media screen {
  .my-class {
    media-screen-property: value;
  }
  div > ul {
    screen-property: value;
  }
  div > ul li {
    screen-property: value;
  }
  div > ul li:hover {
    screen-property: value;
  }
  .someClass {
    screen-property: value;
  }
  .someClass > a {
    screen-property: value;
  }
  .someClass > a:hover {
    screen-property: value;
  }
}
.my-class {
  regular-property: value;
}
div > ul {
  regular-property: value;
}
div > ul li {
  regular-property: value;
}
div > ul li:hover {
  regular-property: value;
}
.more-classes-not-needing-media {
  property: value;
}
.someClass {
  regular-property: value;
}
.someClass > a {
  regular-property: value;
}
.someClass > a:hover {
  regular-property: value;
}

I've been wanting to do something like:

/*
 * Span mixins
 * adapted from Gridpak Beta LESS
 * http://gridpak.com/
 * Created by @erskinedesign
 */

.mixin-span(@num, @gutter_pc, @padding, @max_columns) when (@num > @max_columns) {
    .mixin-span(@max_columns, @gutter_pc, @padding, @max_columns);
}
.mixin-span(@num, @gutter_pc, @padding, @max_columns) when (@num =< @max_columns) {
    @one_col: (100% - (@gutter_pc * (@max_columns - 1))) / @max_columns;
    width:(@one_col * @num) + (@gutter_pc * (@num - 1));
    padding:@padding;
    margin-left:@gutter_pc;
}
.mixin-span_first() {
    margin-left:0;
}

// end of adapted Gridpak LESS

// Namespaced mixin sets

#mob{
    @max_columns: 1;
    @padding: 0 1.5%;
    @gutter_pc: 5%;

    .span(@col){
        @media screen and (max-width:300px){
            .mixin-span(@col, @gutter_pc, @padding, @max_columns);
            .mixin-span_first;
        }
    }
}

#desk{
    @max_columns: 10;
    @padding: 0 3%;
    @gutter_pc: 5%;

    .span(@col){
        @media screen and (min-width:301px){
            .mixin-span(@col, @gutter_pc, @padding, @max_columns);
        }
    }
}

//assign different layouts per namespaced breakpoint
/* ----- Header ----- */
#header{
    #mob > .span(2);
    #desk > .span(4);
    .mixin-span_first;
    background-color:#888;
    color:#fff;
}

/* ----- Main ----- */
#main{
    #mob > .span(1);
    #desk > .span(6);
    background-color:#eee;
    color:#111;
}

but with no grouping, the generated css is a bit bulky

/* ----- Header ----- */
#header {
  margin-left: 0;
  background-color: #888;
  color: #fff;
}
@media screen and (max-width: 300px) {
  #header {
    width: 100%;
    padding: 0 1.5%;
    margin-left: 5%;
    margin-left: 0;
  }
}
@media screen and (min-width: 301px) {
  #header {
    width: 37%;
    padding: 0 3%;
    margin-left: 5%;
  }
}
/* ----- Main ----- */
#main {
  background-color: #eee;
  color: #111;
}
@media screen and (max-width: 300px) {
  #main {
    width: 100%;
    padding: 0 1.5%;
    margin-left: 5%;
    margin-left: 0;
  }
}
@media screen and (min-width: 301px) {
  #main {
    width: 58%;
    padding: 0 3%;
    margin-left: 5%;
  }
}

There is a solution for this https://github.com/buildingblocks/grunt-combine-media-queries however it only orders by min width at the moment so is mainly useful for mobile first sites.

IMO it makes sense to generalize the problem to scope grouping control which will provide solution for issue #930

Great tool @krava ! thanks

Excelent!!! IMO it makes all the sense implement KRAVA´s feature on LESS

+1

I want to do it as a plugin. should be not too hard. too much to do though!

I think plugins should take lower priority than having a system to auto-load plugins (options.json). But yes, a plugin makes sense as an additive feature.

Has this option been implemented yet?
This would probably cut down my outputted css by half as I use media queries within components and would be happy to have them grouped at the output stage.

Regarding slector reordering, if you use a "group" keyword to group something you are aware that it will be removed from the current logical flow and placed in a grouped area.

http://helloanselm.com/2014/web-performance-one-or-thousand-media-queries/
According to this article, it's not necessary to group the media querys.
But a plugin is good.

It's not so much an issue of performance as it is the size of the resulting CSS file. Dozens of @media screen and (max-width: 480px) strings start to add up in terms of CSS file size.

I have asked this question on SO and somebody gave a partial answer to this issue.

@seven-phases-max gave you the answer and referred back to this issue in his answer. Very meta ;)

I definitely prefer the mixin method to merge the media queries, as opposed to post processing them. This allows for easier typing and more control over what gets merged and how.

Here in the comments you can see the solution I use in all my projects:
https://github.com/less/less.js/issues/950#issuecomment-17723748

I just did a search and found two grunt plugins that do media query grouping:

https://github.com/buildingblocks/grunt-combine-media-queries

https://github.com/Se7enSky/grunt-group-css-media-queries

Combine media queries is also available for gulp.
http://github.com/konitter/gulp-combine-media-queries

Since v2 you can use plugins, so see https://github.com/bassjobsen/less-plugin-group-css-media-queries (and https://github.com/bassjobsen/less-plugin-pleeease)

Closing since this is supported by a plugin, and isn't a priority to move into core (AFAIK - @less/admin correct if this is wrong).

@gyopiazza I have a question about https://github.com/less/less.js/issues/950#issuecomment-17723748 above: why do you need to initialise the mixin? I mean, the CSS compiles without the init. I guess I am trying to understand best practices and usage.

@nfq This is not quite an initialization but just "default" empty definition. It's necessary in case you don't provide your custom .step*() mixins (i.e. it's assumed that you may have these things in different files, e.g. "default" .step*() definitions and their rendering are in some generic utility/library code, while custom .step*() definitions are in your theme/project specific code).

@nfq It's actually not necessary. Edited :) Edited again, oh my.
As @seven-phases-max mentioned, it's useful to avoid errors in case you don't use the mixins in your code, since the media-queries will call the non-existent mixin.

Btw, the advantage of this technique is that combining media queries slows down compilation time (a bit).

@gyopiazza Thanks for the quick reply. I don't mind the compilation time, and for huge projects, I definitely prefer grouping all media queries at the bottom of the main stylesheet. I tried a few of the plugins but found your way the easiest for our use case and the most convenient. Thanks!!

@seven-phases-max Thanks, your answer makes sense. I use less a lot, but haven't yet understood the best way to achieve certain things!

Notice that also clean-css support @media merging since v3, and so does the less-plugin-clean-css

with main.less:

header {
    color: green;

    @media only screen (max-width: 500px) { color: red; }
}

footer {
    color: green;

    @media only screen (max-width: 500px) { color: red; }
}

lessc --clean-css="advanced" main.less outputs:
footer,header{color:green}@media only screen (max-width:500px){footer,header{color:red}}

less-plugin-clean-css sets the --skip-advanced true by default you should explicit set the advanced option for @media merging

@nfq With the "mixin approach" media queries are still compiled at the bottom (or anywhere you declare them).

@gyopiazza thanks, yeah. Happy with this approach!!

@bassjobsen I'll definitely use this on a bigger project. I haven't actually started using Less plugins yet. Thanks for the tips!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

chricken picture chricken  ·  6Comments

matthew-dean picture matthew-dean  ·  6Comments

renoth picture renoth  ·  6Comments

awebdev picture awebdev  ·  4Comments

rejas picture rejas  ·  6Comments