user3803100 user3803100 - 5 months ago 12
CSS Question

Extend function doesn't work with nested selectors

I have some problems with Less

extend
function. In some cases it doesn't work and I can't figure out why. There is a piece of problematic code:

.page-header {
.bar:extend(.fixedElement) { // it works
...
}
.menu:extend(.fixedElement) { // it works
...
}
// some other stuff
.menu-list {
...
.btnStyle {
padding: 18px 20px;
min-height: 60px;
width: 100%;
display: block;
border-bottom: 1px solid @light2;
}
.linkStyle:extend(.btnStyle) { // doesn't work
//.btnStyle;
background-color: @light1;
.remCalc(14);
&:active {
background-color: transparent;
}
}
.chosen {
background-color: transparent;
&:after {
color: @primaryColor;
}
}
...
.level-2 {
& > ul {
a:extend(.linkStyle) { // doesn't work
//.linkStyle;
}
}
}
.level-3 {
...
& > ul > li {
.btn-menu {
&.shown:extend(.chosen) { // doesn't work
//.chosen;
}
}
& > a:extend(.linkStyle) { // doesn't work
//.linkStyle;
}
}
.selected {
.btn-menu:extend(.chosen) { // doesn't work
//.chosen;
}
}
}
.btn-menu:extend(.btnStyle) { // doesn't work
//.btnStyle;
...
}
}
}


And:

.gradientBg {
background: rgb(200, 200, 200);
background: -webkit-linear-gradient(295deg, rgb(200, 200, 200) 10%, rgb(255, 255, 255) 90%);
background: -o-linear-gradient(295deg, rgb(200, 200, 200) 10%, rgb(255, 255, 255) 90%);
background: linear-gradient(25deg, rgb(200, 200, 200) 10%, rgb(255, 255, 255) 90%);
border: 1px solid @light2;
}

.gr-box, .btn-1 {
&:extend(.gradientBg); //it works
}


Elements extended by
.fixedElement
or
.gradientBg
compile to CSS normally, but not these with
.btnStyle
,
.linkStyle
and
.chosen
(they just aren't in CSS; now it works only by every time placed mixins). There aren't any errors during compilation.

I use Brackets 1.3 with Less Autocompile 1.1.9 extension. What am I doing wrong?

Answer

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;
}