CSS variables defaults: set if not already set - css

My Web Component uses CSS variables.
These variables need default values.
They are used in many files, so I want to provide the defaults once, and only once.
This first attempt makes the text black. Why?
What is the correct way to provide the defaults once?
.a {
--my-variable: red;
}
.b {
--my-variable: var(--my-variable, blue);
}
<div class="a">
<div class="b">
<span style="color: var(--my-variable);">text</span>
</div>
</div>

To complement the previous answers, there might be a case where you don't want to declare your variables in the global :root scope. For example, when you're creating a re-usable component, you want to declare its styles locally, without depending on the global project styles. Especially if you're building a library for other developers.
In that case, the solution is to expose one variable name to the "outer world", and use a different variable name inside of the component. The component container should just map the optional external variable to the inner variable, and set its default value:
.my-component-container {
/* map optional "external" variables to required "internal" variables */
--my-variable-inner: var(--my-variable, blue);
}
.my-component-container .my-nested-element {
color: var(--my-variable-inner);
}
.my-component-container .my-other-nested-element {
border-color: var(--my-variable-inner);
}
This way you can ensure that --my-variable-inner is always defined in the component, and make it optional for the external consumers to define --my-variable.
The downside is that you need to remember two variable names instead of one. But here you can think of some project-wide convention, e.g. add --inner or some other suffix to each variable like that.

Declare default values in :root, then override in selectors.
:root {
--primary-color: red;
}
* {
color: var(--primary-color);
border: 1px solid var(--primary-color);
padding: 0.25rem;
margin: 0;
}
div {
--primary-color: green;
}
p {
--primary-color: blue;
}
<div>HI!</div>
…
<p>Bye!</p>

