SCSS. Reference second level ascending selector - css

One of the techniques to organise classes in the scope of avoiding collisions, is to extend a parent's class + add some suffix. For example:
<div class="a">
<div class="a__b">
<div class="a__c">
<span class="a__d">
From considerations of not duplicating code, in sass/scss files, one can refer a parent with the help of an ampersand - &, so above structure can be achieved like this:
.a{
&__b{}
&__c{}
&__d{}
Which is transfomed into:
.a__b {}
.a__c {}
.a__d {}
But difficulties appear when one needs to get such a css as the result:
.a:hover{
color: red;
}
.a:hover .a__b{
color: blue;
}
Since the main idea is not to duplicate selectors, a question appears - is there a way to reference second level parent? I know that && ins't an issue but is there a way to simulate double ampersand behaviour?
.a{
&:hover{
color: red;
& __b { /* & -> .a:hover, but I need just .a */
color: blue;
}
}
}
Not an issue, .a is duplicated:
.a:hover { //here
color: red;
.a__b { //here
color: blue;
}
}
Also not an issue:
.a { //ok
&:hover {
color: red;
.a__b { //oops, duplicated selector
color: blue;
}
}
}
So, from the considerations of avoiding collisions many times classes have long names. And that is when duplicated selectors make code look scary. Imagine, that instead of .a selector there would be: .custom-layers-list-panel-conatiner. Another reason of avoiding duplicated classes is that if parent class is changed, it should be changed everywhere. Yes, nowadays it's quite trivial task with some specific tools, but it's still remains a place where mistakes can appear.

Update: better than Original
.a{
$grandparent: &;
&:hover{
color: red;
& #{$grandparent}__b {
color: blue;
}
}
}
and
Original:
#function r-pseudo($s) {
$string: nth(nth($s, 1), 1);
#return str-slice($string, 0, str-index($string, ':') - 1);
}
.a{
&:hover{
color: red;
& #{r-pseudo(&)}__b {
color: blue;
}
}
}
both generate
.a:hover {
color: red;
}
.a:hover .a__b {
color: blue;
}

Your idea was right, but you've to put the a:hover to the top-level to get the result you wanted. It is nothing what you wanted, but the only way that SCSS will give you your target-result.
I think you looking for this:
.a:hover {
color: red;
.a__b {
color: blue;
}
}
Second try, like this?
.a {
&:hover {
color: red;
.a__b {
color: blue;
}
}
}

Related

Exclude elements from group in SCSS

Imagine a set of rules like the ones shown below:
span, div { color: red; }
span { background: white; }
div { background: black; }
Is it possible to wrap them under 1 SCSS rule? Something in the form of:
span, div {
& { color: red; }
&:not(div) { background: white;}
&:not(span) { background: black; }
}
Unfortunately an approach like this could very easily get quite large. So I'm hoping for an SCSS implementation of the code shown at the top but without the use of :not(<every other selector>).
Preferably something looking like (invalid code):
span, div {
& { color: red; }
&(span) { background: white;}
&(span) { background: black; }
}
I don't think that it is possible to do what you want this way (but I may be wrong).
The code below achieve the result you are looking for but uses a map, a #mixin and #extend instead of a single selector. Maybe it's a bit too complex for want you want to achieve but I hope it can help:
#mixin setSelectors($elements) {
%commonProperties {
#content;
}
#each $selector, $properties in $elements {
#{$selector} {
#extend %commonProperties;
#each $property, $value in $properties {
#{$property}: #{$value};
}
}
}
}
#include setSelectors((
span: (background: white),
div: (background: black)
)) {
color: red; // Common properties
}
Will return:
div, span { color: red; }
span { background: white; }
div { background: black; }
The first argument is a map containing all your selectors and their specific properties. The #content of the #mixin contains shared properties.
If you need to add a selector that doesn't have any specific property, you can add it to the map with null as key. Such as:
#include setSelectors((
span: (background: white),
div: (background: black),
i: null
)) {
color: red;
}
However, this solution doesn't allow nested selectors so I believe that separating the selectors is the best way to go.

How to reference SCSS sibling?

