Build list of selectors with LESS
As already mentioned your attempt is almost there, it does not work because of variable visibility rules.
Notice that each .selector-list
iteration defines new @selector-list
variable which have higher precedence in the current scope but does not override @selector-list
variables for the outer scope (i.e. scopes of the previous .selector-list
iterations and above). So when you use @selector-list
after the initial .selector-list
call you get the value set in the "highest".selector-list
iteration (i.e. the first one with @i = 1
).
To "return" a value from the last iteration of a recursive loop you need to define a variable with this value only within that last iteration. Usually the simplest way to do this is to provide a "terminal" mixin (i.e. a specialization for the last call of the recursive mixin). In fact you would need such terminal anyway to handle the final comma of the list. E.g.:
#1
.selector-list(@parent, @children, @i: 1, @list: "") when (@i < length(@children)) {
@child: extract(@children, @i);
.selector-list(@parent, @children, (@i + 1), "@{list} @{parent} @{child},");
}
.selector-list(@parent, @children, @i, @list) when (@i = length(@children)) {
@child: extract(@children, @i);
@selector-list: e("@{list} @{parent} @{child}");
}
// usage:
@text-elements: p, ul, ol, table;
.selector-list("body.single .entry-content", @text-elements);
@{selector-list} {
line-height: 1.8;
}
-
#2 Same as above just slightly "optimized":
.selector-list(@parent, @children, @i: length(@children), @list...) when (@i > 1) {
.selector-list(@parent, @children, (@i - 1),
e(", @{parent}") extract(@children, @i) @list);
}
.selector-list(@parent, @children, 1, @list) {
@selector-list: e(@parent) extract(@children, 1) @list;
}
// usage:
@text-elements: p, ul, ol, table;
.selector-list("body.single .entry-content", @text-elements);
@{selector-list} {
line-height: 1.9;
}
-
#3 Speaking of the use-case in general, a "string-based selector manipulation" is not always a good idea in context of Less. The main problem is that Less does not treat such strings as "native" selectors and most of advanced Less features won't work with them (e.g. Less won't recognize ,
, &
and similar elements there so such rules can't be nested, extend
also can't see such selectors etc. etc.).
An alternative, more "Less-friendly" approach is to define such list as a mixin rather than a variable, e.g.:
.text-elements(@-) {p, ul, ol, table {@-();}}
body.single .entry-content {
.text-elements({
line-height: 1.8;
});
}
and (when you also need to reuse body.single .entry-content /.text-elements/
):
.text-elements(@-) {
p, ul, ol, table
{@-();}}
.selector-list(@-) {
body.single .entry-content {
.text-elements(@-);
}
}
.selector-list({
line-height: 1.9;
});
etc.
-
P.S. Also, speaking in even more general, don't miss that in Less a media query may be put into selector ruleset, so depending on a use-case it's also often more easy to write a list of selectors once and set media depended styles inside it (i.e. doing it in opposite to the standard CSS method where you have to repeat same selector(s) for each media query).
Generating selector lists in LESS
In Less 1.4.0+ you can :extend
a placeholder class:
.dp-list-item {
display: list-item;
}
.label-color {
color: red;
}
// This function generates selectors and corresponding css
.gen (@tag) {
@sel: ~"#@{tag}:checked ~ ul > li";
@{sel} {
&[data-index~=@{tag}]:extend(.dp-list-item){}
& > label[for=@{tag}]:extend(.label-color){}
}
}
// Generate required selectors and css
.gen("foo");
.gen("bar");
.gen("baz");
Forked pen
Generated CSS:
.dp-list-item,
#foo:checked ~ ul > li[data-index~="foo"],
#bar:checked ~ ul > li[data-index~="bar"],
#baz:checked ~ ul > li[data-index~="baz"] {
display: list-item;
}
.label-color,
#foo:checked ~ ul > li > label[for="foo"],
#bar:checked ~ ul > li > label[for="bar"],
#baz:checked ~ ul > li > label[for="baz"] {
color: red;
}
As you can see, the only drawback is that both classes which I've used as placeholders will be in the generated CSS. I believe this can't be worked around until Less implements something akin to Sass' placeholder selectors.
ps. I've omitted the global li
rules which are not part of the mixin for brevity.
As per OP request, here is a Sass (with .scss
syntax) equivalent:
//placeholder selectors
%dp-list-item {
display: list-item;
}
%label-color {
color: red;
}
// This function generates selectors and corresponding css
@mixin gen($tag) {
##{$tag}:checked ~ ul > li {
&[data-index~=#{$tag}] {
@extend %dp-list-item;
}
& > label[for=#{$tag}] {
@extend %label-color;
}
}
}
// Generate required selectors and css
@each $item in (foo bar baz) {
@include gen($item);
}
Demo
Generated CSS:
#foo:checked ~ ul > li[data-index~=foo], #bar:checked ~ ul > li[data-index~=bar], #baz:checked ~ ul > li[data-index~=baz] {
display: list-item;
}
#foo:checked ~ ul > li > label[for=foo], #bar:checked ~ ul > li > label[for=bar], #baz:checked ~ ul > li > label[for=baz] {
color: red;
}
You can see that Sass' syntax is rather verbose when compared to Less. Sass also has some nice features such as control directives and excellent interpolation which I've applied in the example above.
multiple nested selectors with variables in Less
#1
Just yet one more solution in addition to @helderdarocha's answer and those given in https://stackoverflow.com/a/23954580/2712740. Maybe be this one could look a bit more clear:
// define header list as usual just
// put a mixin call with some predefined name there
h1, h2, h3, h4, h5, h6 {.headings}
// now to add styles/childs to the header list just add the mixin definitions:
.headings() {
some: rule;
}
.headings() {
a {color: inherit}
}
.headings() {
span {another: rule}
}
// etc.
The limitation of this solution is that h1, h2, h3 ... {}
and .headings
should be defined at the same level. Additionally, it's important to keep in mind that all these styles will output to CSS at the point of h1, h2, h3 ... {}
definition not at the point of .headings
definitions, so it may break your cascading overrides if you have some).
#2
The alt. solution I'm copy-pasting from https://stackoverflow.com/a/23954580/2712740 #3, basicaly it's the same as #1 but w/o its limitations (just having more special scary symbols):
// the "variable":
.headings(@-) {
h1, h2, h3, h4, h5, h6
{@-();}}
// usage:
.headings({
some: rule;
});
.headings({
a {color: inherit}
});
.headings({
span {another: rule}
});
//etc.
How to use begin with selector in Less
It is not working because your syntax seems to be wrong and not because of any issues with Less.
The below code is invalid because of the .
present between the label
and the class^="label-"]
. Attribute selectors do not require a .
before them. It is necessary only for class selectors.
.label.[class^="label-"]{
background-color: rgba(0,37,100,0.4);
}
The correct version would be the following:
.label[class^="label-"]{
background-color: rgba(0,37,100,0.4);
}
and so in Less terms, if you want nesting, it would be as follows:
.label{
&[class^='label-']{
background-color: rgba(0,37,100,0.4);
}
}
.label.[class^="label-"] { /* this won't work */ background-color: rgba(0, 37, 100, 0.4);}
.label[class^="label-"] { /* this will */ color: green;}
<label class='label-a label'>Label A</label><label class='label-b label'>Label B</label>
Add a value to a list in Less CSS
You can not do that. Less variables use the last declaration wins rule. A variable referencing itself will create a loop, the assigned value becomes the last declared, and so on.
You can use a loop to build a list which you can assign as a value to a property.
The LESS loops used to generate column classes in twitter - How do they work? shows you how to build a list of selectors.
Build a repetitive selector within a Less loop
I would go about it somehow in this manner:
.generateClasses (@index, @n, @in:"") when (@index > 0) {
@concatenate: "@{in} .repeatedClass";
@selector: ~".staticClass @{concatenate} > .finalStaticClass";
@{selector}{ height: unit(@n,px) };
.generateClasses((@index - 1), (unit(@n) + 10), @concatenate);
}
.generateClasses(0, @n, @in){};
.generateClasses(4, 10px);
Where you pass on to the next loop the concatenated generated classes and each time add another class. The @index
is the counter for the loop, and @n
is the value that you want to increase.
CSS output:
.staticClass .repeatedClass > .finalStaticClass {
height: 10px;
}
.staticClass .repeatedClass .repeatedClass > .finalStaticClass {
height: 20px;
}
.staticClass .repeatedClass .repeatedClass .repeatedClass > .finalStaticClass {
height: 30px;
}
.staticClass .repeatedClass .repeatedClass .repeatedClass .repeatedClass > .finalStaticClass {
height: 40px;
}
Edit - for older versions of Less:
in Less <= 1.3.3, you need to include the individual concatenating loops in a separate role (it is called .test
in the example below), that confines the variable. Then you can loop through this, doing something along these lines:
.generateClasses (@index, @n, @in:"") when (@index > 0) {
@concatenate: "@{in} .repeatedClass";
@selector: ~".staticClass @{concatenate} > .finalStaticClass";
.generateClasses((@index - 1), (unit(@n) + 10), @concatenate);
}
.generateClasses(0, @n, @in){};
.test(@i, @ni){
.generateClasses(@i,@ni);
@{selector} {
height: @ni;
}
}
.printClasses(@i:1,@ni:10px) when (@i > 0) {
.test(@i,@ni*@i);
.printClasses(@i - 1,@ni);
}
.printClasses(4);
output CSS will now be:
.staticClass .repeatedClass .repeatedClass .repeatedClass .repeatedClass > .finalStaticClass {
height: 40px;
}
.staticClass .repeatedClass .repeatedClass .repeatedClass > .finalStaticClass {
height: 30px;
}
.staticClass .repeatedClass .repeatedClass > .finalStaticClass {
height: 20px;
}
.staticClass .repeatedClass > .finalStaticClass {
height: 10px;
}
if you just need to generate a selector once at a time, you can skip the second loop and just call the .test()
mixin wherever you need it.
Less: Combining selector variables
(almost copy-pasting from the more wide https://stackoverflow.com/a/23954580/271274)
There're two problems with your attempt:
- By definition a content of escaped strings is not a subject for any kind of evaluation at all, so commas (as well as any other special ops) have no meaning there.
- Variable interpolation in selectors assumes a single interpolated variable contains only a single selector element. So, strictly speaking, even
~"h1.someclass > a"
is already nothing but a hack expected to have side-effects and unspecified/undefined behaviour for anything but extremely trivial cases.
So in your code above the value of @titles
works just as a simple/single selector element (the same as body
for example).
I.e. in summary and in general, "string-based selector manipulation" (like ~"@{title1}, @{title2}"
) should be avoided where possible simply because in Less selectors are not strings and strings are not selectors (nor they automatically converted to each other except in, yet again, certain extremely trivial cases).
So far the only non-hackish method to define a reusable list of selectors in Less is a mixin (mixins can be considered as "variables" too even if they have another syntax) that puts an arbitrary set of rules into a ruleset having the said list as its selector. E.g. for your example above it would be something like:
@title1: ~"h1.someclass > a";
@title2: ~"h1.otherclass > a";
.titles(@rules) {
@{title1}, @{title2} {@rules();}
}
// usage:
.titles({
&:after {
display: none;
}
});
Demo.
LESS: Variables that contains multiple selector for use inside another selector
See also: https://github.com/less/less.js/issues/2263
If you are enable to split @var into two (or more) separated variables you can use the following Less code:
@var1: ~"> a";
@var2: ~"> a:hover";
body > header {@{var1},@{var2} { > strong > em {color:red;}}}
The preceding will compile into the following CSS code:
body > header > a > strong > em,
body > header > a:hover > strong > em {
color: red;
}
When @var: "a", "a:hover";
you can also use:
@var1: e(extract(@var,1));
@var2: e(extract(@var,2));
body > header {
> @{var1}, > @{var2} {
> strong > em {
color: red;
}
}
}
Or use a complex mixin (as Bootstrap does, see: LESS loops used to generate column classes in twitter - How do they work?) to build your selectors:
.mixin(@iterator: 0; @selectors: ~""; @seperator: ~"") when (@iterator < length(@var)) {
@blah: ~"body > header > @{selector} > strong > em";
@selector: extract(@var,@iterator + 1);
@selectorlist: ~"@{selectors} @{seperator} @{blah}";
.mixin((@iterator + 1); @selectorlist; ~",");
}
.mixin(@iterator; @selectors: ~""; @seperator: ~"") when (@iterator = length(@var)) {
@{selectors} {
color:red;
}
}
.mixin();
Related Topics
Are CSS3 ::Before and ::After Pseudo Elements Supported by IE9 or Not
Using Font Awesome Icon for Bullet Points, with a Single List Item Element
Css3 Transform Order Matters: Rightmost Operation First
Css: 100% Font Size - 100% of What
MVC Bundling and CSS Relative Urls
CSS "Outline" Different Behavior Behavior on Webkit & Gecko
Can You Apply a Width to a :Before/:After Pseudo-Element (Content:Url(Image))
Define an <Img>'s Src Attribute in CSS
Webkit CSS Content Unicode Bug
Why Don't Margin-Top: Auto and Margin-Bottom:Auto Work the Same as Their Left and Right Counterparts
Select Inputs and Text Inputs in HTML - Best Way to Make Equal Width
Specifying Style and Weight for Google Fonts
CSS Sticky Footers with Unknown Height