How to fake css variables in scss? - css

I've tinkered around with css variables, finding an interesting application: You can define a --color variable, and use it as value for different attributes based on the class: A button could have its background filled with the --color, while a tab control could use a border-bottom with the --color to highlight the current tab as follows:
:root {
font: 14px sans-serif;
--red: #f44;
--blue: #78f;
--green: #3c1;
--color: var(--blue);
}
.blue {
--color: var(--blue);
}
.red {
--color: var(--red);
}
.green {
--color: var(--green);
}
.fillbutton {
background: var(--color);
color: #fff;
padding: 0.25em 1em;
}
.borderbutton {
background: #fff;
border: 1px solid var(--color);
color: var(--color);
padding: 0.25em 1em;
}
.tab {
border-bottom: 1px solid #ccc;
padding: 0 1em;
line-height: 150%;
}
.tab.current {
border-bottom: 2px solid var(--color);
color: var(--color);
font-weight: bold;
}
<p>
<span class="fillbutton">Default</span>
<span class="fillbutton green">Green</span>
<span class="fillbutton red">Red</span>
<span class="borderbutton">Default</span>
<span class="borderbutton green">Green</span>
<span class="borderbutton red">Red</span>
</p>
<p><span class="tab">Tab1</span><span class="tab current">Tab2</span><span class="tab">Tab3</span></p>
<p><span class="tab red">Tab1</span><span class="tab red current">Tab2</span><span class="tab red">Tab3</span></p>
By defining classes to alter the --color we can easily create different color schemes without having to be specific to the item we want to style: There is only one .red class needed, not separate .red.fillbutton/.red.borderbutton/.red.tab.current
Since css variables are not supported in some browsers I thought I would try to replicate this functionality using a proprocessor like scss, however scss variables seem to be global only and thus cannot mimic this use case. Is there a way that lets me achieve the above behaviour in a similar DRY fashion using scss?

Since generating CSS files via SCSS is easy, you can have separate colour variables SCSS files and master 'import' SCSS file (for all other SCSS files you might have). What you do then is you compile separate CSS files for each of the colour variables files (eg you could have 3 website colour themes, so 3 different colour variables SCSS files and then 3 different output files). When a user selects a different theme, you just remember which theme the user selected (cookie) and you load a proper CSS file via JS, instead of hardcoding which CSS file to use.
It's not completely DRY, as you do have to define "master" output SCSS files (these should include import of a proper colour variable SCSS file, which changes for each file, and an import of "master imports" SCSS file, which is always the same) separately for each colour variable (so if you have 3 colour themes, you would have to define 3 files) - although it can probably be done programatically as well. However, that way you can enable multiple colour themes of your website without worrying about current lack of cross browser support of plain CSS variables. As long as you have a predefined list of colour themes, that's probably the way to go.
If you do not have a predefined list of colour themes and want to enable the users to set up their own colour theme with a colour picker (and don't want them to first submit those values and then use those values, server-side, to generate a CSS file out of them and fetch that file to the user), you could make use of 'currentColor' CSS property. It is supported and can serve as a colour variable, although it's far more limiting in regards to capability than the new CSS variables. Basically, via the currentColor you accesses the colour property of parent element. So you could provide classes and styles for the parents (eg. .blue has a blue colour) and then style pretty much everything via currentColour. Then, when a user changes the colour blue to green via a colourpicker or something, you change the colour of .blue to the selected colour via JS. Obviously, you'd have to remember the user's selection and execute the JS function handling that on document ready. This workaround certainly has it's drawbacks (eg. defining colours via parents will mean lots of extra classes / parent elements that wouldn't otherwise be needed for styling), so it's not an exactly quick method either, but it doesn't rely on generating multiple CSS files.
In my opinion, it's best to use SCSS variables and generate multiple CSS files in case you have predefined colour themes. In case you allow the user to completely modify their colour theme, I'd go with submitting those values to server and generating a proper CSS file and then fetching that CSS file to the user. However, following this principle, the CSS file won't be updated as you make changes to your CSS and deploy those changes. So this method has its' drawbacks as well.
Until native CSS variables are properly supported in all relevant browsers, I fear there is no perfect solution.

