Less Extend() Not Working

LESS extend() not working

When we simply import a CSS file with the .css extension it will be treated as CSS and the @import statement left as-is.

For the extend feature to work, Less compiler has to interpret the imported file as a Less file. This can be done in two ways and they are as follows:

Option 1: (Using the (reference) directive)

Using the (reference) directive, allows for pulling in only the targeted/referenced styles from the external bootstrap library. Hence, it would result in a smaller file and is preferred when you are going to reference only few styles from a large library (like bootstrap). Note that this directive was introduced only with Less v1.5.0 and hence will not work in lower compiler versions.

@import (reference) 'less/bootstrap.less';

.filters {
.type {
button.selected:extend(.btn-success) {};
}
}

Option 2: (Using the (less) directive)

When the (less) directive is used, the Less compiler would treat the code present within the imported file as Less code (irrespective of the file extension) and hence would allow extending any rule-sets/classes specified within it. However, the drawback of using this directive is that the entire contents of the .css file (including classes that you may not require in the output file) will be copied to the output. This was introduced in Less v1.4.0.

@import (less) 'bootstrap.css';

.filters {
.type {
button.selected:extend(.btn-success) {};
}
}

LESS CSS Extend function not working as expected

This is a very weird kind of behavior and currently I don't have an explanation as to why it works this way but there is a workaround. It seems to work if we use the parent selector (&) at all levels before the actual nested selector (except the inner-most one).

Considering for how long the :extend() feature has existed and how common this scenario seems to be, it could possibly be an intended feature. I will add more explanation about the reason once I've managed to find it :)

.h3-text{
color: #424242;
font-size: 20px;
line-height: 28px;
}
#how-it-works {
& .timeline {
& > .timeline-step {
& > h3:extend(.h3-text) {};
}
}
}

Another approach that also seems to work is to avoid the & (parent selector) at all levels. It shouldn't be a big problem for this particular scenario because the & is basically doing nothing.

.h3-text{
color: #424242;
font-size: 20px;
line-height: 28px;
}
#how-it-works {
.timeline {
> .timeline-step {
> h3:extend(.h3-text) {};
}
}
}

Even :extend inside a ruleset works. Seems like the problem happens when :extend attached to a selector is used and the selector has & only at some levels.

.h3-text{
color: #424242;
font-size: 20px;
line-height: 28px;
}
#how-it-works {
.timeline {
& > .timeline-step {
& > h3 {
&:extend(.h3-text);
}
}
}
}

Less CSS :extend() doesn't work when an extra selector is attached to parent selector

There is a primary difference between the two extend modes that are given in the question and that is why one works while the other doesn't.

The first method (extending using parent selector (&)) is generally used by placing it within another selector block like:

.test{
a:a
}
#dummy{
&:extend(.test all);
}

This is called as Extend inside Ruleset and this generally does not require the curly braces {} at the end of the line. In fact, for this aproach, an error would be thrown only when the braces are provided.


The second method is called as Extend attached to a selector because here a new selector is being created by attaching an extra class to the parent selector and for this approach the curly braces are required mandatorily at the end. Compilation error would be thrown otherwise.

Including the curly braces like in the below snippet would cause the code to compile properly:

.test{
a:a
}

#dummy{
&.a_class:extend(.test all){};
}

In my opinion, curly braces are required for the 2nd method and not the 1st because without braces, the 2nd one is generally not how we write the rules in CSS - where every selector must have a body ({}) to contain its rules. The first selector already has its body because the extend itself is placed within it.

Does LESS have an extend feature?

Yes, Less.js introduced extend in v1.4.0.

:extend()

Rather than implementing the at-rule (@extend) syntax used by SASS and Stylus, LESS implemented the pseudo-class syntax, which gives LESS's implementation the flexibility to be applied either directly to a selector itself, or inside a statement. So both of these will work:

.sidenav:extend(.nav) {...}

or

.sidenav {
&:extend(.nav);
...
}

Additionally, you can use the all directive to extend "nested" classes as well:

.sidenav:extend(.nav all){};

And you can add a comma-separated list of classes you wish to extend:

.global-nav {
&:extend(.navbar, .nav all, .navbar-fixed-top all, .navbar-inverse);
height: 70px;
}

When extending nested selectors you should notice the differences:

nested selectors .selector1 and selector2:

.selector1 {
property1: a;
.selector2 {
property2: b;
}
}

Now .selector3:extend(.selector1 .selector2){}; outputs:

.selector1 {
property1: a;
}
.selector1 .selector2,
.selector3 {
property2: b;
}

, .selector3:extend(.selector1 all){}; outputs:

.selector1,
.selector3 {
property1: a;
}
.selector1 .selector2,
.selector3 .selector2 {
property2: b;
}

