LESS CSS: abusing the & Operator when nesting?
I also have been pondering this use further since we encountered it in that question you referenced. While I cannot answer definitively that this is a "bug" use for &
(BoltClock seems to make a good argument that it is not a bug), I want to argue for the value of it (which argues it is not a bug from a logical standpoint).
However, before the logical argument, I did find another short, simple quote (in the "nested rules" section) that seems to indicate it is at least not unintended: "&
represents the current selector parent." That's it. As BoltClock argues, whether prepending or appending seems irrelevant. All that was intended was that it be a placeholder for that "selector parent" that is current up to that point. The fact that it is always mentioned in conjunction with the "nesting" use of the language implies it is designed to designate the full selector string of the nest up to the point of the nest that it resides within. How that string is used (to pre- or append) would seem up to the coder.
Now, you mention (and previously mentioned) that you "would never code this way," and yet I find myself seeing what appears to be a very valuable use for this. Consider the following argument.
The HTML Setup for the Illustration
Assume, for the sake of illustration, there is a dynamic setting of three possible classes ('light', 'medium', 'dark' themes) on the body
element that change the "look" of the site. We have
two columns, and some various types of links we want to style (textLink
, picLink
, textWithIconLink
) differently in each column for each theme.
<body class="light">
<div class="leftCol">
<a class="textLink"></a>
<a class="picLink"></a>
<a class="textWithIconLink"></a>
</div>
<div class="rightCol">
<a class="textLink"></a>
<a class="picLink"></a>
<a class="textWithIconLink"></a>
</div>
</body>
Now the questions to ask are, just looking at the links, of the two following methods, which...
- Is less code in LESS
- Best organzies the code in LESS
- Outputs less code in CSS
- Best organizes the outputted CSS
"Best" may be somewhat subjective. I'll let you weigh that evidence yourself from below.
Option #1 Typical Nesting
LESS (approx. 99 lines of code)
/*Light Color Theme */
.light {
.leftCol {
.textLink {
color: fooL1;
&:hover { color: barL1;}
}
.picLink {
background-image: url(/fooL1.jpg);
&:hover { background-image: url(/barL1.jpg);}
}
.textWithIconLink {
color: fooL2;
background-image: url(/fooL2.jpg);
&:hover { color: barL2; background-image: url(/barL2.jpg);}
}
}
.rightCol {
.textLink {
color: fooL3;
&:hover { color: barL3;}
}
.picLink {
background-image: url(/fooL3.jpg);
&:hover { background-image: url(/barL3.jpg);}
}
.textWithIconLink {
color: fooL4;
background-image: url(/fooL4.jpg);
&:hover { color: barL4; background-image: url(/barL4.jpg);}
}
}
}
/*Medium Color Theme */
.medium {
.leftCol {
.textLink {
color: fooM1;
&:hover { color: barM1;}
}
.picLink {
background-image: url(/fooM1.jpg);
&:hover { background-image: url(/barM1.jpg);}
}
.textWithIconLink {
color: fooM2;
background-image: url(/fooM2.jpg);
&:hover { color: barM2; background-image: url(/barM2.jpg);}
}
}
.rightCol {
.textLink {
color: fooM3;
&:hover { color: barM3;}
}
.picLink {
background-image: url(/fooM3.jpg);
&:hover { background-image: url(/barM3.jpg);}
}
.textWithIconLink {
color: fooM4;
background-image: url(/fooM4.jpg);
&:hover { color: barM4; background-image: url(/barM4.jpg);}
}
}
}
/*Dark Color Theme */
.dark {
.leftCol {
.textLink {
color: fooD1;
&:hover { color: barD1;}
}
.picLink {
background-image: url(/fooD1.jpg);
&:hover { background-image: url(/barD1.jpg);}
}
.textWithIconLink {
color: fooD2;
background-image: url(/fooD2.jpg);
&:hover { color: barD2; background-image: url(/barD2.jpg);}
}
}
.rightCol {
.textLink {
color: fooD3;
&:hover { color: barD3;}
}
.picLink {
background-image: url(/fooD3.jpg);
&:hover { background-image: url(/barD3.jpg);}
}
.textWithIconLink {
color: fooD4;
background-image: url(/fooD4.jpg);
&:hover { color: barD4; background-image: url(/barD4.jpg);}
}
}
}
CSS Output (approx. 87 lines of output, organized by theme of course)
/*Light Color Theme */
.light .leftCol .textLink { color:fooL1; }
.light .leftCol .textLink:hover { color:barL1; }
.light .leftCol .picLink { background-image:url(/fooL1.jpg); }
.light .leftCol .picLink:hover { background-image:url(/barL1.jpg); }
.light .leftCol .textWithIconLink {
color:fooL2;
background-image:url(/fooL2.jpg);
}
.light .leftCol .textWithIconLink:hover {
color:barL2;
background-image:url(/barL2.jpg);
}
.light .rightCol .textLink { color:fooL3; }
.light .rightCol .textLink:hover { color:barL3; }
.light .rightCol .picLink { background-image:url(/fooL3.jpg); }
.light .rightCol .picLink:hover { background-image:url(/barL3.jpg); }
.light .rightCol .textWithIconLink {
color:fooL4;
background-image:url(/fooL4.jpg);
}
.light .rightCol .textWithIconLink:hover {
color:barL4;
background-image:url(/barL4.jpg);
}
/*Medium Color Theme */
.medium .leftCol .textLink { color:fooM1; }
.medium .leftCol .textLink:hover { color:barM1; }
.medium .leftCol .picLink { background-image:url(/fooM1.jpg); }
.medium .leftCol .picLink:hover { background-image:url(/barM1.jpg); }
.medium .leftCol .textWithIconLink {
color:fooM2;
background-image:url(/fooM2.jpg);
}
.medium .leftCol .textWithIconLink:hover {
color:barM2;
background-image:url(/barM2.jpg);
}
.medium .rightCol .textLink { color:fooM3; }
.medium .rightCol .textLink:hover { color:barM3; }
.medium .rightCol .picLink { background-image:url(/fooM3.jpg); }
.medium .rightCol .picLink:hover { background-image:url(/barM3.jpg); }
.medium .rightCol .textWithIconLink {
color:fooM4;
background-image:url(/fooM4.jpg);
}
.medium .rightCol .textWithIconLink:hover {
color:barM4;
background-image:url(/barM4.jpg);
}
/*Dark Color Theme */
.dark .leftCol .textLink { color:fooD1; }
.dark .leftCol .textLink:hover { color:barD1; }
.dark .leftCol .picLink { background-image:url(/fooD1.jpg); }
.dark .leftCol .picLink:hover { background-image:url(/barD1.jpg); }
.dark .leftCol .textWithIconLink {
color:fooD2;
background-image:url(/fooD2.jpg);
}
.dark .leftCol .textWithIconLink:hover {
color:barD2;
background-image:url(/barD2.jpg);
}
.dark .rightCol .textLink { color:fooD3; }
.dark .rightCol .textLink:hover { color:barD3; }
.dark .rightCol .picLink { background-image:url(/fooD3.jpg); }
.dark .rightCol .picLink:hover { background-image:url(/barD3.jpg); }
.dark .rightCol .textWithIconLink {
color:fooD4;
background-image:url(/fooD4.jpg);
}
.dark .rightCol .textWithIconLink:hover {
color:barD4;
background-image:url(/barD4.jpg);
}
Option #2 End Target Grouping
I've named it "End Target Grouping," because that's really how I see using the &
in this other way of adding parent selectors. One is now coding based off the final end target element that is actually being styled.
LESS (approx. 88 lines of code)
/*Links */
/*Text Links*/
.textLink {
.light .leftCol & {
color: fooL1;
&:hover { color: barL1;}
}
.light .rightCol & {
color: fooL3;
&:hover { color: barL3;}
}
.medium .leftCol & {
color: fooM1;
&:hover { color: barM1;}
}
.medium .rightCol & {
color: fooM3;
&:hover { color: barM3;}
}
.dark .leftCol & {
color: fooD1;
&:hover { color: barD1;}
}
.dark .rightCol & {
color: fooD3;
&:hover { color: barD3;}
}
}
/*Picture Links */
.picLink {
.light .leftCol & {
background-image: url(/fooL1.jpg);
&:hover { background-image: url(/barL1.jpg);}
}
.light .rightCol & {
background-image: url(/fooL3.jpg);
&:hover { background-image: url(/barL3.jpg);}
}
.medium .leftCol & {
background-image: url(/fooM1.jpg);
&:hover { background-image: url(/barM1.jpg);}
}
.medium .rightCol & {
background-image: url(/fooM3.jpg);
&:hover { background-image: url(/barM3.jpg);}
}
.dark .leftCol & {
background-image: url(/fooD1.jpg);
&:hover { background-image: url(/barD1.jpg);}
}
.dark .rightCol & {
background-image: url(/fooD3.jpg);
&:hover { background-image: url(/barD3.jpg);}
}
}
/*Text with Icon Links */
.textWithIconLink {
.light .leftCol & {
color: fooL2;
background-image: url(/fooL1.jpg);
&:hover { color: barL2; background-image: url(/barL1.jpg);}
}
.light .rightCol & {
color: fooL4;
background-image: url(/fooL3.jpg);
&:hover { color: barL4; background-image: url(/barL3.jpg);}
}
.medium .leftCol & {
color: fooM2;
background-image: url(/fooM1.jpg);
&:hover { color: barM2; background-image: url(/barM1.jpg);}
}
.medium .rightCol & {
color: fooM4;
background-image: url(/fooM3.jpg);
&:hover { color: barM4; background-image: url(/barM3.jpg);}
}
.dark .leftCol & {
color: fooD2;
background-image: url(/fooD1.jpg);
&:hover { color: barD2; background-image: url(/barD1.jpg);}
}
.dark .rightCol & {
color: fooD4;
background-image: url(/fooD3.jpg);
&:hover { color: barD4; background-image: url(/barD3.jpg);}
}
}
CSS (approx. 88 lines of output [due to one extra comment], organized by end target element; but notice, there is still a suborganization by theme because of the class structure)
/*Links*/
/*Text Links*/
.light .leftCol .textLink { color:fooL1; }
.light .leftCol .textLink:hover { color:barL1; }
.light .rightCol .textLink { color:fooL3; }
.light .rightCol .textLink:hover { color:barL3; }
.medium .leftCol .textLink { color:fooM1; }
.medium .leftCol .textLink:hover { color:barM1; }
.medium .rightCol .textLink { color:fooM3; }
.medium .rightCol .textLink:hover { color:barM3; }
.dark .leftCol .textLink { color:fooD1; }
.dark .leftCol .textLink:hover { color:barD1; }
.dark .rightCol .textLink { color:fooD3; }
.dark .rightCol .textLink:hover { color:barD3; }
/*Picture Links */
.light .leftCol .picLink { background-image:url(/fooL1.jpg); }
.light .leftCol .picLink:hover { background-image:url(/barL1.jpg); }
.light .rightCol .picLink { background-image:url(/fooL3.jpg); }
.light .rightCol .picLink:hover { background-image:url(/barL3.jpg); }
.medium .leftCol .picLink { background-image:url(/fooM1.jpg); }
.medium .leftCol .picLink:hover { background-image:url(/barM1.jpg); }
.medium .rightCol .picLink { background-image:url(/fooM3.jpg); }
.medium .rightCol .picLink:hover { background-image:url(/barM3.jpg); }
.dark .leftCol .picLink { background-image:url(/fooD1.jpg); }
.dark .leftCol .picLink:hover { background-image:url(/barD1.jpg); }
.dark .rightCol .picLink { background-image:url(/fooD3.jpg); }
.dark .rightCol .picLink:hover { background-image:url(/barD3.jpg); }
/*Text with Icon Links */
.light .leftCol .textWithIconLink {
color:fooL2;
background-image:url(/fooL1.jpg);
}
.light .leftCol .textWithIconLink:hover {
color:barL2;
background-image:url(/barL1.jpg);
}
.light .rightCol .textWithIconLink {
color:fooL4;
background-image:url(/fooL3.jpg);
}
.light .rightCol .textWithIconLink:hover {
color:barL4;
background-image:url(/barL3.jpg);
}
.medium .leftCol .textWithIconLink {
color:fooM2;
background-image:url(/fooM1.jpg);
}
.medium .leftCol .textWithIconLink:hover {
color:barM2;
background-image:url(/barM1.jpg);
}
.medium .rightCol .textWithIconLink {
color:fooM4;
background-image:url(/fooM3.jpg);
}
.medium .rightCol .textWithIconLink:hover {
color:barM4;
background-image:url(/barM3.jpg);
}
.dark .leftCol .textWithIconLink {
color:fooD2;
background-image:url(/fooD1.jpg);
}
.dark .leftCol .textWithIconLink:hover {
color:barD2;
background-image:url(/barD1.jpg);
}
.dark .rightCol .textWithIconLink {
color:fooD4;
background-image:url(/fooD3.jpg);
}
.dark .rightCol .textWithIconLink:hover {
color:barD4;
background-image:url(/barD3.jpg);
}
Concluding Thoughts
A few other considerations:
First, most of your theme colors (and possibly other themeing aspects) will be set up with variables, which can be grouped at the top of the LESS code by theme even using Option #2 above--so having the theme structure for the output CSS itself scattered in the code is not necessarily bad.
Second, any "standard" code for a type of element is defined above any theme code. My examples did not show this, but say all .textLink
elements had text-decoration: none;
set. That would occur once under Option #2 without any further selector code, and would appear above all the theme changes below. For Option #1, I need to set up a new, unnested .textLink
selector (at least one other line of code) to apply it to all themed links, and that "base" code for the class will, again, be somewhere unrelated to where the rest of the code for the theme link info is.
Third, as a developer, if I am having issues with my picLinks
(a specific type of element on my page), Option #2 makes it far easier to examine my code for the element I am having issues with, as all my code for all the themes is right in one spot.
Obviously, how one wants the final LESS and CSS organized is going to be a major factor in how one sees the value of this. My point here is to merely demonstrate that there is a very useful, logical reason to use the &
in this way of adding parent level selectors to the &
reference.
It's not abuse of the &
combinator at all; you can place it anywhere in a nested selector and it'll be substituted with whatever is above it (its so-called parent selector):
[The
&
combinator is] used when you want a nested selector to be concatenated to its parent selector, instead of acting as a descendant.
Notice that it says "concatenated"; it doesn't say you can only either prepend or append the parent selector to the nested selector. Concatenation doesn't work in any specific direction only.
Further, the word "descendant" has to do with the nature of nested selectors being treated as though they were linked by the descendant combinator by default. In no way does that inherently limit the use of &
to descendants only, nor does it imply that the parent selector has to represent a parent or ancestor element in such a way that the nested selector may only be appended to it, and not prepended or even inserted into the middle of it.