Bubble up rules in SASS for variation / theme classes - sass

How does one handle making "variation classes" in Sass? Example:
.bookshelf {
color: brown;
height: 100px;
.panel {
trimmed: no;
height: 10px;
}
.door {
height: 100px;
.knob {
shape: circle;
height: 10px;
}
}
}
You want to make a .bookshelf.small variation. Is there a way to write the variation code inside the main element using mixins or something that would "bubble up" the data?
.bookshelf {
color: brown;
height: 100px;
#include small-version {
height: 50px;
}
.panel {
trimmed: no;
height: 10px;
#include small-version {
height: 5px;
}
}
.door {
height: 100px;
#include small-version {
height: 50px;
}
.knob {
shape: circle;
height: 10px;
#include small-version {
height: 5px;
}
}
}
}
And the output of the mixin would be
.bookshelf.small {
height: 50px;
.panel {
height: 5px;
}
.door {
height: 50px;
.knob {
height: 5px;
}
}
Several things looked promising, such as the #at-root function and #content function, but neither would work for this scenario. I know if I write #media settings, they will bubble up. But I do not want this tied to a media query, I want this tied to a specific class (if .bookshelf also has the class small, apply these rules).

There is a small hack about it. But you should work with it really carefully. And it's not so good as you ask.
First of all, it prepends theme class to selector, so you can't use any element selectors. There is actually lot of selector helpers in docs but i didn't found any that could help with real parsing selector string.
Second it's not grouping out selectors. It inserts rules right where mixin were included. But it generates higher priority rules for your styles, so it could be a half a way solution.
You can dig for postcss parser or any other file post processing tool that could help you to separate your styles in a different files.
#mixin small-version {
#at-root .small#{&} {
#content;
}
}
.bookshelf {
color: brown;
height: 100px;
#include small-version {
height: 60px;
}
.panel {
trimmed: no;
height: 10px;
#include small-version {
height: 5px;
}
}
.door {
height: 100px;
.knob {
shape: circle;
height: 10px;
}
}
}
Gist link

You can qualify a selector by putting & to the right of the intended parent of the selector. Wrapping it in #{} allows you to place it directly beside that parent.
The #at-root rule causes everything proceeding it to be emitted at the root instead of using regular nesting.
If you use both, you can achieve what you are looking for.
.flashlight {
.light {
background: yellow;
#at-root .dead-battery#{&} {
background: transparent;
}
.daytime &{
background: transparent;
}
}
}
This would compile to:
.flashlight .light {
background: yellow;
}
.dead-battery.flashlight .light {
background: transparent;
}
.daytime .flashlight .light {
background: transparent;
}

Related

Render comma separated selectors separately in SCSS

I'm trying to style progress bars using SCSS. To get this working in both Webkit and Gecko browsers I need to use both -webkit and -moz prefixes:
progress {
height: 50px;
-webkit-appearance: none;
appearance: none;
background: cyan;
&::-moz-progress-bar,
&::-webkit-progress-value {
background-color: orange;
}
&::-webkit-progress-bar {
background-color: cyan;
}
}
which renders to
progress {
height: 50px;
-webkit-appearance: none;
appearance: none;
background: cyan;
}
progress::-moz-progress-bar, progress::-webkit-progress-value {
background-color: orange;
}
progress::-webkit-progress-bar {
background-color: cyan;
}
This works great in Firefox, but Chrome doesn't seem to like it. Compare the following two implementations:
Comma separated selectors
progress {
height: 50px;
-webkit-appearance: none;
appearance: none;
background: cyan;
}
progress::-moz-progress-bar, progress::-webkit-progress-value {
background-color: orange;
}
progress::-webkit-progress-bar {
background-color: cyan;
}
<progress max="1" value="0.5"></progress>
Entirely separate declarations
progress {
height: 50px;
-webkit-appearance: none;
appearance: none;
background: cyan;
}
progress::-webkit-progress-value {
background-color: orange;
}
progress::-moz-progress-bar {
background-color: orange;
}
progress::-webkit-progress-bar {
background-color: cyan;
}
<progress max="1" value="0.5"></progress>
The above code snippets render in Firefox and Chrome as shown below
Firefox
Chrome
comma separated
separate declarations
It seems like the problem comes from rendering the CSS with vendor-specific pseudos in comma-separated lists. Is there any way to force the SASS processor to render each selector in a comma separated list as its own declaration?
It would be nice to not use mix-ins, but if it's the only way it's the only way.
Yes, you are able to do so! If you want to render SASS to seperate CSS rules simply divide the comma seperated list into two seperate rules. SASS keeps different rules seperate and will not wrap them together. Example:
// ### > SASS
xprogress {
height: 50px;
appearance: none;
background: cyan;
//## divide comma seperated selectors
//## into different rules
&::-moz-progress-bar {
background-color: orange;
}
&::-webkit-progress-value {
background-color: orange;
}
&::-webkit-progress-bar {
background-color: cyan;
}
}
// ### > compiles to css
progress {
height: 50px;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background: cyan;
}
//## when compiling CSS
//## different rules will survive
progress::-moz-progress-bar {
background-color: orange;
}
progress::-webkit-progress-value {
background-color: orange;
}
progress::-webkit-progress-bar {
background-color: cyan;
}

