This question already has an answer here:
Why does Sass change the format of my colors?
(1 answer)
Closed 7 years ago.
I'm currently writing a mixin to easily add multiple fonts in one line. In the mixin I write all the weights I want to use like this:
$font-weights: (
"hairline": 100,
"thin": 200,
"light": 300,
"regular": 400,
"medium": 500,
"semibold": 600,
"bold": 700,
"heavy": 800,
"black": 900,
);
$main-font: "Lato";
#include font-list($main-font,Hairline Thin Light Regular Medium SemiBold Bold Heavy Black, normal, $font-weights);
The last weight in the list of weights given in the #include is "black", all the links go well, but the mixin I use gives an error on the last value, because it in some way converts "black" automatically before using already to #000000.
Is there any way to make Sass not do this?
the Mixin(s):
#mixin font-include($name, $file, $weight, $type) {
#include font-face("#{$name}", font-files("#{$file}.woff", "#{$file}.ttf"), "#{$file}.eot", $weight, $type);
}
#mixin font-list($name,$weights,$type,$font-weight){
#for $i from 1 through length($weights) {
#include font-include($name,#{$name}-#{nth($weights,$i)},map-get($font-weight,#{to-lower-case(nth($weights,$i))}),$type);
}
}
The error given by Compass is:
$string: #000000 is not a string for `to-lower-case'
Fixed it using unquote in the mixin on all weights.
(And added functionality for Italic fonts).
#mixin font-list($name,$weights,$type,$font-weight){
$italic: '';
#if $type == 'italic'{
$italic: 'Italic';
}
#for $i from 1 through length($weights) {
$weight: unquote("#{nth($weights,$i)}");
#include font-include($name,#{$name}-#{nth($weights,$i)}#{$italic},map-get($font-weight,#{to-lower-case($weight)}),$type);
}
}
Related
I'm implementing a websites style.
For legacy support reasons, I need to support IE11, at least for a while. For workflow and my sanity reasons, I would like to use css variables where possible.
I have looked at this solution, which would generate something that works, but it is quite verbose to use.
My goal is to end up with a hardcoded value, which is overwritten by a CSS variable immediately. This way I can add a default theme for everyone, and different themes (like darkmode) for those browsers that support css variables. Obviously without needing to write all of that myself.
So the idea would be that I can write something like this:
$foo {...?}
:root {
--foo: red;
}
:root.dark {
--foo: black;
}
p {
color: $foo;
}
and it gets transpiled to
:root {
--foo: red;
}
:root.dark {
--foo: black;
}
p {
color: red;
color: var(--foo);
}
Is this possible with scss? I do not want to add some random npm modules or other third party compilers and transpilers to this project to bloat it.
I know of the possibility of adding a polyfill for IE11 that adds support for CSS variables, but most that I've found so far have some form of unfortunate limitation (plus again, they are third party code that I would prefer to avoid if I can). If there is no nice solution using SCSS, that is probably what I will go with.
Here is a quick solution you might want to improve:
Define a map with all your colors:
$colors: ("blue": #0000ff, "red": #ff0000, "green": #00ff00);
Loop over the map to create the css custom properties:
#each $color, $value in $colors {
:root {
--#{$color}: #{$value};
}
}
Create a mixin to output both the fallback and the value.
I've decided to create a mixin that takes 2 params, the css property and the color you want.
#mixin propertyPlusColorValue($property, $color) {
#{$property}: map.get($colors, $color);
#{$property}: var(--#{$color});
}
Then you can use it like this:
.foobar {
#include propertyPlusColorValue(color, "blue");
#include propertyPlusColorValue(background-color, "red")
}
Full code:
#use "sass:map";
$colors: ("blue": #0000ff, "red": #ff0000, "green": #00ff00);
#each $color, $value in $colors {
:root {
--#{$color}: #{$value};
}
}
#mixin propertyPlusColorValue($property, $color) {
#{$property}: map.get($colors, $color);
#{$property}: var(--#{$color});
}
.foobar {
#include propertyPlusColorValue(color, "blue");
#include propertyPlusColorValue(background-color, "red")
}
I need to see if there is a way to parse out variables that are effectively, dynamically generated.
I have a list of 'color' definitions from a variables.scss file in my application:
$colorsList: "primary" "success" "info" "warning" "danger" "inverse";
I then have a number of variables that map to various colors:
$primary-color: #558bc3 !default;
$success-color: #259d2c !default;
$info-color: rgba(43, 169, 251, 0.39) !default;
$warning-color: #FAAA00 !default;
$danger-color: #c82e2d !default;
$inverse-color: #000;
I am trying to create an SCSS page that will effectively create some generated color blocks/css rules:
#each $color in $colorsList {
$col: "$color-#{$color}";
$bgcol: "$color-#{$color}";
.bg-#{$color} {
background-color: "#{$bgcol}";
}
.color-#{$color} {
color: "#{$col}";
}
}
This results in the following output:
.bg-primary {
background-color: "$color-primary"; }
.color-primary {
color: "$color-primary"; }
.bg-success {
background-color: "$color-success"; }
Essentially, what I would like to do - is have those variables in the output parsed to the variable values: $color-success gets changed with #259d2c, and so forth.
Is there a way to do this? Or maybe some workaround?
No, what you are trying is not possible, at least so for. A SCSS variable will be interpreted only once, as you can read on the official documentation:
Sass variables are simple: you assign a value to a name that begins with $, and then you can refer to that name instead of the value itself
So when you call background-color: "#{$bgcol}", $bgcol gets replaced by its value, which is just a string in the eye of SCSS, as any interpolated result:
Interpolation can be used in SassScript to inject SassScript into unquoted strings. This is particularly useful when dynamically generating names (for example, for animations), or when using slash-separated values. Note that interpolation in SassScript always returns an unquoted string.
What you can do that's common in the SCSS world is to map your color names to your colors with the help of a map, like so:
#use "sass:map";
$colorsList: "primary" "success" "info" "warning" "danger" "inverse";
$mapedColorsList: (
"primary": #558bc3,
"success": #259d2c,
"info": rgb(43 169 251 / 39%),
"warning": #faaa00,
"danger": #c82e2d,
"inverse": #000,
);
#each $color in $colorsList {
.bg-#{$color} {
background-color: map.get($mapedColorsList, $color);
}
.color-#{$color} {
color: map.get($mapedColorsList, $color);
}
}
I'm working on a project that requires to be themeable at runtime. So I created a theme system that combines SCSS variable with CSS Variables.
This is how it looks.
:root {
--primary-color: 196;
}
// Primary
$primary-100: hsl(var(--primary-color), 90%, 98%);
$primary-400: hsl(var(--primary-color), 90%, 65%);
$primary-main: hsl(var(--primary-color), 90%, 50%);
$primary-700: hsl(var(--primary-color), 90%, 30%);
$primary-900: hsl(var(--primary-color), 90%, 10%);
While this works amazingly with my custom components, I'm having a hard time making it work with the Material design theme system.
My thinking was that I will create the theme as explained in the Angular material docs, and instead of using static colors, I will use my SCSS variables. this is how my theme.scss file looks like.
#import '~#angular/material/theming';
#import 'var.scss';
#include mat-core();
$shop-primary: (
50: $primary-100,
100: $primary-100,
200: $primary-200,
300: $primary-300,
400: $primary-400,
// ..... all other colors
contrast: (
50: $black-87-opacity,
100: $black-87-opacity,
200: $black-87-opacity,
// ..... all other colors
)
);
$shop-app-primary: mat-palette($shop-primary);
$shop-app-accent: mat-palette($shop-primary);
$shop-app-warn: mat-palette($shop-primary);
$shop-app-theme: mat-light-theme($shop-app-primary, $shop-app-accent, $shop-app-warn);
#include angular-material-theme($shop-app-theme);
And I'm getting an error:
Argument `$color` of `rgba($color, $alpha)` must be a color
Presumingly because the Angular Material mixin is expecting a color and not a hsl() value.
So my question is how would I be able to create a custom material theme with runtime CSS variables?
I created a little library to make this a little easier.
You can use it like so:
Install:
npm i angular-material-css-vars -S
Then remove any existing #import '~#angular/material/theming'; from your main stylesheet file.
Add this to your main stylesheet instead:
#import '~angular-material-css-vars/main';
#include initMaterialCssVars();
Change the main theme color like so:
import {MaterialCssVarsService} from 'angular-material-css-vars';
export class SomeComponentOrService {
constructor(public materialCssVarsService: MaterialCssVarsService) {
const hex = '#3f51b5';
this.materialCssVarsService.changePrimaryColor(hex);
}
}
If you upgrade to #angular/material 7.3.4 CSS Variables will mostly work. Only riples and other stuff that uses opacity will need a little fix. I use rgba() for my project, but it should also work for hsla()
Include this:
#function mat-color($palette, $hue: default, $opacity: null) {
#if type-of($hue) == number and $hue >= 0 and $hue <= 1 {
#return mat-color($palette, default, $hue);
}
$color: map-get($palette, $hue);
#if (type-of($color) != color) {
#if ($opacity == null){
#return $color;
}
// Here is the change from the original function:
// If the $color resolved to something different from a color, we assume it is a CSS variable
// in the form of rgba(var(--rgba-css-var),a) and replace the 'a' value.
#return #{str-slice($color, 0, str-index($color, ',')) + $opacity + ')'};
}
#return rgba($color, if($opacity == null, opacity($color), $opacity));
}
directly after:
#import '~#angular/material/theming';
and define your colors like this:
--primary-color-50-parts: 0,158,224;
// ... all other colors
$color-primary: (
50: rgba(var(--primary-color-50-parts), 1),
// ... all other colors
);
if you define your colors in the map like this:
50: hsla(var(--primary-color), 90%, 98%, 1);
then you need to change str-index($color, ',') in the sass function to something that finds the last ',' in the string. Unfortunatelly my sass knowledge covers only the bare minimum and I don't know how to do that :/
I created a little library - material-theme-creator
You can theming your angular-application or use this approach to create themes
NPM: https://www.npmjs.com/package/material-theme-creator
DOCS: https://artik-man.github.io/material-theme-creator/
npm i material-theme-creator
#import "~material-theme-creator/ngx-mtc";
#import '~#angular/material/theming';
#include mat-core();
#include ngx-mtc-init();
$primary-map: ngx-mtc-create-theme-map('primary');
$accent-map: ngx-mtc-create-theme-map('accent');
$warn-map: ngx-mtc-create-theme-map('warn');
:root {
--is-dark-theme: 1; // Is dark theme? 1 or 0;
#include ngx-mtc-theme-base(); // Creates base colors
// Creates theme colors
#include ngx-mtc-create-variables-from-color('primary', #009688, 38%);
#include ngx-mtc-create-variables-from-color('accent', #2196f3, 57%);
#include ngx-mtc-create-variables-from-color('warn', #f44336, 62%);
}
// Creates Angular Material Theme
#include angular-material-theme(
ngx-mtc-custom-theme(
mat-palette($primary-map),
mat-palette($accent-map),
mat-palette($warn-map)
)
);
The second theme code:
.second-theme {
--is-dark-theme: 0;
#include ngx-mtc-update-theme('primary', #142148, 45%);
#include ngx-mtc-update-theme('accent', #658e14, 50%);
#include ngx-mtc-update-theme('warn', #750101, 50%);
}
You can use it with Angular Material or SCSS or pure CSS
Sooo... css variables are runtime, not compile time. SASS doesn't know what to do with them. You should be able to refactor your css vars using the ${} interpolation of SCSS and have everything still work the same. http://sass-lang.com/documentation/file.SASS_REFERENCE.html#interpolation_
$primary-color: 196;
:root {
--primary-color: #{$primary-color};
}
$primary-100: hsl($primary-color, 90%, 98%);
TL;DR: How can you use SCSS to have CSS variables with a fallback for older browsers.
I'm trying to make sense of this article. In my opinion, you have to already be an advanced SASS user to understand it, which I'm not. To make matters worse, it's the only article I found on the subject.
Here is what I'm trying to achieve:
My scss should be along the lines of :
body {
#include v(background-color, primary)
}
then the processed CSS should be
body{
background: yellow; /* Yellow being defined above as the primary color */
background: var(--color-primary);
}
By playing around a bit, I can already get the value of the CSS variable like so:
$colors: (
primary: yellow,
);
:root {
#each $name, $color in $colors {
--color-#{$name}: $color;
}
}
#mixin background-color($color_) {
background: var(--color-#{$color_});
}
To use it:
body{
#include background-color(primary);
}
Which will result in this:
body {
background: var(--color-primary);
/* But the fallback is missing :(, I tried things with the map-get but it's really eluding me... */
}
If you're using Sass, you can automate fallbacks through a Sass mixin. Create a map of your CSS variable names and their values, and then you can look up those values in a mixin that outputs the fallback style and the preferred one
$vars: (
primary: yellow,
);
:root {
--primary: map-get($vars, primary);
}
#mixin var($property, $varName) {
#{$property}: map-get($vars, $varName);
#{$property}: var(--#{$varName});
}
The above mixin is used like so:
body {
#include var(background-color, primary);
}
and outputs the following CSS:
:root {
--primary: yellow;
}
body {
background-color: yellow;
background-color: var(--primary);
}
Et voilĂ :)
Update: Postcss Custom properties can do fallback and is way easier than the below code
step 1: declare scss variables
So first of all we want to put some variables in a $map, I'll go with color variables:
$colors: (
primary: #FFBB00,
secondary: #0969A2
);
step 2: automate css 4 var generation
// ripped CSS4 vars out of color map
:root {
// each item in color map
#each $key, $value in $colors {
--colors-#{$key}: $value;
}
}
What happens in root is : for each key and value in the colors map, we print the followng :
--colors-#{$key}: $value;
Which corresponds to css variable declarations. I believe the weird bit with #{} around the key is to not have spaces around the value.
Thus the result is:
--colors-primary: #FFBB00,
--colors-secondary: #0969A2
Note that the prefix (--colors-) is the same name as the scss color map above it. The why will become clear in last step.
step 3: Plenty of maps !
$props: (
background-color: $colors
);
$map-maps: (
background-color: colors
);
Here we add the map $props which maps a css property to the map containing the values. background-color will hold color, so the correct map is $colors.
map-maps is a copy of props where instead of the map we have the name of said map. (this is relative to the note in step 2).
Step 4 : let's make it work !
#mixin v($prop, $var) {
// get the map from map name
$map: map-get($props, $prop);
// fallback value, grab the variable's value from the map
$var-fall: map-get($map, $var);
// our css4 variable output
$var-output: var(--#{$map}-#{$var});
#{$prop}: $var-fall;
// css4 variable output
#{$prop}: $var-output;
}
body{
#include v(background-color, primary);
}
I simplified the code in the article quite a bit, it still works, for this example at least, the code in the article takes more into account.
Anyhow, here is what happens.
First, we call the mixin with:
#include v(background-color, primary);
Then upon entering,
$map: map-get($props, $prop); // map-get($props, background-color)
we have a variable called $map to which we assign the value that is inside the $props map at the key background-color which happen to be the $colors map. It's a bit of a maze but it's not that complicated once you resolve it.
Then for the fallback:
$var-fall: map-get($map, $var);
This simply gets the value of the map we just got (which is $colors) at the $var key (which happens to be primary). Thus the result is #FFBB00.
For the css var
$map-name: map-get($map-maps, $prop);
$var-output: var(--#{$map-name}-#{$var});
we recreate what we did to generate the var in the #each loop
Whole code would be :
$colors: (
primary: #FFBB00,
secondary: #0969A2
);
// ripped CSS4 vars out of color map
:root {
// each item in color map
#each $name, $color in $colors {
--colors-#{$name}: $color;
}
}
$props: (
background-color: $colors,
color: $colors
);
$map-maps: (
background-color: colors
);
#mixin v($prop, $var) {
// get the map from map name
$map: map-get($props, $prop);
// fallback value, grab the variable's value from the map
$var-fall: map-get($map, $var);
// our css4 variable output
$map-name: map-get($map-maps, $prop);
$var-output: var(--#{$map-name}-#{$var});
#{$prop}: $var-fall;
// css4 variable output
#{$prop}: $var-output;
}
body{
#include v(background-color, primary);
}
Now this is a simplification of what is done in the article. You should check it out to have code a bit more robust.
I assume you are aware of the reason why it didn't show the fallback. But since it's an answer I will explain the reasons
The current mixin block has only one background property which makes the sass compiler to generate only one property. I don't think sass can identify whether 'var' is supported in browser or not. So, we have to explicitly specify if we need the fallback.
Since you already have the map all you need is to get the value by giving the key 'primary'
#mixin background-color($color_) {
background: var(--color-#{$color_});
background: map-get($colors, primary);
}
This will add the background: yellow to the body class always. Alternatively if you want to control the addition of the fallback based on condition. You can do like this
#mixin background-color($color_, $showFall) {
background: var(--color-#{$color_});
#if $showFall {
background: map-get($colors, primary);
}
}
and call like this
body{
#include background-color(primary, true);
}
Code pen for the same
https://codepen.io/srajagop/pen/xdovON
Note: I am writing the answer under the assumption that you want only the background-color to work and not all the other properties like mentioned in that post. For that you need to create a proper data structure
This question already has answers here:
Creating or referencing variables dynamically in Sass
(7 answers)
Closed 7 years ago.
I have a map in Sass with keys and values
$colors: (blue: #0081d2, green: #288600, orange: #e57323);
#each $color, $value in $colors {
.#{$color} {
color: $value;
}
}
This works and gives me css classnames with the appropriate values, like:
.blue{
color: #0082d1;
}
What is would like to have is a function that produces sass variables based on the keys in the map.
$blue: #0082d1;
$green: #288600;
I have been breaking my head but can't get it to work.
You could create a function to return the value by the key specified.
Example
$colors: (blue: #0081d2, green: #288600, orange: #e57323);
#function color($color) {
#return map-get($colors, $color);
}
.bacon {
color: color(blue);
}
Results in
.bacon {
color: #0081d2;
}
The whole point of a map is to have data structured in a more hierarchical way and not just as a bunch of variables. If you don't want this, define variables instead of a map in the first place.
But I'm guessing what you really want is a way to access map values. Try this:
map-get($colors, blue)