Assuming that I have the following HTML:
<div class="navigation__item">
<span class="navigation__item__icon"></span>
</div>
I want to apply some rules to an icon, when hovering an item, which can be described with the following CSS:
.navigation__item__icon {
color: black;
}
.navigation__item:hover .navigation__item__icon {
color: white;
}
I can achieve this using the following SCSS:
.navigation__item {
&:hover {
.navigation__item__icon { <-- here
color: white;
}
}
&__icon {
color: black;
}
}
Here, is there any way to avoid writing navigation__item? Something like "parent rule \ element".
I like Sass for logical structure so that if I want to rename the whole navigation block with elements, I can simply change navigation class name in the root, and everything is renamed. This case breaks this advantage.
Update: Actually, I have found a way to do this without using {} braces. & can be repeated more than once:
.navigation__item {
&:hover &__icon {
color: white;
}
&__icon {
color: black;
}
}
It is great, but it doesn't make much sense if I have many rules and rules for &:hover itself. The question is still open - is this possible to access sibling element definition from within the {} block.
In Stylus there is a Partial reference but I don't know anything similar in SASS. One solution could be using a variable for the parent selector:
.navigation__item {
$selector: &;
&:hover {
#{$selector}__icon {
color: white;
}
}
&__icon {
color: black;
}
}
Is usefull is you change navigation__item class for another.
EDIT: I had used a wrong example, it's OK now.

Scoping CSS / Prepend selector with LESS

I have a chunk of CSS that I want to "scope" to a specific block of HTML. I'm generating a unique ID and then setting it on the block of HTML and then would like to wrap the chunk of CSS with the same ID so that those selectors can't match sibling or parent elements. I don't know the contents of the chunk of CSS. Given a chunk of CSS:
.container {
background-color: black;
}
.container .title {
color: white;
}
.container .description {
color: grey;
}
I need it to come out like this:
.theme0 .container, .theme0.container {
background-color: black;
}
.theme0 .container .title, .theme0.container .title {
color: white;
}
.theme0 .container .description, .theme0.container .description {
color: grey;
}
Is there any way to do this with LESS? The first selector is easy, just wrap the CSS chunk with '.theme0 {' + cssChunk + '}'. But I haven't been able to figure out a way to prepend '.theme0' to all of the selectors without the space.
EDIT:
So I should clarify that our intentions are to build such a system into our build process / dependency system. We're attempting to scope a chunk of css to a react component. We have a couple different approaches we're trying out, this is just one of them. Point is, the CSS and HTML we're trying to scope could be anything, we have no control or knowledge of it. The first pattern can easily be achieved by prepending .uniqueID { and appending }. This gives .uniqueID .someSelector {}. I'm wondering if it's possible to do a similar thing but get .uniqueID.someSelector {}? Ideally without having to write the original chunk of CSS with knowledge of our scoping system.
Assuming the component styles are in a separate CSS file, i.e.:
// component.css
.container {
background-color: black;
}
.container .title {
color: white;
}
.container .description {
color: grey;
}
The wrapper code could be:
.theme0 {
#import (less) "component.css";
&.container:extend(.theme0 .container all) {}
}
in less you can nest selectors for selecting inside that element like:
.theme {
color: black;
.container {
color: blue;
}
}
This wil generate:
.theme {
color:black;
}
.theme .container {
color:blue;
}
Creating elements that are connected is easy enof:
.test#badge will select a class test width an id badge
In less this is dont with the & symbol. (this selects the starting property)
.test {
color: blue;
&#badge {
color:black;
}
}
Compiles to:
.test {
color: blue;
}
.test#badge {
color: black;
}
And for the final selector:
To get the output of .test, .container use the function: .test:extends(.container);
.test {
color: black;
&:extends(.conatiner);
}
.container {
color: pink;
}
Compiles to:
.test {
color: black;
}
.test, .container {
color: pink;
}
You can even extend multiple ones in a single line:
.test:extends(.oclas, .tclss);
and its wil work as abose only for both classes. So outputed selectors would be .test, .oclass and .test, .tclass

Focused selector in variable . How to do that stuff in LESS like in SCSS