,.selector3:extend(.selector2){}; outputs

.selector1 {
property1: a;
}
.selector1 .selector2 {
property2: b;
}

and finally .selector3:extend(.selector2 all){};:

.selector1 {
property1: a;
}
.selector1 .selector2,
.selector1 .selector3 {
property2: b;
}

Less extend from imported file not working vs mixin from imported file

As @seven-phases-max pointed out, the problem is a differing version of the less preprocessor shipped in the GUI tool, which does not have the :extend feature.

Getting styles outside a media query with Less extend?

As you have correctly found out, it is currently not possible to extend the properties of a block that is outside the media query in a selector block that is within media query. I suspect the reason for this is that in simple terms all that the :extend does is to group selectors which cannot be done here. If the .col-2 is grouped within the media query, it creates a wrong behavior (and) a media query can't be added as a comma separated value in a selector group.

You can just call the required classes within the media query like in the below snippet.

Less Code:

@md: ~"only screen and (min-width: 48.9375em)";
.col-2 { width: 50%; }
.pull-left { float: left; }
.block1, .block2 {
@media @md {
.col-2;
.pull-left;
}
}

Output CSS:

.col-2 {
width: 50%;
}
.pull-left {
float: left;
}
@media only screen and (min-width: 48.9375em) {
.block1,
.block2 {
width: 50%;
float: left;
}
}

Not able to extend Less mixin

As mentioned in comments and discussed in the chat, Less currently does not support extending of mixins (there is a request and it might get addressed in v2.0.0 or later). (Note: To be more precise with wordings, extending of mixins which are not output in CSS is not supported.)

So, the best way forward would be to do the below:

Less:

.selected-dropdown-values { // just remove the braces
background-color: #505050;
color: #fff;
}

Just remove the braces from the .selected-dropdown-values rule. Ofcourse this would cause that rule also to be present in the CSS output but given that we are using extend, it would mean only one extra line in CSS output.

Output:

.selected-dropdown-values,
.selected-values,
.selected-values a {
background-color: #505050;
color: #fff;
}

Extend function doesn't work with nested selectors

Reason:

Quoting Less Website: emphasis is mine

Extend by default looks for exact match between selectors. It does matter whether selector uses leading star or not. It does not matter that two nth-expressions have the same meaning, they need to have to same form in order to be matched. The only exception are quotes in attribute selector, less knows they have the same meaning and matches them.

The following would work because the parameter to the extend function exactly matches another selector that is already defined. (Note: I have removed some properties to keep it simple.)

.gradientBg { background: rgb(200, 200, 200); }
.gr-box, .btn-1 { &:extend(.gradientBg); }

But the below would not work because the compiled selector path of the nested selector would be .parent .gradientBg and the parameter provided to the extend function is not an exact match.

.parent{
.gradientBg { background: rgb(200, 200, 200); }
}
.gr-box, .btn-1 { &:extend(.gradientBg); }

Less compiler would not even throw an error in the above scenario because it fails silently when there is no match.



Solution:

When using extend feature to extend nested selectors, an exactly matching selector (full selector path) should be provided (or) the all keyword should be used.

The below would work as the full matching selector path is provided to the extend function.

.parent{
.gradientBg { background: rgb(200, 200, 200); }
}
.gr-box, .btn-1 { &:extend(.parent .gradientBg); }

Or, depending on your needs even this would work because the all keyword is used.

.parent{
.gradientBg { background: rgb(200, 200, 200); }
}
.gr-box, .btn-1 { &:extend(.gradientBg all); }

But note how the output selector has the structure .parent .gr-box and .parent .btn-1. This is because Less replaces only the matched part of the selector with the new selector. In .parent .gradientBg (original selector), the matched part (provided as parameter to extend) is only .gradientBg and hence the output selector after extend would be .parent .gr-box.

Here is what the Less website says about extend "all": emphasis is mine

When you specify the all keyword last in an extend argument it tells Less to match that selector as part of another selector. The selector will be copied and the matched part of the selector only will then be replaced with the extend, making a new selector.

Another thing that should also be noted when using all keyword is that, Less would extend properties of all selectors that have a matching part. So, for example if we consider the below Less code.

.parent{
.gradientBg { background: rgb(200, 200, 200); }
}
.parent2{
.gradientBg{ color: red; }
}
.gr-box, .btn-1 { &:extend(.gradientBg all); }

the output would be as follows because .gradientBg selector is used both within .parent and .parent2.

.parent .gradientBg, .parent .gr-box, .parent .btn-1 {
background: #c8c8c8;
}
.parent2 .gradientBg, .parent2 .gr-box, .parent2 .btn-1 {
color: red;
}


Related Topics



Leave a reply



Submit