This first attempt makes the text black. Why?
Because this --my-variable: var(--my-variable, blue); is invalid as you are trying to express the same variable with itself which is not allowed so the browser will simply ignore it. Then later when using color: var(--my-variable); the color will fallback to the initial value which is black.
The correct way is to simply define the variable on an upper level and it will get inherited by all the element (like the solution provided by #kornieff)
From the specification:
Custom properties are left almost entirely unevaluated, except that they allow and evaluate the var() function in their value. This can create cyclic dependencies where a custom property uses a var() referring to itself, or two or more custom properties each attempt to refer to each other.
For each element, create a directed dependency graph, containing nodes for each custom property. If the value of a custom property prop contains a var() function referring to the property var (including in the fallback argument of var()), add an edge between prop and the var. Edges are possible from a custom property to itself. If there is a cycle in the dependency graph, all the custom properties in the cycle must compute to their initial value (which is a guaranteed-invalid value).

Related

Is it possible to _define_ a CSS Variable (Custom Property) using itself? [duplicate]

My Web Component uses CSS variables.
These variables need default values.
They are used in many files, so I want to provide the defaults once, and only once.
This first attempt makes the text black. Why?
What is the correct way to provide the defaults once?
.a {
--my-variable: red;
}
.b {
--my-variable: var(--my-variable, blue);
}
<div class="a">
<div class="b">
<span style="color: var(--my-variable);">text</span>
</div>
</div>
To complement the previous answers, there might be a case where you don't want to declare your variables in the global :root scope. For example, when you're creating a re-usable component, you want to declare its styles locally, without depending on the global project styles. Especially if you're building a library for other developers.
In that case, the solution is to expose one variable name to the "outer world", and use a different variable name inside of the component. The component container should just map the optional external variable to the inner variable, and set its default value:
.my-component-container {
/* map optional "external" variables to required "internal" variables */
--my-variable-inner: var(--my-variable, blue);
}
.my-component-container .my-nested-element {
color: var(--my-variable-inner);
}
.my-component-container .my-other-nested-element {
border-color: var(--my-variable-inner);
}
This way you can ensure that --my-variable-inner is always defined in the component, and make it optional for the external consumers to define --my-variable.
The downside is that you need to remember two variable names instead of one. But here you can think of some project-wide convention, e.g. add --inner or some other suffix to each variable like that.
Declare default values in :root, then override in selectors.
:root {
--primary-color: red;
}
* {
color: var(--primary-color);
border: 1px solid var(--primary-color);
padding: 0.25rem;
margin: 0;
}
div {
--primary-color: green;
}
p {
--primary-color: blue;
}
<div>HI!</div>
…
<p>Bye!</p>
This first attempt makes the text black. Why?
Because this --my-variable: var(--my-variable, blue); is invalid as you are trying to express the same variable with itself which is not allowed so the browser will simply ignore it. Then later when using color: var(--my-variable); the color will fallback to the initial value which is black.
The correct way is to simply define the variable on an upper level and it will get inherited by all the element (like the solution provided by #kornieff)
From the specification:
Custom properties are left almost entirely unevaluated, except that they allow and evaluate the var() function in their value. This can create cyclic dependencies where a custom property uses a var() referring to itself, or two or more custom properties each attempt to refer to each other.
For each element, create a directed dependency graph, containing nodes for each custom property. If the value of a custom property prop contains a var() function referring to the property var (including in the fallback argument of var()), add an edge between prop and the var. Edges are possible from a custom property to itself. If there is a cycle in the dependency graph, all the custom properties in the cycle must compute to their initial value (which is a guaranteed-invalid value).

Overriding CSS variable with the same name doesn't work until assigned to a different variable? [duplicate]

My Web Component uses CSS variables.
These variables need default values.
They are used in many files, so I want to provide the defaults once, and only once.
This first attempt makes the text black. Why?
What is the correct way to provide the defaults once?
.a {
--my-variable: red;
}
.b {
--my-variable: var(--my-variable, blue);
}
<div class="a">
<div class="b">
<span style="color: var(--my-variable);">text</span>
</div>
</div>
To complement the previous answers, there might be a case where you don't want to declare your variables in the global :root scope. For example, when you're creating a re-usable component, you want to declare its styles locally, without depending on the global project styles. Especially if you're building a library for other developers.
In that case, the solution is to expose one variable name to the "outer world", and use a different variable name inside of the component. The component container should just map the optional external variable to the inner variable, and set its default value:
.my-component-container {
/* map optional "external" variables to required "internal" variables */
--my-variable-inner: var(--my-variable, blue);
}
.my-component-container .my-nested-element {
color: var(--my-variable-inner);
}
.my-component-container .my-other-nested-element {
border-color: var(--my-variable-inner);
}
This way you can ensure that --my-variable-inner is always defined in the component, and make it optional for the external consumers to define --my-variable.
The downside is that you need to remember two variable names instead of one. But here you can think of some project-wide convention, e.g. add --inner or some other suffix to each variable like that.
Declare default values in :root, then override in selectors.
:root {
--primary-color: red;
}
* {
color: var(--primary-color);
border: 1px solid var(--primary-color);
padding: 0.25rem;
margin: 0;
}
div {
--primary-color: green;
}
p {
--primary-color: blue;
}
<div>HI!</div>
…
<p>Bye!</p>
This first attempt makes the text black. Why?
Because this --my-variable: var(--my-variable, blue); is invalid as you are trying to express the same variable with itself which is not allowed so the browser will simply ignore it. Then later when using color: var(--my-variable); the color will fallback to the initial value which is black.
The correct way is to simply define the variable on an upper level and it will get inherited by all the element (like the solution provided by #kornieff)
From the specification:
Custom properties are left almost entirely unevaluated, except that they allow and evaluate the var() function in their value. This can create cyclic dependencies where a custom property uses a var() referring to itself, or two or more custom properties each attempt to refer to each other.
For each element, create a directed dependency graph, containing nodes for each custom property. If the value of a custom property prop contains a var() function referring to the property var (including in the fallback argument of var()), add an edge between prop and the var. Edges are possible from a custom property to itself. If there is a cycle in the dependency graph, all the custom properties in the cycle must compute to their initial value (which is a guaranteed-invalid value).

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);

How to have the same CSS variable names in children components

Hey there I have multiple components within each-other.
I want them all to have the CSS variable --border-radius and pass the value down to the children component's variable.
I have 4 components deep but for this example I'll simplify it to 2 components to illustrate my problem.
Component A
:host{
--border-radius: 50%;
border-radius: var(--border-radius);
component-b {
--border-radius: var(--border-radius);
}
}
Component B
:host{
--border-radius: 0;
border-radius: var(--border-radius);
}
When I inspect component B in the browser, I see it's border-radius is set to "var(--border-radius)" instead of the expected "50%".
Is there a way to accomplish this?
EDIT:
Note Component A & Component B are both stand-alone components that should be able to take the CSS property --border-radius in. The issue is with passing the variable property down to the next component that has the same variable name.
Assuming you wanted to create a CSS variable that stored the border radius of your elements, you can use the :root selector to create a global variable, example :
:root { --border-radius: 50%; }
Okay so after thinking about how CSS works for awhile with the fact that it cascades the styles down... I came to the conclusion that I was thinking about the variables incorrectly.
The way it works is:
Component A defines --border-radius as 10px;
Component B does not define it, but instead uses it if it exists, otherwise uses a default value. By not defining it at the beginning of B, it will automatically cascade down from A.
Component A
:host {
--border-radius: 10px;
border-radius: var(--border-radius);
}
Component B
:host{
border-radius: var(--border-radius, 0);
}
In that example, B will default to 0, but accept A's 10px or any other value you pass directly into B.

Less CSS: Can I call a mixin as an argument when calling another mixin?

I'm trying to call a mixin as an argument in another mixin but I get a syntax error. There's no variables in the offending mixin call, just arguments.
I'm not sure if this is possible. The answers I've seen on here seem to be either hacks or to be dealing with variables and strings as arguments.
Less CSS
// color variables for user's color
#userColor: #13acae;
#darkUser: hsl(hue(#userColor), saturation(#userColor), lightness(tint(#userColor, 30%)));
#lightUser: hsl(hue(#userColor), saturation(#userColor), lightness(shade(#userColor, 30%)));
// color mixin to alter user's color using Less 'darken' and 'contrast' functions
.contrastColorDark(#percent) { color: darken(contrast(#userColor, #darkUser, #lightUser), #percent); }
// border mixin
.border(#width, #color) { border: #width solid #color; }
// CSS rule using both mixins
.thing {
.border(1px, .contrastColorDark(10%));
}
Error (at the dot before .contrastColorDark(10%) )
SyntaxError: expected ')' got '.'
What I am trying to achieve: I am trying to get the box border color to match certain elements inside it that are using the contrast mixin.
As discussed in comments, Less mixins are not functions and the mixin calls cannot return any value. Because of this, one mixin (or its output value) cannot be passed as an argument to another mixin.
Having said that, we can still set a variable within a mixin, call the mixin within each selector block where it is required and make use of the variable defined within it. The mixin call effectively exposes the variable defined within it to the parent scope.
Below is a sample snippet which would call the contrast mixin and assign the calculated value as the text color and border color of the element.
// color variables for user's color
#userColor: #13acae;
#darkUser: hsl(hue(#userColor), saturation(#userColor), lightness(tint(#userColor, 30%)));
#lightUser: hsl(hue(#userColor), saturation(#userColor), lightness(shade(#userColor, 30%)));
// color mixin to alter user's color using Less 'darken' and 'contrast' functions
.contrastColorDark(#percent) {
#color: darken(contrast(#userColor, #darkUser, #lightUser), #percent);
//color: darken(contrast(#userColor, #darkUser, #lightUser), #percent);
}
// border mixin
.border(#width, #color) {
border: #width solid #color;
}
// CSS rule using both mixins
.thing {
.contrastColorDark(10%);
color: #color;
.border(1px, #color);
}
.thing2 {
.contrastColorDark(50%);
color: #color;
.border(1px, #color);
}

Resources