Search code examples
csslessless-mixins

Css Less Mixin not outputting all media query code


I have a mixin - it's a litte more complicated than anything I've done before, so now I've come across a problem that I can't get to the bottom of. The problem is that the default never gets created. I've also explicitly added the mobile first media query but this doesn't work either.

.fluid-margin(@top; @right; @bottom; @left;)
{
    .make-margin(@xs-gutter-width);

    @media @sm-screen {
        .make-margin(@sm-gutter-width);
    }
    @media @md-screen {
        .make-margin(@md-gutter-width);
    }
    @media @lg-screen {
        .make-margin(@lg-gutter-width);
    }

    .make-margin(@scale)
    {
        .make-margin(@scale) when (@top > 0)
        {
            margin-top:@scale * @top;
        }
        .make-margin(@scale) when (@right > 0)
        {
            margin-right:@scale * @right;
        }
        .make-margin(@scale) when (@bottom > 0)
        {
            margin-bottom:@scale * @bottom;
        }
        .make-margin(@scale) when (@left > 0)
        {
            margin-left:@scale * @left;
        }
    }
}

Output

.list-section-header {
  overflow: hidden;
  text-align: center;
}
@media only screen and (min-width:600px) and (max-width:899px) {
  .list-section-header {
    margin-top: 2.25rem;
    margin-bottom: 2.25rem;
  }
}
@media only screen and (min-width:900px) and (max-width:1199px) {
  .list-section-header {
    margin-top: 2.25rem;
    margin-bottom: 2.25rem;
  }
}
@media only screen and (min-width:1200px) and (max-width:1280px) {
  .list-section-header {
    margin-top: 3rem;
    margin-bottom: 3rem;
  }

Calling class

.fluid-margin(1.5rem; 0; 1.5rem; 0);

Solution

  • Behavior Explanation:

    In your mixin code, there is one outer (parent) .make-margin(@scale) mixin which has nothing but some guarded mixins present inside it. Typically when a mixin is called, only the properties which are present within it or those that are part of any selector blocks are output. Here there is nothing to output when the mixin is called for the first time as the contents of the parent mixin are only additional mixins as stated earlier.

    When the mixin is called for the first time (the call for the default settings), it scopes all contents of the .make-margin() parent mixin into the .fluid-margin() mixin and thereby exposes all its child mixins. Because of this exposure, when you call it for the second (and subsequent) time(s) from within the other media queries, the compiler is able to process them and produce the required output.

    Basically, the first mixin call (the default) does nothing other than exposing its contents to the caller scope.


    Behavior Verification:

    The above statement can be verified by doing any one of the below.

    1. Comment out the first mixin call (the one to output the default settings) and you will notice that even the other media queries do not produce any output. This is because the guarded child mixins are not visible to the .fluid-margin().
    2. Repeat the default mixin call (.make-margin(@xs-gutter-width);) directly below the first one and you will notice that it does produce the default output also because the child mixins are now exposed to the .fluid-margin().
    3. Add another .make-margin(@scale) line within the parent .make-margin() mixin. This will also produce the default output because the child mixins are in the same scope and hence the mixin call is able to reach them.

    Possible Solutions:

    Option 1: Avoid the parent .make-margin(@scale) nesting totally as it is not needed. You can directly expose its child mixins to the .fluid-margin mixin. A sample for this approach can be found here.

    .make-margin(@scale) when (@top > 0){
        margin-top: @scale * @top;
    }
    .make-margin(@scale) when (@right > 0){
        margin-right: @scale * @right;
    }
    .make-margin(@scale) when (@bottom > 0){
        margin-bottom: @scale * @bottom;
    }
    .make-margin(@scale) when (@left > 0){
        margin-left: @scale * @left;
    }
    

    Option 2: Instead of using another mixin definition within the parent, just use & (parent selector) for the guards like below. Here the & would refer to the selector from within which the mixin was called. Since the properties specified under & will be part of a selector block and also because there are no parentheses attached to it, it would still produce the output. Sample for this approach could be found here.

    .make-margin(@scale)
    {
        & when (@top > 0){
            margin-top: @scale * @top;
        }
        & when (@right > 0){
            margin-right: @scale * @right;
        }
        & when (@bottom > 0){
            margin-bottom: @scale * @bottom;
        }
        & when (@left > 0){
            margin-left: @scale * @left;
        }
    }
    

    The second option is much more cleaner (as you have also alluded to in comments).