How to build a CSS style with a LESS mixin parameter? - css

Due to CSS specificity issues, I am attempting to build a CSS expression with a LESS mixin as follows:
// overriding any previous styling with a more specific expression:
.styleSpecificListElement(#id: "#index") {
main .list ul #{id} a {
// referencing another mixin here:
.setLiStyling();
}
}
LESS will parse this, however it is parsed with the inserted parameter in double-quotes, in which does not evaluate as intended:
/* don't want the double-quotes around the id: */
main .list ul "#index" a {
/* ...code from the .setLiStyling() mixin generated here... */
}

Ok nevermind, I just figured it out -- the hash-symbol # can precede the #{id} reference in the mixin, where the parameter is then passed as a String without quotes:
.styleSpecificListElement(#id: index) {
main .list ul ##{id} a {
// referencing another mixin here:
.setLiStyling();
}
}
...where calling .styleSpecificListElement(foobar) will then parse into CSS as:
main .list ul #foobar a {
/* ...code from the .setLiStyling() mixin generated here... */
}

Related

How I exclude more than one element with CSS selector group? [duplicate]

I'm trying to select input elements of all types except radio and checkbox.
Many people have shown that you can put multiple arguments in :not, but using type doesn't seem to work anyway I try it.
form input:not([type="radio"], [type="checkbox"]) {
/* css here */
}
Any ideas?
Why :not just use two :not:
input:not([type="radio"]):not([type="checkbox"])
Yes, it is intentional
If you're using SASS in your project, I've built this mixin to make it work the way we all want it to:
#mixin not($ignorList...) {
//if only a single value given
#if (length($ignorList) == 1){
//it is probably a list variable so set ignore list to the variable
$ignorList: nth($ignorList,1);
}
//set up an empty $notOutput variable
$notOutput: '';
//for each item in the list
#each $not in $ignorList {
//generate a :not([ignored_item]) segment for each item in the ignore list and put them back to back
$notOutput: $notOutput + ':not(#{$not})';
}
//output the full :not() rule including all ignored items
&#{$notOutput} {
#content;
}
}
it can be used in 2 ways:
Option 1: list the ignored items inline
input {
/*non-ignored styling goes here*/
#include not('[type="radio"]','[type="checkbox"]'){
/*ignored styling goes here*/
}
}
Option 2: list the ignored items in a variable first
$ignoredItems:
'[type="radio"]',
'[type="checkbox"]'
;
input {
/*non-ignored styling goes here*/
#include not($ignoredItems){
/*ignored styling goes here*/
}
}
Outputted CSS for either option
input {
/*non-ignored styling goes here*/
}
input:not([type="radio"]):not([type="checkbox"]) {
/*ignored styling goes here*/
}
Starting from CSS Selectors 4 using multiple arguments in the :not selector becomes possible (see here).
In CSS3, the :not selector only allows 1 selector as an argument. In level 4 selectors, it can take a selector list as an argument.
Example:
/* In this example, all p elements will be red, except for
the first child and the ones with the class special. */
p:not(:first-child, .special) {
color: red;
}
Unfortunately, browser support is somewhat new.
I was having some trouble with this, and the "X:not():not()" method wasn't working for me.
I ended up resorting to this strategy:
INPUT {
/* styles */
}
INPUT[type="radio"], INPUT[type="checkbox"] {
/* styles that reset previous styles */
}
It's not nearly as fun, but it worked for me when :not() was being pugnacious. It's not ideal, but it's solid.
If you install the "cssnext" Post CSS plugin, then you can safely start using the syntax that you want to use right now.
Using cssnext will turn this:
input:not([type="radio"], [type="checkbox"]) {
/* css here */
}
Into this:
input:not([type="radio"]):not([type="checkbox"]) {
/* css here */
}
https://cssnext.github.io/features/#not-pseudo-class

sass, custom selectors based on parent

