Search code examples
csslessless-mixins

Name clash between Less mixin and CSS selector


I have this simplified Less script

.placeholder(@color: #333333) {
    &::-webkit-input-placeholder  { color: @color; }
}

input {
    .placeholder();
}

.placeholder {
    margin-top: 20px;
}

The output when I run this through my local compiler or winless online less compiler is

input {
  margin-top: 20px;
}
input::-webkit-input-placeholder {
  color: #333333;
}
.placeholder {
  margin-top: 20px;
}

Insted of the desired output

input::-webkit-input-placeholder {
  color: #333333;
}
.placeholder {
  margin-top: 20px;
}

Is this a bug or am I missing something here?

By the result it looks to me like I can't have CSS-selectors with the same name as mixins with default values.

I'm running into this problem when compiling Bootstrap with my site specific code. In this particular case I can work around it, but as the project grows and I include other projects I can't imaging I have to keep track of any mixins with default values?

Edit: I see now that I should have read the manual and pretty much seen on the first page of the docs that everything can be treated as a mixin.


Solution

  • In Less, everything is technically a mixin irrespective of whether we write it with parantheses (as in with parameters) or without parantheses (as in like a CSS class selector). The only difference between the two is that when the parantheses are present, the properties present within it are not output unless called from within a selector block.

    Quoting the Less Website:

    It is legal to define multiple mixins with the same name and number of parameters. Less will use properties of all that can apply.

    In this case, since the other mixin has a default value for its only parameter, both the properties can apply when called without any parameter and hence there is no way to avoid it from happening.

    Workaround Solution: One possible solution to work-around this problem is to enclose all such conflicting rules within a parent selector (like body).

    .placeholder(@color: #333333) {
        &::-webkit-input-placeholder  { color: @color; }
    }
    
    input {
        .placeholder();
    }
    
    body{
        .placeholder{
            margin-top: 20px;
        }
    }
    

    Compiled CSS:

    input::-webkit-input-placeholder {
        color: #333333;
    }
    body .placeholder {
        margin-top: 20px;
    }
    

    Option 2: Extracted from the solution posted by seven-phases-max in the Less GitHub Issue thread.

    For the particular use-case one of possible workarounds is to isolate conflicting classes in unnamed scope so they won't interfere with external names:

    .placeholder(@color: #333333) {
        &::-webkit-input-placeholder  { color: @color; }
    }
    
    input {
        .placeholder();
    }
    
    & { // unnamed namespace
        .placeholder {
            background: #ffffff;
        }
    } // ~ end of unnamed namespace
    

    Note: The above is a straight copy/paste from the GitHub thread without any modifications so as to not tamper with the information.