I work on styles for multi-tenant application for which the choice has been made to use SASS. Following the latest version guidelines, I started building base styles using #use and #forward. Now I want to theme this base with customers' colors and fonts variables to receive multiple stylesheets that can be served to different instances of our application.
If I was to use deprecated #import, I would go like this:
styles/customerA/index.scss
#import "customerA-variables";
#import "../base/index";
But with new rules I cannot find an easy way to simply feed theme specific variables to the base styles. I tried to use with keyword, but it turns out I need to define variables in a module, while I would rather encapsulate them in another module and import it.
styles/customerA/index.scss
#use "customerA-variables" as variables;
#use "../base/index" with (
$bgColor: variables.$bgColor,
$textColor: variables.$textColor
);
styles/base/_base-variables.scss
$bgColor: #eee !default;
$textColor: #333 !default;
styles/base/index.scss
/* HOW IT WORKS */
$bgColor: #eee !default;
$textColor: #333 !default;
/* HOW I WISH IT WORKED */
#use "./base-variables" as *;
/* LATER IN THE FILE */
body {
background-color: $bgColor;
color: $textColor;
}
In #use "../variables" as * scenario I get this error:
Error: This variable was not declared with !default in the #used module.
I'm looking for a working solution preferably without copy-pasting all theme variables inside with parenthesis.
First the error:
Error: This variable was not declared with !default in the #used module.
That error arises when you try to #use a module/file with configuration variables but the configuration variables are not set as !default variables in the module/file. So, SASS checks if you are passing a configuration which is not provided to be configurated for the module. That's additional security to you.
I am not quite sure if I did understand your example right, but it could be the way:
// styles/customerA/index.scss
#use "customerA-variables" as variables;
#use "../base/index" with (
$bgColor: variables.$bgColor,
$textColor: variables.$textColor
);
--> SASS ERROR because $bgColor and/or $textColor
--> in #used module/file are not set as defaults, i.e.:
--> $bgColor: green;
--> must be: $bgColor: green !default;
So, you may check the module if the variables are all set to defaults and not overwritten by non-default values.
**SECOND: usage of #use:
The new rule #use indeed is really confusing ... in your example that leads to doubled code: once when you set the custom vars in customerA-variables.scss and then when you repeat that variables when you #use the module/file in styles/customerA/index.scss (see your second code example).
A good way to avoid that doubled code is to prepare a configuration file with the settings for the individual customer and THAN #use the configuration file (not the wanted module/file direct).
Example:
// ###
// ### module/file: ../base/index.scss
$bgColor: #eee !default;
$textColor: #333 !default;
body {
background-color: $bgColor;
color: $textColor;
}
// ###
// ### customer configuration file: configModuleCustomerA.scss
#forward "../base/index" with (
$bgColor: red,
$textColor: blue
);
// ###
// ### use that configuration file
// ### to your main file: styles/customerA/index.scss
#use "...path.../configModuleCustomerA" as *;
// Note:
// 1. that writes the configurated css
// 2. and it provides the variables for additional use to THIS file
// see going on this file below ...
.additionalElement {
background: $bgColor;
color: $textColor;
}
// ###
// ### ---> that compiles to CSS:
body {
background-color: red;
color: blue;
}
.additionalElement {
background: red;
color: blue;
}
NOTE: there is an additional TRICK/EFFECT you should know.
YOU ONLY NEED TO SET THE VARIABLES ONCE ... just the time when you config the module/file. And as you do it in a/the module config file the variables you set there are part of you project configuration!
So, if you need the SAME variables of a module a second/third/... time (i.e. in additional partials files) you #use that configuration file in any file where you need it/them. Don't worry: the css code is only compiled ONCE to your CSS: the first time you #use the module.
BUT HEAD UP IN YOUR CASE:
But if you want to #use a module/file with different configurations as in your case you have to compile it into two DIFFERENT CSS files. One module with two or more different configurations loaded to the same CSS is blocked by SASS. In that case you need to split the css to different customer css files which all uses different module configuration files.
Related
I don't really understand about #import and partial file
But I want to ask for a little understanding.
I have 2 files. file2.scss must use the variable from file1.scss.
File1.scss
/* file1.scss */
$color-1 : #7E3D97;
#font-face{
font-family : AngsanaNew;
src: url(/font/AngsanaNew.TTF);
}
div.test1{
background-color: #222;
}
File2.scss
/* file2.scss */
#import 'file1'
div.content{
background-color : $color-1;
}
When I compile SCSS, It generates 2 files. Which is not what I want. I want it to be
/* file1.css */
#font-face{
font-family : AngsanaNew;
src: url(/font/AngsanaNew.TTF);
}
div.test1{
background-color: #222;
}
AND
/* file2.css */
div.content{
background-color : #7E3D97;
}
Because if I want to create a file file3.css, file4.css. while there is already a file1.css style rule. I will see #font-face and div.test1 contain in file3.css, file4.css.
I don't want that. What should I do?
P.S. Sorry my english. If you edit my text to make it easy to read. I will be very grateful.
If SASS is generating the CSS it does it for every 'normal named' file.scss. To mark a file for SASS that it should not be used to generate a separate css file use an underscore _ as prefix, - i.e. _partial.scss. That files will only be used to be imported to the main scss file.
Now you can organize your project:
// POSSIBLE PROJECT STRUCURE
// variables to whole project
// --> variables only, no classes
_defaults.scss
// partial files
// --> use vairables from _default.scss
// --> or if you want/need to define variables here
// --> use variables with default-flag: '$variable: value !default'
// --> so they will/can be overwritten by same variable set BEFORE in _default.scss
_partial-structure.scss
_partial-element.scss
...
// bring them together in main file
styles.scss
#import 'defaults';
#import 'partial-structure';
#import 'partial-element';
...
I have an admin template in which I would like user to provide option to switch between different theme color such as dark/light theme along with setting variable values, but I'm unable to conditionally import style files.
#if $light-theme {
#import "./_metronic/_assets/sass/themes/layout/aside/light.scss";
$primary: green;
} #else {
#import "./_metronic/_assets/sass/themes/layout/aside/dark.scss";
$primary: blue;
}
Getting error: Import directives may not be used within control directives or mixins.
Also it is a very long wished item to sass ... as the error message returns: that is not possible. (https://github.com/sass/sass/issues/279)
But what you can do is setting a theme variable and import your theme files. Every theme file has an conditional output based on the theme variable:
$theme: 'light-theme';
#import 'path/dark-theme';
#import 'path/light-theme';
// EVEREY THEME FILE WITH STRUCTURE:
#if( $theme == 'this-theme' ){
/*
here goes your theme variables or
if whanted your theme code in the file
*/
}
I've set up some scss files/folders that load in the following way (Angular project)
1) Settings - which have colour settings for the project, as well as some functions
2) Files and folders that define variables fonts, widths, etc. for the components and pages'
3) Files and folders that use the variables for classes
The problem I'm having is; the functions I've created in the settings file are not working and giving an invalid property value
_settings: loaded first
$brand-colours: (
text: #2F2F2F,
text-white: #FFFFFF,
primary-bg-colour: #F3F3F3,
heading: #5C1544,
border: #969696,
borderSecondary: #DCDCDC,
primary: #0068AA,
secondary: #D11E4F,
focusState: #F6C037,
focusBackgroundState: #FFF7E6,
hoverState: #00B2CF,
hoverBackgroundState: #00B2CF,
activeState: #00B2CF,
errorState: #D91F26,
validState: #C5DA46,
panelLightBlue: rgb(0, 0, 0)
);
#function brand-colour($colour, $colours: $brand-colours) {
#return map-get($brand-colours, $colour);
}
_body: loaded second
$body-bg-colour: brand-color('primary-bg-colour') !default;
$body-bg-colour-1: #FFFFFF !default;
$body-colour: brand-colour('text');
$body-font-family: 'ProxinaNova';
_body: loaded last
body {
background-color: $body-bg-colour-1;
color: $body-colour;
font-family: $body-font-family;
#include hg-mq('med') {
background-color: $body-bg-colour;
}
}
in style.scss
#import 'settings/settings';
#import 'base/body';
#import 'shared/body';
The reason I'm doing this way is that I want to have multiple apps using the same code (last loaded) and just update 1st and second loaded
You have a tiny mistake. The brand-colour function's 2nd argument is $colours, but you're not using the argument, you're using the default value. Here's a DEMO...
The brand-colour function is just map-get whose 2nd argument has a default value. It doesn't add any value + adds unnecessary complexity to your code. I'd recommend just using map-get...
I'm trying to write an atom theme. However, it will not be static. I want to use imported css from pywal to change how atom looks depending on the color scheme of the rest of the desktop at that given moment. I am able to import the colors from the pywal cache, and use them as css colors. I can even assign them to less variables. However, when I try to use a function such as darken, I get the error error evaluating function 'darken': color.toHSL is not a function
I had concluded it was due to the import settings, so I tried using the (less) keyword in my import for the css file. When I did so, it said it could not find the file, so (in order to test if I was at least right about the import settings), I copied the pywal css file into the styles directory and changed the filename in import accordingly. This led me back to square one, as I started receiving the same error. An example of my attempts are below.
Relevant code:
~/.cache/wal/colors.css (aka "tempcolors.css"):
/* CSS variables
Generated by 'wal' */
:root {
--wallpaper: url("/home/regular/wallpapers/current/image.jpg");
/* Special */
--background: #0a0b10;
--foreground: #d5c5a8;
--cursor: #d5c5a8;
/* Colors */
--color0: #0a0b10;
--color1: #483E45;
/* ... */
--color15: #d5c5a8;
}
colors.less:
// These colors are specific to the theme. Do not use in a package!
//#import (less) url('file:///home/[MYUSERNAME]/.cache/wal/colors.css'); // this stopped working when I added (less)
#import (less) "tmpcolors.css";
#very-light-gray: #c5c8c6;
#light-gray: #969896;
/* the rest of the standard colors */
#background: var(--background);
#foreground: var(--foreground);
#color-1: var(--color1);
#color-2: var(--color2);
/* ... */
#color-15: var(--color15);
Here's the section in base.less where I tried to use my custom colors:
atom-text-editor {
/* ... */
.gutter {
background-color: #color-7; //works
background-color: darken(#color-7, 25%); //doesn't work
background-color: darken(#red, 25%); //works
Can someone help me? Is what I'm trying to do possible?
P.S. Please let me know if you need more info
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.