Sass ampersand and two parents together in mixin? - css

I'm faced with the task of theming the site. I found a suitable mixin. Everything would work well, if not for the mixin for events. It turns out that I need to do something, so that if the topic's mixin is invoked in the mixin of events, then the class did not go cascade, but substituted for the topic class, the .no-touchevents class on the html tag.
Ideally, that would be so on the output:
.card {
color: #fff;
}
.t-dark .card {
color: #000;
}
.no-touchevents .card:hover {
color: #000;
}
.t-dark.no-touchevents .card:hover {
color: #fff;
}

It's a little hacky (or maybe a lot hacky) but by adding an additional parameter to the themify mixin and building our selector manually, you can achieve the output you're looking for.
$themes: (
dark: (
colorDark: #000,
colorLight: #fff
)
);
#mixin on-event() {
.no-touchevents &:hover {
#content;
}
}
#mixin themify($themes, $flat: ' ') { // Add a parameter to separate by default
#each $theme, $map in $themes {
#at-root .t-#{$theme}#{$flat}#{&} { // Build our selector manually
$theme-map: () !global;
#each $key, $submap in $map {
$value: map-get(map-get($themes, $theme), '#{$key}');
$theme-map: map-merge($theme-map, ($key: $value)) !global;
}
#content;
$theme-map: null !global;
}
}
}
#function themed($key) {
#return map-get($theme-map, $key);
}
.card {
color: #fff;
#include themify($themes) {
color: themed('colorDark')
}
#include on-event() {
color: #000;
#include themify($themes, '') { // Tell themify not to separate selectors
color: themed('colorLight')
}
}
}

Related

Use Sass #content and unify parent selector to immediate child selector

I'm trying to use Sass / SCSS to unify the #content directive to the parent class. I am attempting to use a conditional statement within the mixin to allow for both use cases:
$themes: (
Light: (
page_background: #ffffff,
),
Dark: (
page_background: #181818,
)
);
#function get_color($key) {
#return map-get($theme-map, $key);
}
#mixin theme($makeAncestor: true) {
#each $theme, $map in $themes {
$theme-map: $map !global;
#if $makeAncestor {
.#{$theme} & {
#content;
}
} #else {
.#{$theme} & {
#content;
}
}
}
$theme-map: null !global;
}
.fixed {
#include theme() {
background: get_color(page_background);
}
}
Here is the output:
.Light .fixed {
background: #ffffff;
}
Desired output:
.Light.fixed {
background: #ffffff;
}
You can use #at-root in your mixin and interpolate the parent selector:
#mixin theme($makeAncestor: true) {
#at-root {
#each $theme, $map in $themes {
$theme-map: $map !global;
#if $makeAncestor {
.#{$theme}#{&} {
#content;
}
} #else {
.#{$theme}#{&} {
#content;
}
}
}
$theme-map: null !global;
}
}

How to implement switchable themes in scss?