In SCSS i can do so:
and then
$selector-active: "&:hover, &:focus, &:active";
.class {
color: red;
#{$selector-active} {
color: green;
}
}
And its working.
How can i do this in LESS?
Hmm, interesting. Currently LESS does not expand its "&" within a selector interpolation, i.e. the straight-forward conversion DOES NOT work:
#selector-active: &:hover, &:focus, &:active;
.class {
color: red;
#{selector-active} {
color: green;
}
}
So you'll need some more tricky code... Using a callback/hook technique for example:
.selector-active() {
&:hover, &:focus, &:active {
.selector-active-properties();
}
}
.class {
color: red;
.selector-active();
.selector-active-properties() {
color: green;
}
}
You can get it even shorter:
.selector-active() {&:hover, &:focus, &:active {.-}}
.class {
color: red;
.selector-active;.-() {
color: green;
}
}
However there's important thing to remember when using hackish names for a hook/callback mixins.
If at some point you need another mixin with the same technique then you'll also need another name
for its callback (not the one you used for .selector-active()). Otherwise you get into problems if you try to use both "utilities" in the same scope. More over if you define some .inside() or .-() in the global scope they will override those coming from within .class and the trick becomes broken...
In other words, using "long/descriptive/unique" hook/callback names are just "safer" in a long run.
Btw. there's also a shorter syntax for the "hover specialization":
// same as .selector-active(#arg) when (#arg = hover):
.selector-active(hover) {
&:hover {
.inside();
}
}
I liked #Max nice solution. And this give me a way to move further. So i did a tweek with words for my self.
.selector-active() {
&:hover, &:focus, &:active {
.inside();
}
}
.selector-active(#type) when (#type = hover) {
&:hover {
.inside();
}
}
In use:
.class {
color: red;
.selector-active(); .inside() {
color: red;
}
}
I also tried to work with classes. LESS is prety owkward in this stuff, in 1.4.1 i must use:
.smthElse(#string) {
&.class-#{string}-small,
&.class-#{string}-big,
&.class-#{string}-tall {
.inside();
}
}
in 1.3.1 i must to use:
(~".myclass_#{index}") {...
#see http://lesscss.org/
Enough compact, and could be in use. So i can still work with LESS :) yey.
P.S.: All above is for less.js v1.4.1

SASS CSS: Target Parent Class from Child