I have the following mixins to make easy work with BEM syntax, sass 3.3.2 code:
=b($name)
.#{$name}
#content
=e($name)
&__#{$name}
#content
=m($name)
&--#{$name}
#content
+b(menu)
+e(item)
color: grey
+e(item)
+m(alert)
color: red
This gives me the desired result:
.menu__item {color: grey;}
.menu__item--alert {color: red;}
So this works pretty nice for element level modifiers, however when i want to have block level modifiers the problem begins:
+b(menu)
+m(theme-1)
+e(item)
color: blue
css output:
.menu--theme-1__item {color: blue;}
when the thing i really want is this:
.menu--theme-1 .menu__item {color: blue;}
So i need a way to check what the context of an element is, when the context is a block there is no problem but when is a modifier the syntax fails. I tried inside e mixin to take the parent selector as string, so when e parent is b it will not have the -- syntax, in the other way when his parent is m it will have the -- syntax, with that i could decide what syntax use for both context.
I didn't find a way to take the parent selector as a string and i think is not possible, is there a way to make this works?
Update
I found a not very straightforward solution with it works fine, it uses a context argument in the element mixin:
=e($name, $context:null)
#if $context
&
+b($context)
&__#{$name}
#content
#else
&__#{$name}
#content
Now i can call the mixin as follows:
+b(menu)
+m(theme-1)
+e(item, nav)
color: blue
getting:
.menu--theme-1 .menu__item {color: blue;}
Answering a 2 year old question - long shot :) But hopefully could help someone else as well.
So a more robust way would be to improve your Element mixin to check if the parent selector has a modifier.
So you would need 2 functions:
one to check if a selector contains a modifier
one to get the block name from that selector
#function _bem-selector-has-modifier($selector) {
$selector: _bem-selector-to-string($selector);
#if str-index($selector, $bem-modifier-separator) or str-index($selector, ':') {
#return true;
} #else {
#return false;
}
}
#function _bem-get-block-name($selector) {
$selector: _bem-selector-to-string($selector);
$modifier-separator: '--';
$modifier-start: str-index($selector, $modifier-separator) - 1;
#return str-slice($selector, 0, $modifier-start);
}
And then you just need to apply the check in your element mixin
$bem-element-separator: '__';
#mixin element($element) {
$selector: &;
#if _bem-selector-has-modifier($selector) {
$block: _bem-get-block-name($selector);
#at-root {
#{$selector} {
.#{$block + '__' + $element} {
#content;
}
}
}
} #else {
#at-root {
#{$selector +'__' + $element} {
#content;
}
}
}
}
So calling element('item') inside a modifier('with-modifier') should render a .block--with-modifier .block__item selector in your compiled CSS and you won't have to manually pass the context.
The code posted is SCSS, but the idea should be the same with SASS.

How to write "any span that is a child of label is extended by x, whether it has a class or not" using SASS?