I have an existing project with a scss file that uses semantic variables:
$background-color: white;
body {
background-color: $background-color;
}
I would like to change the background to black when I add a theming class to the body:
<body class="theme-dark">...</body>
and back to white if I remove the class (or switch to a theme-light).
I haven't found any light-weight methods to do this in scss (parametrizing a class for each theme seems like a very hard to maintain approach).
I've found a hybrid scss/css-custom properties solution:
original:
.theme-light {
--background-color: white;
}
update (based on Amar's answer):
:root {
--background-color: white;
}
.theme-dark {
--background-color: black;
}
$background-color: var(--background-color);
body {
background-color: $background-color;
}
Defining the scss variable as having a css-variable expansion as the value, i.e. (from above):
$background-color: var(--background-color);
generates the following css:
:root { --background-color: white; }
.theme-dark { --background-color: black; }
body { background-color: var(--background-color); }
which seems to be what we want...?
I like it since it only requires changing the definition of $background-color (not every usage in a very large scss file), but I'm unsure if this is a reasonable solution? I'm pretty new to scss, so maybe I've missed some feature..?
Doing this with SCSS is possible but you would have to add styles to all elements you want to theme. That is because SCSS is compiled at build-time and you can't toggle the variables with classes. An example would be:
$background-color-white: white;
$background-color-black: black;
body {
background-color: $background-color-white;
}
.something-else {
background-color: $background-color-white;
}
// Dark theme
body.theme-dark {
background-color: $background-color-black;
.something-else {
background-color: $background-color-black;
}
}
The best way to currently do it is by using CSS variables. You would define the default variables like this:
:root {
--background-color: white;
--text-color: black;
}
.theme-dark {
--background-color: black;
--text-color: white;
}
Then, you would use these variables in your elements like this:
body {
background-color: var(--background-color);
color: var(--text-color);
}
If the body element has the theme-dark class, it will use the variables defined for that class. Otherwise, it will use the default root variables.
All credit goes to Dmitry Borody
I would recommend an approach like what is mentioned in this Medium article. With this approach, you can assign what classes need to be themed without specifically mentioning the theme name so multiple themes can be applied at once.
First, you set up a SASS map containing your themes. The keys can be whatever makes sense to you, just make sure that each theme is using the same name for the same thing.
$themes: (
light: (
backgroundColor: #fff,
textColor: #408bbd,
buttonTextColor: #408bbd,
buttonTextTransform: none,
buttonTextHoverColor: #61b0e7,
buttonColor: #fff,
buttonBorder: 2px solid #fff,
),
dark: (
backgroundColor: #222,
textColor: #ddd,
buttonTextColor: #aaa,
buttonTextTransform: uppercase,
buttonTextHoverColor: #ddd,
buttonColor: #333,
buttonBorder: 1px solid #aaa,
),
);
Then use the mixin and function pair to add theme support.
body {
background-color: white;
#include themify {
background-color: theme( 'backgroundColor' );
}
}
.button {
background-color: lightgray;
color: black;
#include themify {
background-color: theme( 'buttonBackgrounColor' );
color: theme( 'buttonTextColor' );
}
&:focus,
&:hover {
background-color: gray;
#include themify {
background-color: theme( 'buttonBackgroundHoverColor' );
color: theme( 'buttonTextHoverColor' );
}
}
}
If you're going to be adding a lot of themes or a theme will be touching a lot of stuff, you might want to set up your SCSS files a little differently so that all the theming doesn't bloat your main CSS file (like the example above would do). One way to do this might be to create a themes.scss file and replicate any selector paths that need theming and have a second build script that outputs just the themes.scss file.
The Mixin
#mixin themify( $themes: $themes ) {
#each $theme, $map in $themes {
.theme-#{$theme} & {
$theme-map: () !global;
#each $key, $submap in $map {
$value: map-get(map-get($themes, $theme), '#{$key}');
$theme-map: map-merge($theme-map, ($key: $value)) !global;
}
#content;
$theme-map: null !global;
}
}
}
The Function
#function themed( $key ) {
#return map-get( $theme-map, $key );
}

Reduce amount of CSS generated by a SASS function

I'm very new to the (I guess) more advanced features of SASS. Referencing this article I've been able to create a SASS setup that lets me easily do dark mode theming! I'd like to reduce the amount of CSS it generates, however, and wanted to know if I've reached the limit of my knowledge or just what SASS/CSS is capable of.
I have this map of "themes":
$themes: (
light: (
primaryOne: #f0f,
primaryTwo: #0ff
),
dark: (
primaryOne: #000,
primaryTwo: #111
)
);
I have these mixins/functions that were mostly lifted from the article:
#mixin themify($themes: $themes) {
#each $theme, $map in $themes {
$theme-map: () !global;
#each $key, $submap in $map {
$value: map-get(map-get($themes, $theme), '#{$key}');
$theme-map: map-merge($theme-map, ($key: $value)) !global;
}
#if $theme == dark {
#media (prefers-color-scheme: dark) {
#content;
}
} #else {
#content;
}
$theme-map: null !global;
}
}
#function themed($key) {
#return map-get($theme-map, $key);
}
And finally a custom mixin and the class where I'm using it:
#mixin set-color-primary {
#include themify($themes) {
color: themed('primaryOne');
}
}
.foo-container div {
#include set-color-primary;
}
Finally this generates CSS like the following:
.foo-container div {
color: #f0f;
}
#media (prefers-color-scheme: dark) {
.foo-container div {
color: #000;
}
}
I was hoping I could squeeze out a few more lines and get something more like:
.foo-container div {
color: #f0f;
#media (prefers-color-scheme: dark) {
color: #000;
}
}
Is this a limitation of my SASS knowledge or is this not possible in plain CSS?

SASS – looping through map via mixin doesn’t compile CSS