After trying different strategies in scss i found the following solution using mixins. First, we must define all our colors, along with their desired class names, as a list variable:
// define all colors
$colors: (red, #f44),
(green, #3c1),
(blue, #78f);
// define default color to be blue
$color: nth(nth($colors, 3), 2);
Then we define a mixin colorize which first defines the given attributes with the default $color, then iterates the $colors list, doing the same thing for each color class, using the parent selector:
#mixin colorize($properties...) {
// for each property assign the default $color
#each $property in $properties {
#{$property}: $color;
}
// for each color class - assign the class color to each property
#each $cls, $col in $colors {
&.#{$cls} {
#each $property in $properties {
#{$property}: $col;
}
}
}
}
Now we can use the colorize-mixin to assign class-specific colors to one or more css properties:
.fillbutton {
#include colorize(background);
color: #fff;
padding: 0.25em 1em;
}
.borderbutton {
#include colorize(border-color, color);
background: #fff;
border: 1px solid;
padding: 0.25em 1em;
}
And finally, use it in our html like this:
<span class="fillbutton green">Green</span>
<span class="fillbutton blue">Blue</span>
<span class="borderbutton">Default</span>
<span class="borderbutton green">Green</span>

Related

CSS variables defaults: set if not already set

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

Workaround for CSS variables in IE?

