I'm running a white label application where many brands are using a set of SASS variables. But over the time, it happens that additional variables needs to be added for a given breakpoint in order to allow more flexibility. Thus, in order to keep backward compatibility, I assign the current value to the newly created variable ($test--large). For the brand, to be able to customize style for large breakpoint.
The issue is that it doesn't work the way i'd expect it to work. If at brand level, I change $test, i'd like $test--large to also have the new value assignment.
Hope it makes sense, if not what would you suggest. Knowing that we have many brands and it would be painful to redefine all the new variables.
$test: blue !default;
$test--large: $test !default; // new variable
$test: green; // Override from brand
.text {
color: $test;
// Newly added piece
#include breakpoint(large) {
color: $test--large; //result will be blue but i'd expect it to be green
}
}
`
It looks like when you assign $test to $test--large, the variable is being passed by value, not reference. Hence when you reassign $test to green, you need to also reassign $test--large, e.g. like this:
$test--large: $test
or
$test--large: green
I wanted to avoid this solution because it will require me to change it in all my project.
The variables override need to happen before and not after the !default values
I'm still wondering if it isn't possible without interchanging the variable import order
$test: green; // Override from brand
$test: blue !default;
$test--large: $test !default; // new variable
.text {
color: $test;
// Newly added piece
#include breakpoint(large) {
color: $test--large; //result will be green as expected
}
}
Related
I have two css variables and two scss variables , the problem is the last scss variable doesn't return a value at all.
note that the values of the two sass variables comes from the css variables.
the first scss variable takes its value normally from the css variable.
:root {
--intro-img-url: url("../images/pexels-los-muertos-crew-7487374.jpg");
--dl-mode: black;
}
// the intro-img scss variable takes its value normally from the
// css variable and there's no problem with it.
$intro-img: var(--intro-img-url);
// but the dl-mode scss variable doesn't take the css variable val
// and just return nothing
$dl-mode: var(--dl-mode);
#if $dl-mode == black {
:root {
--dominant1-wmode-color: #030712;
--dominant1-bmode-color: #ffffff;
}
} #else {
:root {
--dominant1-wmode-color: green;
--dominant1-bmode-color: #030712;
}
}
Even though --dl-mode is set to black in the context of :root, the value of $dl-mode is set to var(--dl-mode). This is what your SCSS compiler will be seeing when it runs the check #if $dl-mode == black, so that check will always be false.
SCSS cannot look inside the values of CSS variables, because it needs to know those values at compile time but those values may depend on context within the HTML itself. For example, consider the following:
:root {
--my-var: black;
}
.class {
--my-var: white;
}
$my-var: var(--my-var);
#if $my-var == black {
// How can you know the value within --my-var if you don't know if you're in a .class element or not?
}
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).
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).
I'm using Laravel Mix and Webpack for SASS pre-processing.
I have two "themes" in my website which I want to be lean, inheriting variables where they need to. For example, my primary theme will include in this order:
// Primary theme
#import "./primary-variables.scss";
#import "/path/to/default/theme/main.scss";
My default theme would look like this:
// Default theme
#import "./default-variables.scss";
#import "~bootstrap-sass/assets/stylesheets/_bootstrap";
Similarly to this question, I've included the primary variables first, then the default theme variables, then bootstrap last.
In my default theme I add !default to all variables so where they are redefining Bootstrap they will be used in priority, and where new they will be a default value. The primary theme doesn't use !default at all.
Working example
If Bootstrap defines $brand-danger as say red !default, my default theme redefines it as blue !default and my primary theme redefines it as yellow, my rendered output will be yellow - great!
The problem
When I need to reference variables that are only defined at other levels from my primary theme. For example:
// Primary theme:
// This fails since I haven't defined $brand-primary in my primary theme
$my-primary-theme-variable: $brand-primary;
The build now fails with an error saying primary-theme/src/scss/main.scss doesn't export content.
Workaround
I can work around this problem by copying the entire Bootstrap variables file through to my primary theme and changing variables as necessary, but I don't really want to do this.
Question
How does the SASS variable processor actually work? Is it possible for me to just change one of the Bootstrap variables in my theme without necessarily having to redefine the entire file?
This question is pretty similar.
It seems like you are using #include to import your SCSS try using #import instead – If this is just a typo in the question please let me know :-)
#import "./primary-variables.scss",
"/path/to/default/theme/main.scss"
;
I've added a few quick notes on the question you were referring to.
The important thing to know about the !default flag is that it takes effect at the point when it is used in a selector and does not re-define variables.
Sass does not look ahead when processing variables – it prints out the current value. In this example .class-1 will be red as the re-definition comes after it being used in the selector and .class-2 will be blue as there is no default flag.
$brand-color: red !default; // defined
.class-1 { background-color: $brand-color; } // red
$brand-color: blue; // re-defined
.class-2 { background-color: $brand-color; } // blue
Default flags will cause Sass to skip variable re-definition. In this example the result will be red as being defined first. The two following re-definitions are ignored because of the default flags.
$brand-color: red !default; // defined
$brand-color: blue !default; // ignored
$brand-color: green !default; // ignored
.class-1 { background-color: $brand-color; } // red
In this case all variables from from the config will be used – then variables from partial-1 if not defined in config and last partial-2 will define any variable not defined in the two others.
#import '_config.scss'; // definition
#import '_partial-1.scss'; // contains defaults
#import '_partial-2.scss'; // contains defaults
Hope it makes sense :-)
Import structure
// _default-theme.scss
#import '_default-variables.scss', '_bootstrap.scss';
// _primary-theme.scss
// primary variables will override defaults or use defaults if not defined
#import '_primary-variables.scss', '_default-theme.scss';
// style.scss
#import '_primary-theme.scss'; // or '_default-theme.scss'
Scope
In case your default and primary has content that is unique to each theme you could create a scoping mixin to handle what is compiled.
Here is a very rudimentary version:
// _scope.scss
$scope-context: null !default;
#function scope($scopes: null, $true: true, $false: false) {
#each $scope in $scope-context {
#if index($scopes, $scope) { #return $true }
}
#return $false;
}
#mixin scope($scopes: null) {
#if scope($scopes) or length($scopes) == 0 and not $scope-context {
#content;
}
}
How it works
The scope mixin takes a context argument and a content block #content. If the passed context matches a global variable ($scope-context) the content block get's rendered.
// _default-theme.scss
.class { content: 'Will show in both themes'; }
#include scope(default-theme){
.class { content: 'Will only show in the default theme'; }
}
#include scope(primary-theme){
.class { content: 'Will only show in the primary theme'; }
}
// can also be used as "if" function
.class {
content: scope(default-theme, 'Is default', 'Not default')
}
In your case define the $scope-context in both default and primary variables
// _default-variables.scss
$scope-context: default-theme !default;
// _primary-variables.scss
$scope-context: primary-theme;
... and add _scope.scss to the _default-theme.scss
// _default-theme.scss
#import '_default-variables.scss', '_bootstrap.scss', '_scope.scss';
The problem I found was that I was assuming things incorrectly about how SASS works.
When you define a variable declaration, the value of it is compiled at the time your write it. For example $my-var: $brand-primary would assign the current value of $brand-primary to $my-var at the time it is processed.
This means simply that I can't achieve what I wanted, which was to include a minimal variables file over the top of Bootstrap, because it would only update the variable itself, but not any other variables that reference that variable within Bootstrap.
The solution
It's not elegant, but duplicate the entire variable file for each theme and adjust them as required in each place.
I have this variable:
$color_pr1: #d6ad3f;
Now, I'm using Gumby and it uses it's own settings sheet where the following is set:
$header-font-color: #55636b !default;
Is it possible to use $color_pr1 instead? Like this?
$header-font-color: $color_pr1; ?
If now, am I thinking about this all wrong?
I'd like to have my own set of colors etc and reuse those within my project.
From the docs: http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#variable_defaults_
You can assign to variables if they aren’t already assigned by adding
the !default flag to the end of the value. This means that if the
variable has already been assigned to, it won’t be re-assigned, but if
it doesn’t have a value yet, it will be given one.
For example:
$content: "First content";
$content: "Second content?" !default;
$new_content: "First time reference" !default;
#main {
content: $content;
new-content: $new_content;
}
is compiled to:
#main {
content: "First content";
new-content: "First time reference"; }
Variables with null values are treated as unassigned by !default:
$content: null;
$content: "Non-null content" !default;
#main {
content: $content;
}
is compiled to:
#main {
content: "Non-null content"; }
Use css calc() function:
$header-font-color: calc(#{$color_pr1});
You can define a map:
From the Sass Documentation
Users occasionally want to use interpolation to define a variable name based on another variable. Sass doesn’t allow this, because it makes it much harder to tell at a glance which variables are defined where. What you can do, though, is define a map from names to values that you can then access using variables.
SCSSSassCSS
SCSS SYNTAX
#use "sass:map";
$theme-colors: (
"success": #28a745,
"info": #17a2b8,
"warning": #ffc107,
);
.alert {
// Instead of $theme-color-#{warning}
background-color: map.get($theme-colors, "warning");
}