Issue mixing variable with mixin with keyframe animation?

First time using SCSS, and testing my knowledge from the Sass-Lang.com guide. According to the guide, it is possible to both set variables and use mixins to simplify your CSS.
I was coding an animation where the div is clipped from bottom to top. I used variables to set the initial and final clip-path settings, and used them while calling a mixin. Yet I get the error, 'Invalid CSS after "...slider-initial)": expected "{", was "; }"'. What am I doing wrong?
Here is my code:
<body>
<section id='main'>
<div id='left'></div>
<div id='right'></div>
<section>
</body>
$slider-initial: inset(0 0 0 0);
$slider-final: inset(0 0 100% 0);
#mixin slider-clip($slider-state) {
-webkit-clip-path: $slider-state;
clip-path: $slider-state;
}
body {
height: 100%; width: 100%;
margin: 0 auto;
}
#main {
height: 64vh; width: 38vw;
margin: 0 auto;
margin-top: 10%;
position: relative;
display: flex;
flex-direction: row;
justify-content: center;
border: 1vh solid black;
}
#left {
order: 1;
width: 4%;
height: 100%;
margin-left: 46%;
background: green;
}
#right {
opacity: 1;
order: 2;
width: 4%;
height: 100%;
margin: auto;
margin-left: 0;
animation-name: dropdown;
animation-duration: 4s;
background: red;
}
#keyframes dropdown {
from { #mixin slider-clip($slider-initial); }
to { #mixin slider-clip($slider-final); }
}
You called your mixin in a wrong way:
#keyframes dropdown {
from { #mixin slider-clip($slider-initial); }
to { #mixin slider-clip($slider-final); }
}
In the guide on sass-lang.com, you can see the following example of how to include a mixin:
.box { #include border-radius(10px); }
Applied to your case, your code should look like this:
#keyframes dropdown {
from { #include slider-clip($slider-inital); }
to { #include slider-clip($slider-final); }
}

Sass / Scss: Changing the order of nested psuedo selectors for :before and :hover in generated CSS?

Given the following Sass:
div.test {
display: inline-block;
background-color: #ffffff;
color: #000000;
&:before {
& {
&:hover {
border: 1px solid salmon;
}
}
width: 25px;
height: 25px;
content: "";
}
}
The resulting CSS compiles to:
div.test {
display: inline-block;
background-color: #ffffff;
color: #000000;
}
div.test:before {
width: 25px;
height: 25px;
content: "";
}
div.test:before:hover {
border: 1px solid salmon;
}
What I am attempting to do is generate div.test:hover:before (the current output is before:hover).
NOTE: I am able to generate the expected CSS by using the following Sass:
div.test {
display: inline-block;
background-color: #ffffff;
color: #000000;
&:hover {
&:before {
border: 1px solid salmon;
}
}
&:before {
width: 25px;
height: 25px;
content: "";
}
}
However I would like to know if it is possible using the first nested approach or some modification of it.
The goal was to avoid having to repeat &:before if there was such a way to do so using Sass syntax. I am also OK with knowing it isn't possible.
While initially the plan was to have '&' available in SassScript as a string that could be manipulated so that you could insert values wherever you wanted, those plans have been abandoned for 3.3 due to complication. Unfortunately you'll have to wait a while to be able to do this. At the moment '&' is immutable and just means "whatever the selector chain up to this point is".
EDIT (2020.02.15):
it is now technically possible to achieve this with recent versions of dart-sass:
#use "sass:selector";
#mixin unify-parent($child) {
#at-root #{selector.unify(&, $child)} {
#content;
}
}
div.test {
display: inline-block;
background-color: #ffffff;
color: #000000;
&:before {
width: 25px;
height: 25px;
content: "";
#include unify-parent(":hover") {
border: 1px solid salmon;
}
}
}
Sources:
https://sass-lang.com/blog/the-module-system-is-launched
https://sass-lang.com/documentation/style-rules/parent-selector#advanced-nesting

Custom directives within scss files? Perhaps for a new pseudo class?

Is it be possible to use some sort of #directive creation syntax, similar to creating #mixins? Secondly, is it possible to create a SASS-only pseudo class?
I'd like to declare my own SASS directive,although I'd prefer not to have to force my teammates to install an extra ruby gem to use it so I'd want to store it in a scss partial. I do understand that they are orders of levels in complexity, so perhaps it just isn't possible.
In addition to perhaps creating a new scss-only pseudo class (such as :parent, :clicked, :unchecked, etc) I'd be interested in a custom-made directive that assists with using checkboxes to direct css animations ("css checkbox hack"). Here is my scss pseudocode to generalize what I'm trying to do:
// this code monitors when a checkbox (#someinput) is checked,
// then applies style to div #target div. Basically an 'onClick' event in scss.
body {
#wrapper {
#targetdiv {
#spotcheck(#someinput) { #
color: red; border: 2px solid blue; # <-- move this ...
} #
color: blue; border: 0;
#someinput {
width: 20px; height: 20px;
}
}
}
}
// ... the above scss should be converted to this pre-compiled state, also scss
body {
#someinput:checked ~ #targetdiv { #
color: red; border: 2px solid blue; # <-- ..to here. it needs to be
} # above the #targetdiv
#wrapper {
#targetdiv {
color: blue; border: 0;
#someinput {
width: 20px; height: 20px;
}
}
}
}
Make your selectors only as specific as they absolutely need to be and no more. A mixin would only be more verbose with no real benefit.
#targetdiv {
color: blue; border: 0;
#someinput:checked ~ & {
color: red; border: 2px solid blue;
}
}
#someinput {
width: 20px; height: 20px;
}
Output:
#targetdiv {
color: blue;
border: 0;
}
#someinput:checked ~ #targetdiv {
color: red;
border: 2px solid blue;
}
#someinput {
width: 20px;
height: 20px;
}
Alternately, this would give the same result with the ability to overqualify as much as you want:
#targetdiv {
color: blue; border: 0;
}
#someinput {
width: 20px; height: 20px;
~ #targetdiv {
color: red; border: 2px solid blue;
}
}