I'm currently developing a web application in Outsystems in which I have the need to customize the CSS, in which I'm using variables. I need to guarantee the app works cross-browser, including in Internet Explorer. IE doesn't support CSS variables, as you can see in the picture below from this source.
Since I have to use CSS variables, is there any workaround for the usage of variables in IE?
Yes there is a way, the same way you make any css compatible: use a specific css fallback that is supported by the browser.
body {
--text-color: red;
}
body {
color: red; /* default supported fallback style */
color: var(--text-color); /* will not be used by any browser that doesn't support it, and will default to the previous fallback */
}
This solution is incredibly redundant and 'almost' defeats the purpose of css variables....BUT it is necessary for browser compatibility. Doing this would essentially make the css variables useless but I implore you to still use them because it will serve as an important reminder to the fact that these values are referenced elsewhere and need to be updated in all cases, otherwise you forget to update every related occurrence of 'color' and then you have inconsistent styling because relevant css values are out of sync. The variable will serve more as a comment but a very important one.
There is a polyfill, which enables almost complete support for CSS variables in IE11:
https://github.com/nuxodin/ie11CustomProperties
(i am the author)
The script makes use of the fact that IE has minimal custom properties support where properties can be defined and read out with the cascade in mind.
.myEl {-ie-test:'aaa'} // only one dash allowed! "-"
then read it in javascript:
getComputedStyle( querySelector('.myEl') )['-ie-test']
From the README:
Features
handles dynamic added html-content
handles dynamic added , -elements
chaining --bar:var(--foo)
fallback var(--color, blue)
:focus, :target, :hover
js-integration:
style.setProperty('--x','y')
style.getPropertyValue('--x')
getComputedStyle(el).getPropertyValue('--inherited')
Inline styles: <div ie-style="--color:blue"...
cascade works
inheritance works
under 3k (min+gzip) and dependency-free
Demo:
https://rawcdn.githack.com/nuxodin/ie11CustomProperties/b851ec2b6b8e336a78857b570d9c12a8526c9a91/test.html
In case someone comes across this, has a similar issue where I had it set like this.
a {
background: var(--new-color);
border-radius: 50%;
}
I added the background colour before the variable so if that didn't load it fell back on the hex.
a {
background: #3279B8;
background: var(--new-color);
border-radius: 50%;
}
Yes, so long as you're processing root-level custom properties (IE9+).
GitHub: https://github.com/jhildenbiddle/css-vars-ponyfill
NPM: https://www.npmjs.com/package/css-vars-ponyfill
Demo: https://codepen.io/jhildenbiddle/pen/ZxYJrR
From the README:
Features
Client-side transformation of CSS custom properties to static values
Live updates of runtime values in both modern and legacy browsers
Transforms <link>, <style>, and #import CSS
Transforms relative url() paths to absolute URLs
Supports chained and nested var() functions
Supports var() function fallback values
Supports web components / shadow DOM CSS
Watch mode auto-updates on <link> and <style> changes
UMD and ES6 module available
TypeScript definitions included
Lightweight (6k min+gzip) and dependency-free
Limitations
Custom property support is limited to :root and :host declarations
The use of var() is limited to property values (per W3C specification)
Here are a few examples of what the library can handle:
Root-level custom properties
:root {
--a: red;
}
p {
color: var(--a);
}
Chained custom properties
:root {
--a: var(--b);
--b: var(--c);
--c: red;
}
p {
color: var(--a);
}
Nested custom properties
:root {
--a: 1em;
--b: 2;
}
p {
font-size: calc(var(--a) * var(--b));
}
Fallback values
p {
font-size: var(--a, 1rem);
color: var(--b, var(--c, var(--d, red)));
}
Transforms <link>, <style>, and #import CSS
<link rel="stylesheet" href="/absolute/path/to/style.css">
<link rel="stylesheet" href="../relative/path/to/style.css">
<style>
#import "/absolute/path/to/style.css";
#import "../relative/path/to/style.css";
</style>
Transforms web components / shadow DOM
<custom-element>
#shadow-root
<style>
.my-custom-element {
color: var(--test-color);
}
</style>
<div class="my-custom-element">Hello.</div>
</custom-element>
For the sake of completeness: w3c specs
Hope this helps.
(Shameless self-promotion: Check)
Make a seperate .css file for your variables. Copy/paste the contents of the variable.css file to the end of your main.css file. Find and replace all the variable names in the main.css file to the hex code for those variables. For example: ctrl-h to find var(--myWhiteVariable) and replace with #111111.
Side note: if you keep the :root{ } in the main.css file and just comment it out, you can use that to track those hex codes later if you want to update your fallback colors.
Another way to do it is declaring colors in a JS file (in my case I'm using react) and then just use the variable you defined in the JS file.
For example:
in globals.js
export const COLORS = {
yellow: '#F4B400',
yellowLight: '#F4C849',
purple: '#7237CC',
purple1: '#A374EB',
}
in your file
import { COLORS } from 'globals'
and then just use COLORS.yellow, COLORS.purple, etc.
body {
--text-color : red; /* --text-color 정의 */
}
body {
color: var(--text-color, red); /* --text-color 정의되지 않으면 red로 대체됨 */
}
body {
color: var(--text-color, var(--text-color-other, blue));
/* --text-color, --text-color-other 가 정의되지 않으면 blue로 대체됨 */
}
There is no way yet in "normal" css but take a look at sass/scss or less.
here is a scss example
$font-stack: Helvetica, sans-serif;
$primary-color: #333;
body {
font: 100% $font-stack;
color: $primary-color;
}
I recommend setting your css variables as sass variables, then using sass interpolation to render the color in your elements.
:root {
--text-color: #123456;
}
$text-color: var(--text-color);
body {
color: #{$text-color};
}
If im not wrong there is a workaround, the CSS #ID Selector. Which should work for IE > 6 I guess.. So you can
.one { };
<div class="one">
should work as
#one {};
<div id="one">

Compass - include one value for another property

I want to include a value from another class - and use this for something different:
I have this class:
.sourceClass {
color: red;
}
And I have this class:
.destinationClass {
border-color: ###should be the color from .sourceClass => red
}
is this possible? And how can I do that?
You tagged your post with "Sass". Are you using the Sass/SCSS preprocessor? If so, you'd declare and use a variable like this:
$myColor: red;
.sourceClass { color: $myColor; }
.destinationClass { border-color: $myColor; }
If you're not using Sass, you can read about native CSS variables - which are currently working in Firefox and Chrome, but not IE/Edge.
Lastly, there is a possible solution supported in all current browsers, which would be applicable depending on your DOM hierarchy: currentColor.
If your .destinationClass is a child of .sourceClass, and therefore is inheriting color: red, you could simply use border-color: currentColor to take that color and use it as the border color.
Hope this helps!

Updating a Variable through a Mixin

So lets say I set the background of 10 elements on the page to #base, then a user lands on the "Foo" page which has the class on the body of the page.
How does one update the #base via a css declaration? I understand that variables are local to a function (or css declaration) but there must be a method to do this! (would make styling alternative pages so easy!)
#base: #00000;
body.foo{
#base = #FFF;
}
LESS is a Preprocessor so...
...it all has to be precompiled into CSS ahead of time. That means all possible class combinations need to be made into valid CSS ahead of time. If you wanted something like this, you would need to do something like the following in your LESS:
LESS
#base: #000000;
.setColorOptions(#className: ~'', #base: #base) {
#classDot: escape(`('#{className}' == '' ? '' : '.')`);
#class: escape(#className);
body#{classDot}#{class} {
someElement {color: #base;}
.someClass {color: #base;}
// etc.
}
}
.setColorOptions();
.setColorOptions(foo, #fff);
.setColorOptions(bar, #ccc);
CSS Output
body someElement {
color: #000000;
}
body .someClass {
color: #000000;
}
body.foo someElement {
color: #ffffff;
}
body.foo .someClass {
color: #ffffff;
}
body.bar someElement {
color: #cccccc;
}
body.bar .someClass {
color: #cccccc;
}
Obviously if there were many elements and a lot of color dependent things going on, this could get big fast. Imagine 100 elements under body with three color variations as above, and you have 300+ lines of CSS, 200+ (two-thirds) of which do not apply to any one page. And this doesn't account for other changes, like background colors, etc. In such a case, it is best to set up different LESS files that import a different set of values for #base and build different style sheets to be loaded on the pages that need it. However, if you are just doing a small subset of color changes to a page, this could be a valid way to go.
There is no way to do that.
LESS has no way to know whether the selector body.foo will apply at compile time.

Can I create Less color themes with variables that rely on other variables?

Does anyone know how to do what I am attempting to do here?
#theme (dark) {#primary: black;}
#theme (light) {#primary: white;}
#theme (#_) {#primary: yellow;}
#name: dark;
#theme(#name);
.rule {
color: #primary;
}
I am trying to define a few "themes" which will have colors and images (possibly) that will be used throughout the various Less files. I have made do in the past with defining them globally and commenting out those that are not in use, but I am trying to see if there are people who have found better strategies in Less than what I have.
I had at one point found a feature that used to be (?) a part of Less but it doesn't seem to work.
.theme {
#color: red;
}
.rule {
color: .theme > #color;
}
This would be great, if it worked.
After a bit of messing with LESSCSS, I've come up with a reasonable way to change all variables based on a single #theme variable.
The trick is to use variable interpolation to specify a variable reference to a variable.
//this can be either "dark" or "light"
#theme: dark;
#theme-primary: "#{theme}-primary"; //this will evaluate to "dark-primary"
#theme-secondary: "#{theme}-secondary"; //this will evaluate to "dark-secondary"
#dark-primary: #F00;
#dark-secondary: #000;
#light-primary: #333;
#light-secondary: #FFF;
body {
//#theme-secondary evaluates to "dark-secondary"
//#dark-secondary evalutates to #000
background-color: ##theme-secondary;
//#theme-primary evaluates to "dark-primary"
//#dark-primary evaluates to #F00
color: ##theme-primary;
}
Older version
While I don't know of an easy way to conditionally define variables, if you're going to change one word and change a color theme, you might as well change an import statement:
#import ’themes/dark.less’;
//#import ’themes/light.less’;

Resources