Why can't I change a scss variable based on a class? I want the variable to be green when in class dark-mode.
Css:
$test: red;
#mixin darkTheme {
$test: green;
}
.theme-dark {
#include darkTheme();
.test {
background-color: $test;
}
}
Html:
<body class="dark-mode">
<div class="test">test</div>
</body>
How do I accomplish this? What I don't want is 2 variables.
This is because of Variable Scoping.
In Sass, all variables declared outside of a mixin or function will have a global scope and can be referenced in any Sass selector that uses the variable. (source)
This means that any variable value set inside of a mixin or function is only available within that mixin or function, even if the variable was previously set globally.
Switching between different sets of Sass variables
Partials
You could have a partial file for each theme, and import those under each theme's parent class.
_theme-dark.scss
$background-color: #000;
$text-color: #fff;
_theme-light.scss
$background-color: #fff;
$text-color: #000;
_themed-page.scss
body {
background: $background-color;
color: $text-color;
}
theme-styles.scss
.theme-dark {
#import "theme-dark";
#import "themed-page";
}
.theme-light {
#import "theme-light";
#import "themed-page";
}
Maps
Another option is to store the theme values in a map and have a utility function to retrieve the desired value. (source)
_theme-variables.scss
$theme: 'default';
$theme-values: (
'background-color': (
'default': #eee,
'light': #fff,
'dark': #000
),
'text-color': (
'default': #333,
'light': #000,
'dark': #fff
)
);
#function theme-value($key) {
$map: map-get($theme-values, $key);
#return map-get($map, $theme);
}
_themed-page.scss
body {
background: theme-value('background-color');
color: theme-value('text-color');
}
theme-styles.scss
.theme-dark {
$theme: 'dark';
#import "themed-page";
}
.theme-light {
$theme: 'light';
#import "themed-page";
}
I'm trying to write a mixin that will modify the parent selector on output. The idea is that in cases where a mixin is called, the parent selector will need to have a string replacement done on it. I have most of this working, but I can't figure out how to swallow the &.
.test {
#include alt_parent() {
content: 'test';
}
}
The mixin is something like this:
#mixin alt_parent() {
#{str-replace(unquote("#{selector_append(&)}"), "s", "x")} {
#content;
}
}
I have the string replacement working, so that isn't the problem. What I get is this (and I understand why):
.test .text {
content: 'test';
}
What I want is this:
.text {
content: 'test';
}
You have to use the #at-root directive to defeat the automatic inclusion of the selectors represented by &.
http://alwaystwisted.com/articles/2014-03-08-using-sass-33s-at-root-for-piece-of-mind
#mixin alt_parent($parent) {
#at-root {
#{str-replace(unquote("#{selector_append(&)}"), "s", "x")} {
#content;
}
}
}
I use #warn a lot, but I'd like to import pre-written messages into my warnings that are stored for example in another function or file.
Is this possible?
I know the following won't work, but so you get the idea...
#if $a == 'red' {
color: red;
} #else if $a == 'blue' {
color: blue;
} #else {
#warn '#include error-msg_no-color-detected';
}
This can be done using variables...
$msg: 'Something went wrong!';
#if ... {
...
} #else {
#warn: '#{$msg}';
}
This allows me to create a _debug.scss file containing lots of messages.
#import 'config';
#import 'debug';
#import 'dark';
#import 'application';
_debug.scss:
#msg-1: 'Oops!';
#msg-2: 'No color defined. Check the config file.';
This question already has answers here:
How to assign to a global variable in Sass?
(2 answers)
Closed 6 years ago.
Is it possible to update a value in a sass map from within a mixin so that the change is saved globally?
Eg
$obj: (
init: false
)
#mixin set($map) {
#if map-get($obj, init) != true {
// mixin hasn't been called before
$map: map-set($map, init, true);
}
#else {
// mixin has been called before
}
}
.test {
#include set($obj);
// sets the init value to true
}
.test-2 {
#include set($obj);
// init value has already been set to true
}
I'm not sure if I understood what you are trying to do, but your code seems to be fine (haven't tested it though), excepting that there is no map-set function, but you can create one or just use map-merge (check here: http://oddbird.net/2013/10/19/map-merge/). I hope that helps.
#update 1: I think I got your question now, you want to pass the reference through the mixin, so if you have multiple maps, you can send the one you want to update to the mixin, I don't think this is possible though, because no reference is kept, if you need to update the variable you have to link directly to it, for exemple, this works (tested):
$obj: (
init: false
);
#mixin set($map) {
#if map_get($map, init) != true {
$obj: map-set($map, init, true) !global;
body {
background-color: #000;
}
} #else {
body {
background-color: #ff0000;
}
}
}
#include set($obj);
#include set($obj);
But if you reference to $map instead of $obj (in this line $obj: map-set($map, init, true) !global;), then a new global map (called $map), will be created. And every time you call the mixin again, it will be replaced by the map you sent as a parameter.
#update 2: I found a way to do it, but you have to keep a global 'map of maps', and every time you update this guy, you send the name of the map you want to update as parameter, so I came up with the following code, it's tested and working fine :)
#function map-set($map, $key, $value) {
$new: ($key: $value);
#return map-merge($map, $new);
}
$maps: (
obj1: (
init: false
),
obj2: (
init: false
),
);
#mixin set($prop) {
#if map_get(map_get($maps, $prop), init) != true {
$obj: map-set(map_get($maps, $prop), init, true);
$maps: map-set($maps, $prop, $obj) !global;
body {
background-color: #000;
}
} #else {
body {
background-color: #ff0000;
}
}
}
#include set(obj1); //black
#include set(obj2); //black
#include set(obj1); //red
#include set(obj2); //red
source: myself
Following on from #Paulo Munoz
Here is the solution
#function map-set($map, $key, $value) {
$new: ($key: $value);
#return map-merge($map, $new);
}
$extend : ();
$obj : (
margin: 0,
padding: 10
);
#mixin set($map, $name) {
#if map-has-key($extend, $name) {
map: has-key;
// call placeholder class
} #else {
$extend: map-set($extend, $name, true) !global;
map: does-not-have-key;
// create placeholder class
// call placeholder class
}
}
.test {
#include set($obj, test);
}
.test-2 {
#include set($obj, test);
}
which generates
.test {
map: does-not-have-key;
}
.test-2 {
map: has-key;
}
Is there a way to have SASS emulate the way LESS concatenates repeated mixin definitions (redefining a mixin in LESS doesn't overwrite the original).
For instance, would it be possible to push a block of CSS rules into a buffer, and then flush them all at once?
Example:
With LESS I'd do this:
// _menu.less
#_base () { .menu{ /*styles*/ } }
#_mobile () { .menu{ /*styles*/ } }
#_desktop () { .menu{ /*styles*/ } }
...
// _widget.less
#_base () { .widget{ /*styles*/ } }
#_mobile () { .widget{ /*styles*/ } }
#_desktop () { .widget{ /*styles*/ } }
...
and then:
// styles.less
#import "_menu.less";
#import "_widget.less";
#media screen { #_base(); }
#media screen and (max-width:700px) { #_mobile(); }
#media screen and (min-width:701px) { #_desktop(); }
...
// styles-oldie.less
#import "_menu.less";
#import "_widget.less";
#media screen {
#_base();
#_desktop();
}
To the best of my knowledge, there is no way to replicate what you want to achieve by building on to existing mixins. If you define a mixin two times, the second will overwrite the first. See example
AFAIK the common practice in Sass is to use media query mixins inside the selector to keep the code clean and readable. Breakpoint is a popular library that adds a lot of nice functionality for doing this.
An example of the code would be.
#import "breakpoint";
$medium: 600px;
$large: 1000px;
$breakpoint-no-queries: false; // Set to true to ignore media query output
$breakpoint-no-query-fallbacks: true; // Set to true to output no-query fallbacks
$breakpoint-to-ems: true; // Change px to ems in media-queries
.menu {
content: "base";
// Mobile styles
#include breakpoint(min-width $medium - 1) {
content: "mobile";
}
// Tablet styles
#include breakpoint($medium $large) {
content: "tablet";
}
// Desktop styles with no-query fallback
#include breakpoint($large, $no-query: true) {
content: "large";
}
}
This could output (depending on your settings)
.menu {
content: "base";
content: "large";
}
#media (min-width: 37.4375em) {
.menu {
content: "mobile";
}
}
#media (min-width: 37.5em) and (max-width: 62.5em) {
.menu {
content: "tablet";
}
}
#media (min-width: 62.5em) {
.menu {
content: "large";
}
}
You can play around with the settings here
I often have a stylesheet for modern browsers that support media queries set up like this:
// main.scss
$breakpoint-no-queries: false;
$breakpoint-no-query-fallbacks: false;
#import "imports";
And another stylesheet for older browsers that don't support media queries
// no-mq.scss
$breakpoint-no-queries: true;
$breakpoint-no-query-fallbacks: true;
#import "imports";