How to convert this SCSS mixin to LESS?

I am used to coding in SCSS but pretty new to LESS.
I have the following code in SCSS but would like to know how to write this in LESS.
Here is the SCSS code...
#mixin posL($property, $value) {
{$property}: $value;
}
.box {
width:200px;
height:200px;
background:red;
position:absolute;
#include posL(left, 100px);
}
So far I have something like this in LESS but I have to declare selectors...
.posL(#property: 100px, #value: 2px) {
left: #property;
-rtl-left: #value;
}
.box {
width:200px;
height:200px;
background:red;
position:absolute;
.posL(200px);
}
Is there a better way to write my LESS code so the selectors in the mixin remain generic (not specified)?
Update for LESS 1.6+
It is almost a direct mapping now with the 1.6 update, like so:
LESS
.posL(#property, #value) {
#{property}: #value;
}
.box {
width:200px;
height:200px;
background:red;
position:absolute;
.posL(left, 100px);
}
CSS Output
.box {
width: 200px;
height: 200px;
background: red;
position: absolute;
left: 100px;
}
Original Answer (pre 1.6)
There is currently no real way to do dynamic property names in LESS (whether for prefixing or for full property names ,like you want), though there is discussion about it.
I recommend a generic mixin with a nested, guarded mixin for the logic. This still gives you selection to specific properties, but does require some more explicit code to set it up. Something like:
.posL(#property, #value) {
.getProp(left) {
left: #value;
}
.getProp(-rtl-left) {
-rtl-left: #value;
}
.getProp(#property);
}
Then use it very similar to how you do the SASS version:
.box {
width:200px;
height:200px;
background:red;
position:absolute;
.posL(left, 100px);
}
Compiles to:
.box {
width: 200px;
height: 200px;
background: red;
position: absolute;
left: 100px;
}

Resources