what I have is a simple SASS color map:
$brand_col: (
def: blue,
mus: red,
ser: yellow
);
The following:
#each $brand, $col in $brand_col {
body.#{$brand} {
background: $col;
}
}
leads to expected output:
body.def { background: blue; }
body.mus { background: red; }
body.ser { background: yellow; }
When I try to put the same thing into a mixin like so:
$color: null;
#mixin branding {
#each $brand, $col in $brand_col {
&.#{$brand} {
$color: $col;
#content;
}
}
}
.body { #include branding { background: $color; } }
I would expect the same output, but nothing is getting compiled at all. I copied the mixin from a sass specific site and don’t fully understand the whole process. Any hints what I'm doing wrong?
Thanks
Ralf
To achive the same result as in your first example, have two options:
Option 1
Make a simple non-reusable mixin:
$brand_col: (
def: blue,
mus: red,
ser: yellow
);
#mixin branding {
#each $brand, $col in $brand_col {
&.#{$brand} {
background: $col;
}
}
}
.body {
#include branding;
}
This will compile to:
.body.def {
background: blue;
}
.body.mus {
background: red;
}
.body.ser {
background: yellow;
}
Option 2
Make a reusable mixin, so you can pass the color map to apply:
$brand_colors: (
def: blue,
mus: red,
ser: yellow
);
#mixin branding($colors) {
#each $class, $color in $colors {
&.#{$class} {
background: $color;
}
}
}
.body {
#include branding($brand_colors);
}
// Latter you can use it to apply the same 'branding' for any other element
div {
#include branding($brand_colors);
}
Will compile to:
.body.def {
background: blue;
}
.body.mus {
background: red;
}
.body.ser {
background: yellow;
}
div.def {
background: blue;
}
div.mus {
background: red;
}
div.ser {
background: yellow;
}
You could even implement a second parameter to the mixin to specify which css property you want to apply, with background as a default:
#mixin branding($colors, $property: background) {
#each $class, $color in $colors {
&.#{$class} {
#{$property}: $color;
}
}
}
// Latter you can use it to apply the same 'branding' for any other element and property
h1 {
#include branding($brand_colors, color);
}
Will compile to:
h1.def {
color: blue;
}
h1.mus {
color: red;
}
h1.ser {
color: yellow;
}
You can find out more about mixins here.
Hope it helps!
What do you mean by $color: $col;? no such property like "null" in CSS, because when you set $color: null at top and then trying to set a property $color: $col; you actually trying to set like that null: blue; this is nothing mean anything to compiler.
I think you no need #content directive use here. You should try just following way:
$brand_col: (
def: blue,
mus: red,
ser: yellow
);
#mixin branding {
#each $brand, $col in $brand_col {
&.#{$brand} {
background: $col;
}
}
}
.body { #include branding(); }

Sass and libraries for theming [duplicate]

I'm refactoring some of my Sass code and I came across a weird issue. My code currently looks like this:
// household
$household_Sector: 'household';
$household_BaseColor: #ffc933;
// sports
$sports_Sector: 'sports';
$sports_BaseColor: #f7633e;
// the mixin to output all sector specific css
#mixin sector-css($sector_Sector,$sector_BaseColor) {
.sector-#{$sector_Sector} {
&%baseColor {
background-color: $sector_BaseColor;
}
}
}
// execute the mixin for all sectors
#include sector-css($household_Sector, $household_BaseColor);
#include sector-css($sports_Sector, $sports_BaseColor);
.product-paging {
h2 {
#extend %baseColor;
}
}
DEMO
The compiled result looks like this:
.product-paging h2.sector-household {
background-color: #ffc933;
}
.product-paging h2.sector-sports {
background-color: #f7633e;
}
But what I need is this:
.sector-household.product-paging h2 {
background-color: #ffc933;
}
.sector-sports.product-paging h2 {
background-color: #f7633e;
}
What I don't understand is why my placeholder (&%baseColor) isn't attached to the parent selector (&%baseColor) as I added the ampersand right in front of it?
Is this maybe a bug when combining & and %? Is there another solution on how to achieve what I want?
EDIT
Alright I figured out why this isn't possible. Anyway is there a workaround for what I'd like to achieve?
Extends, as you've already discovered, can get rather messy. I would go about solving your problem by using an #content aware mixin in combination with global variables (this uses mappings, which are part of 3.3... you can do it with lists of lists, but it's a little less elegant):
$base-color: null; // don't touch
$accent-color: null; // don't touch
$sections:
( household:
( base-color: #ffc933
, accent-color: white
)
, sports:
( base-color: #f7633e
, accent-color: white
)
);
// the mixin to output all sector specific css
#mixin sector-css() {
#each $sector, $colors in $sections {
$base-color: map-get($colors, base-color) !global;
$accent-color: map-get($colors, accent-color) !global;
&.sector-#{$sector} {
#content;
}
}
}
.product-paging {
#include sector-css() {
h2 {
background-color: $base-color;
}
}
}
Output:
.product-paging.sector-household h2 {
background-color: #ffc933;
}
.product-paging.sector-sports h2 {
background-color: #f7633e;
}
Update: Since you want to guarantee that the sector class is always at the top, you just need to switch around a little.
#mixin sector-css() {
#each $sector, $colors in $sections {
$base-color: map-get($colors, base-color) !global;
$accent-color: map-get($colors, accent-color) !global;
.sector-#{$sector} {
#content;
}
}
}
#include sector-css() {
&.product-paging {
h2 {
background-color: $base-color;
}
h3 {
background-color: #CCC;
}
h2, h3 {
color: $accent-color;
}
}
}

Resources