SASS/SCSS variable not working with CSS variable assignment - css

I have the following SCSS code:
#mixin foo($bar: 42) {
--xyzzy: $bar;
}
bar {
#include foo;
}
I would expect that I get CSS variable --xyzzy set to 42 on all bar elements. Instead of this, I get CSS stating bar { --xyzzy: $bar; }. The variable was not interpreted. I would need to use #{…} syntax instead to get the variable set.
Is this a feature of the SCSS/SASS? A bug? Can I get the interpretation working without enclosing the variable name in #{…}?
Actual result:
bar {
--xyzzy: $bar;
}
Expected:
bar {
--xyzzy: 42;
}

It's not a bug, it's how the Sass compiler works regarding CSS custom properties, known as CSS variables. The syntax #{…} is called interpolation, and it is the only way to inject dynamic values into a custom property. Here is a quote from the doc:
CSS custom properties, also known as CSS variables, have an unusual declaration syntax: they allow almost any text at all in their declaration values. What’s more, those values are accessible to JavaScript, so any value might potentially be relevant to the user. This includes values that would normally be parsed as SassScript.
Because of this, Sass parses custom property declarations differently than other property declarations. All tokens, including those that look like SassScript, are passed through to CSS as-is. The only exception is interpolation, which is the only way to inject dynamic values into a custom property.
That's the reason why you have that behavior, and only doing so works:
#mixin foo($bar: 42) {
--xyzzy: $bar; // does not work
--xyzzy: #{$bar}; // works
}

Related

Referencing class names between SASS modules

I'm confused how to reference my SASS module class name in another module. I have a Contact page module and a PhoneNumber module. In certain context on this page, I'd like to change the button inside the PhoneNumber. Using simplified example for clarity:
// src/pages/contact/index.module.scss
#use 'src/components/PhoneNumber/index.module' as phoneNumber;
.page {
phoneNumber.container {
:global(.button) {
background-color: pink;
}
}
}
It doesn't work, I'm not sure how to reference .container class name of PhoneNumber component.
There are 2 issues:
(1) If you include variables imported by directive #use ... as namespace you have to call them using the added namespace with character $ as well. So you have to call the variable with phoneNumber.$container, not: 'phoneNumber.container'.
(2) If you use a variable as element/id/class-name you have to wrap variables in #{$hereMyVariable} at all.
So try following code:
.page {
.#{phoneNumbers.$container}{
:global(.button) {
background-color: pink;
}
}
}
FURTHER EXPLANATION
Rule #use loads mixins, functions, and variables from other Sass stylesheets so you can use them. To separate same variables/mixins/functions names from different stylesheets you can separate them to different namespaces by #use scssFileName as myNamespace.
Now you can call i.e. variables with same name namespace1.$container and namespace2.$container from different stlesheetswith different values. Mixins works the same way just without $.
So: if you load #use filepath/filename as phoneNumber to your stylesheet and call phoneNumber.container SASS is looking to the other stylesheet for a function or mixin named container. From the context of you simplified example I assume you want to call a variable with the name of the class. Than you need to use $ and the brackets.
(Notice: But if you want just to name your class phoneNumber.container you should write .phoneNumber.containerso SASS will interpret it as classname and not as reference to the namespace of the other stylesheet ...)
Wider explanations how to use #use can be find in the excellent official docs: https://sass-lang.com/documentation/at-rules/use

When using CSS Variables (CSS Custom Properties) why is the setting syntax and the getting syntax different?