I am using SASS and found an inconvenience. This is an example of what I am trying to do:
.message-error {
background-color: red;
p& {
background-color: yellow
}
}
Expected CSS:
.message-error {
background-color: red;
}
p.message-error {
background-color: yellow ;
}
The idea: all elements with .message-error will be red, except if it is p.message-error. This is not real-life situation, just to show an example.
SASS is not able to compile this, I even tried string concatenation. Is there some plugin that will do exactly the same?
NOTE:
I know I can put another CSS definition like:
p.message-error{....}
...under, but I would like to avoid that and use one place for all .message-error definitions.
Thanks.
As of Sass 3.4, this is now supported. The syntax looks like this:
.message-error {
background-color: red;
#at-root p#{&} {
background-color: yellow
}
}
Note the #at-root directive and the interpolation syntax on the ampersand. Failure to include the #at-root directive will result in a selector like .message-error p.message-error rather than p.message-error.
You can assign the current selector to a variable and then use it at any depth:
.Parent {
$p: &;
&-Child {
#{$p}:focus & {
border: 1px solid red;
}
#{$p}--disabled & {
background-color: grey;
}
}
}
Natalie Weizenbaum (the lead designer and developer of Sass) says it will never be supported:
Currently, & is syntactically the same as an element selector, so it
can't appear alongside one. I think this helps clarify where it can be
used; for example, foo&bar would never be a valid selector (or would
perhaps be equivalent to foo& bar or foo &bar). I don't think this use
case is strong enough to warrant changing that.
Source: #282 – Element.parent selector
To my knowledge, there is no possible workaround.
The best thing to do would be probably this (assuming you have a little more in your .message-error class than just background color.
.message-error {
background-color: red;
}
p.message-error {
#extend .message-error;
background-color: yellow
}
This approach doesn't offer that close grouping, but you can just keep them close to each other.
I had the same problem so I made a mixin for that.
#mixin tag($tag) {
$ampersand: & + '';
$selectors: simple-selectors(str-replace($ampersand, ' ', ''));
$main-selector: nth($selectors, -1);
$previous-selectors: str-replace($ampersand, $main-selector, '');
#at-root {
#{$previous-selectors}#{$tag}#{$main-selector} {
#content;
}
}
}
To make it work, you will need a string replacement function as well (from Hugo Giraudel):
#function str-replace($string, $search, $replace: '') {
$index: str-index($string, $search);
#if $index {
#return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace);
}
#return $string;
}
How it works:
SCSS
.foo {
color: blue;
#include tag(p) {
color: red;
}
}
Output
.foo {
color: blue;
}
p.foo {
color: red;
}
Use case
This method works with nested selectors but not whit compound ones.
#Zeljko It is no possible to do what you want via SASS.
See Nex3 comment: https://github.com/nex3/sass/issues/286#issuecomment-7496412
The key is the space before the '&':
.message-error {
background-color: red;
p & {
background-color: yellow
}
}
instead of:
.message-error {
background-color: red;
p& {
background-color: yellow
}
}
I think if you want to keep them grouped by parent selector, you might need to add a common parent:
body {
& .message-error {background-color: red;}
& p.message-error {background-color: yellow}
}
Of course, body could be replaced with some other common parent, such as #Content or another div name that will contain all the error messages.
UPDATE (Another Idea)
If you leverage #for and lists then it seems like this should work (what I don't know for sure is if the list will allow the . (period).
#for $i from 1 to 3 {
nth(. p. ul., #{$i})message-error {
background-color: nth(red yellow cyan, #{$i}));
}
}
Should compile to something like:
.message-error {
background-color: red;}
p.message-error {
background-color: yellow;}
ul.message-error {
background-color: cyan;}
I made a mixin that solves this problem.
Github: https://github.com/imkremen/sass-parent-append
Example: https://codepen.io/imkremen/pen/RMVBvq
Usage (scss):
.ancestor {
display: inline-flex;
.grandparent {
padding: 32px;
background-color: lightgreen;
.parent {
padding: 32px;
background-color: blue;
.elem {
padding: 16px;
background-color: white;
#include parent-append(":focus", 3) {
box-shadow: inset 0 0 0 8px aqua;
}
#include parent-append(":hover") {
background-color: fuchsia;
}
#include parent-append("p", 0, true) {
background-color: green;
}
}
}
}
}
Result (css):
.ancestor {
display: inline-flex;
}
.ancestor .grandparent {
padding: 32px;
background-color: lightgreen;
}
.ancestor .grandparent .parent {
padding: 32px;
background-color: blue;
}
.ancestor .grandparent .parent .elem {
padding: 16px;
background-color: white;
}
.ancestor:focus .grandparent .parent .elem {
box-shadow: inset 0 0 0 8px aqua;
}
.ancestor .grandparent .parent:hover .elem {
background-color: fuchsia;
}
.ancestor .grandparent .parent p.elem {
background-color: green;
}
I created package/mixin with a similar solution :) (Maybe it will help U)
https://github.com/Darex1991/BEM-parent-selector
so writing:
.calendar-container--theme-second-2 {
.calendar-reservation {
#include BEM-parent-selector('&__checkout-wrapper:not(&--modifier):before') {
content: 'abc';
}
}
}
This mixin will add selector only for the last parent:
.calendar-container--theme-second-2 .calendar-reservation__checkout-wrapper:not(.calendar-reservation--modifier):before {
content: 'abc';
}
More info on the repo.
I have ran into this before as well. Bootstrap 3 handles this using a parent selector hack. I've tweaked it slightly for my own purposes...
#mixin message-error() {
$class: '.message-error';
#{$class} {
background-color: red;
}
p#{$class} {
background-color: yellow;
}
}
#include message-error();
wheresrhys uses a similar approach above, but with some sass errors. The code above allows you to manage it as one block and collapse it in your editor. Nesting the variable also makes it local so you can reuse $class for all instances where you need to apply this hack. See below for a working sample...
http://sassmeister.com/gist/318dce458a9eb3991b13
I use an #mixin function like this, when i need change some element in middle
of a sass big tree.
The first parameters is the parent element, the target, and the second the class that should have.
SASS
#mixin parentClass($parentTarget, $aditionalCLass) {
#at-root #{selector-replace(&, $parentTarget, $parentTarget + $aditionalCLass)} {
#content;
}
}
Sample,
like i need to improve font size in a strong tag, when .txt-target had .txt-strong too
HTML
<section class="sample">
<h1 class="txt-target txt-bold">Sample<strong>Bold</strong>Text</h1>
</section>
SASS
section{
.txt-target{
strong{
#include parentClass('.txt-target','.txt-bold'){
font-weight:bold;
font-size:30px;
}
}
}
}
Font:
https://sass-lang.com/documentation/at-rules/at-root
Here you can see a function called #mixin unify-parent($child) that looks like this
This cheat might work
{
$and: .message-error;
#{$and} {
background-color: red;
}
p#{$and} {
background-color: yellow
}
}
You may even be able to use $& as your variable name but I'm not 100% sure it won't throw an error.
And SASS has inbuilt scoping, which removes having to worry about the value of $and leaking out to the rest of your stylesheet
Variables are only available within the level of nested selectors
where they’re defined. If they’re defined outside of any nested
selectors, they’re available everywhere.
In the Current Release: Selective Steve (3.4.14) this is now possible, just need to update a little bit your code:
.message-error {
background-color: red;
p &{
background-color: yellow
}
}
this only works if you are one level nested, for instance it does not work if you have something like this:
.messages{
.message-error {
background-color: red;
p &{
background-color: yellow
}
}
}

Resources