I'd like that every span that is a child of a label receive an extension, and at the same time simplify the SASS and remove repetition.
e.g. I'd like to rewrite this:
label > span {
extend #informational;
}
label > span.info {
extend #informational;
/* other info stuff */
}
label > span.error {
extend #informational;
/* other error stuff */
}
Into something resembling this:
label {
span {
extend #informational;
}
span.info {
/* other info stuff */
/* this also gets extended, but no explicit extension statement here */
}
span.error {
/* other error stuff */
/* this also gets extended, but no explicit extension statement here */
}
}
because just extending the span does not also extend a span.xxx (though I'd expect it to).
Is there a way? All the combinations I've attempted (including the one above) have failed.
I've also tried this without using #extend but through repeating the CSS in informational within each relevant block, to the same effect. This makes it a CSS problem (afaics) but if it can be solved using SASS that's fine too.
You may have other style rules that are overriding whatever is in your #informational extension for your span elements with classes. That's as far as I can see as well...
If that is the case you'll have to keep your class selectors so they make up for the less specific span selector, but you can group them together like this so you don't have to repeat the extend #informational; statement (you'll also want to use > if you're specifically only looking for children, and not just descendants at any nesting level):
label {
> span, > span.info, > span.error {
extend #informational;
}
> span.info {
/* other info stuff */
}
> span.error {
/* other error stuff */
}
}
If you're not interested in which specific classes your span elements have, as long as they either have or don't have a class attribute, you could use an attribute selector instead as a cheap hack:
label {
> span, > span[class] {
extend #informational;
}
/* ... */
}

Declare a global CSS property ? Is this possible?

I have a very wierd question, I dont know wether if its possible in css or not
Suppose I have say 3 different css classes as shown below, as you can see I have a common property of all these classes, I want to declare this color somewhere else and pass a reference to it here, so if next time I want to change the color I can simply change at one place rather than changing in all the 5 classes.
I know that you can use body{}, or a wrapper for this but that would affect the colors of the entire site right ? Is there a way to do this ?
Is this even possible ?
.abc {
color:red;
}
.abc2 {
color:red;
}
.abc3 {
color:red;
}
.abc4 {
color:red;
}
.abc5 {
color:red;
}
The bad news: you can't do it in CSS.
The good news: you can write in a meta-CSS language like LESS, which then processes a LESS file to pure CSS. This is called a "mixin".
In LESS:
#errorColor: red;
.error-color {
color: #errorColor;
}
#error-1 {
.error-color;
}
.all-errors {
.error-color;
}
More info: http://lesscss.org/#-mixins
if you want to declare all of them at a time, you can use:
.abc, .abc2, .abc3, .abc4, .abc5 {
color:red;
}
Or you can declare an additional class & add to all the .abc, .abc2.... & make its color:red;.
This can not be done with CSS, but that is still a very popular thing to do by using a CSS preprocessor such as LESS, SASS, SCSS, or Stylus.
A preprocessor will let you define a variable (say $red = #F00). It will replace the variable in your CSS document with the variable value for you, allowing you to write very DRY and module CSS.
This functionality is referred to as "CSS variables", which is part of the future spec, but not yet implemented on any browsers.
For now, the best way to do this in pure CSS is to declare an additional class for the desired "global", and then add that class to all relevant items.
.abc_global { color: red; }
.abc1 { /* additional styling */ }
.abc2 { /* additional styling */ }
<div class="abc1 abc_global"></div>
<div class="abc2 abc_global"></div>
With LESS
You are able to define that red color once:
.myRedColor {
color:red;
}
Now you can call that red on any CSS styles. Even NESTED styles! It's a wicked tool!
.abc1 {
.myRedColor;
}
.abc2 {
.myRedColor;
}
.abc3 {
.myRedColor;
}
.abc4 {
.myRedColor;
}
NESTED EXAMPLE:
.abc {
.itsEasyAsOneTwoThree{
.myRedColor;
}
}
Now all of our "itsEasyAsOneTwoThree" classes that are properly nested inside of an "abc" class will be assigned the red style. No more remembering those long #867530 color codes :) How cool is that?!
You can also use PostCSS with the plugin postcss-preset-env and support custom properties/variables, then use the :root selector to add global css variables.
:root {
--color-gray: #333333;
--color-white: #ffffff;
--color-black: #000000;
}

Can the :not() pseudo-class have multiple arguments?

I'm trying to select input elements of all types except radio and checkbox.
Many people have shown that you can put multiple arguments in :not, but using type doesn't seem to work anyway I try it.
form input:not([type="radio"], [type="checkbox"]) {
/* css here */
}
Any ideas?
Why :not just use two :not:
input:not([type="radio"]):not([type="checkbox"])
Yes, it is intentional
If you're using SASS in your project, I've built this mixin to make it work the way we all want it to:
#mixin not($ignorList...) {
//if only a single value given
#if (length($ignorList) == 1){
//it is probably a list variable so set ignore list to the variable
$ignorList: nth($ignorList,1);
}
//set up an empty $notOutput variable
$notOutput: '';
//for each item in the list
#each $not in $ignorList {
//generate a :not([ignored_item]) segment for each item in the ignore list and put them back to back
$notOutput: $notOutput + ':not(#{$not})';
}
//output the full :not() rule including all ignored items
&#{$notOutput} {
#content;
}
}
it can be used in 2 ways:
Option 1: list the ignored items inline
input {
/*non-ignored styling goes here*/
#include not('[type="radio"]','[type="checkbox"]'){
/*ignored styling goes here*/
}
}
Option 2: list the ignored items in a variable first
$ignoredItems:
'[type="radio"]',
'[type="checkbox"]'
;
input {
/*non-ignored styling goes here*/
#include not($ignoredItems){
/*ignored styling goes here*/
}
}
Outputted CSS for either option
input {
/*non-ignored styling goes here*/
}
input:not([type="radio"]):not([type="checkbox"]) {
/*ignored styling goes here*/
}
Starting from CSS Selectors 4 using multiple arguments in the :not selector becomes possible (see here).
In CSS3, the :not selector only allows 1 selector as an argument. In level 4 selectors, it can take a selector list as an argument.
Example:
/* In this example, all p elements will be red, except for
the first child and the ones with the class special. */
p:not(:first-child, .special) {
color: red;
}
Unfortunately, browser support is somewhat new.
I was having some trouble with this, and the "X:not():not()" method wasn't working for me.
I ended up resorting to this strategy:
INPUT {
/* styles */
}
INPUT[type="radio"], INPUT[type="checkbox"] {
/* styles that reset previous styles */
}
It's not nearly as fun, but it worked for me when :not() was being pugnacious. It's not ideal, but it's solid.
If you install the "cssnext" Post CSS plugin, then you can safely start using the syntax that you want to use right now.
Using cssnext will turn this:
input:not([type="radio"], [type="checkbox"]) {
/* css here */
}
Into this:
input:not([type="radio"]):not([type="checkbox"]) {
/* css here */
}
https://cssnext.github.io/features/#not-pseudo-class

Resources