When using CSS Variables (CSS Custom Properties) why is the setting syntax and the getting syntax different?
If I wish to set a value for --my-custom-width, I will use:
:root {
--my-custom-width: 120px;
}
And if I wish to get a value for --my-custom-width, I will use a var() function to retrieve the same value:
.my-div {
width: var(--my-custom-width);
}
Why do we not simply write:
.my-div {
width: --my-custom-width;
}
Having been using CSS Custom Properties since late 2017, I've finally understood properly what they really are and why the var() function is necessary...
They are not (as they so often appear to be) variables intended to directly represent CSS values.
CSS Custom Properties are exactly what they say they are - they are new CSS properties which have not (yet) been assigned values.
In CSS, an example of something which really does approximate a variable representing a value is currentColor.
We see currentColor representing a value, here:
.my-div {
border: 1px dashed currentColor;
}
But CSS Custom Properties are not CSS variables which stand in for values like currentColor, .
Instead, CSS Custom Properties are newly invented, named, null-value-properties...
... and those newly-invented, named, null-value-properties are completely re-usable. Just like width, height, color etc. they may have values set and reset in different contexts.
E.g.
/* My custom property is --my-custom-width but I want this
property to hold different values in different contexts */
.left-two-thirds-of-page {
--my-custom-width: 120px;
}
.right-third-of-page {
--my-custom-width: 60px;
}
.my-div {
width: var(--my-custom-width);
}
That's why the var() function is necessary - it's not delivering "the custom property" - it's extracting the value that custom property is currently holding and then delivering that value.
Further Thoughts:
In hindsight, I wonder if the whole name-value relationship wouldn't have been a little clearer if CSS Custom Properties had been called:
CSS Custom Property Names
and the corresponding function had been called:
value()
so the syntax would have been written and read out as:
value(--my-custom-property-name)
By extension we could use the value() function (or var() function) not just on custom properties but on any property.
For instance:
width: value(height);

use variables on mixin or extend in Less.js

Using variable on mixin or extend in Less.js as follow will throw error.
#bar : bar;
.#{bar} {
background: yellow;
}
// ParseError: Missing closing ')'
.foo {
.#{bar}();
}
// Not work
.jam {
&:extend(.#{bar});
}
Has Less.js a proper syntax to call mixin with variables?
You are trying to call a mixin using selector interpolation, which is not possible.
As for extend, Less documentation states it clearly:
Extend is NOT able to match selectors with variables. If selector contains variable, extend will ignore it.

How to override mixins in LESS CSS 1.4+

I've been using what I thought was a very elegant pattern for defining the styles of reusable components/widgets, using LESS. It works beautifully in LESS 1.3-, but after upgrading recently, my whole library is broken. Does anyone know a way to accomplish something like this in 1.4+?
Here's a very simple example of a component:
#componentName {
.loadMixins(){
.text() {}
.header() {}
}
.apply(){
> h3 {
// markup-specific styles
padding: 3px;
margin-bottom: 0;
// custom styles
.header();
}
> div.body, > div.popup p {
color: red;
// custom styles
.text()
}
}
}
And here's how it would be used:
.coolWidget {
#componentName.loadMixins();
// override mixins here
.text(){
color: green;
}
#componentName.apply();
}
This keeps all the markup-dependent styles abstracted from the user. I could completely change my markup and the user's styles would still work. According to the less.js changelog, 1.4.0 Beta 1 has a line "variables in mixins no longer 'leak' into their calling scope"
Is there any way around this?
Strictly speaking nested variables and mixins are still expanded into calling scope unless this scope already has those names defined.
Your example above results in a error:
SyntaxError: .header is undefined...
and it's expected as no .header() is actually defined within the .coolWidget (or anywhere else).
This can be fixed by providing "default" definitions for .text and .header somewhere inside #componentName.
For example if you modify .loadMixins() to:
.loadMixins() {
.text();
.header();
// default properties in case a caller does not provide its own:
.text() {}
.header() {}
}
then the example compiles OK and all text/header properties are overridden as expected.
I can imagine how your library may become broken because of new scope rules but this particular example you gave above does not illustrate the problem.

LESS: mixin with non-class ruleset

In LESS, I am trying to define the button.c2 ruleset, to be applied to <button class="c2">...</button> elements. This ruleset is mainly based on the button.c1 ruleset. However, the following code produces a ParseError:
button.c2 {
button.c1;// Trying to import a ruleset
... // additional rules, such as font-size: 120%;
}
It seems to me that the ParseError is caused by the fact that the imported ruleset does not refer to a class or ID ("button.c1" does not start with a "." or a "#"). From the LESS documentation:
Any CSS class or id ruleset can be mixed-in that way.
Why is there such a limitation? Is there any way around it?
The limitation might just be ease of parsing, since . or # don't show up as the first character of a normal style rule the parser automatically knows that those should be mixed in.
You could get around it by defining .c1 as a mixin and using it for both buttons:
.c1() {
// c1 rules
}
button.c1 {
.c1;
}
button.c2 {
.c1;
// additional rules
}
However, coming up in LESS 1.4.0 are :extend selectors, which will allow you to do what you want. The syntax would be:
button.c2:extend(button.c1) {
// additional rules
}

Resources