Avoid duplicate styles in Angular Material theme - css

I have a styles.theme.scss that looks like the following.
#media (prefers-color-scheme: dark) {
#include example-theme($dark-theme);
}
#media (prefers-color-scheme: light) {
#include example-theme($light-theme);
}
[data-theme="dark-theme"] {
#include example-theme($dark-theme);
}
[data-theme="light-theme"] {
#include example-theme($light-theme);
}
The goal is to use the prefers-color-scheme if that is configured by the user agent, but override it if the user has configured it on the website.
The current SCSS causes the following warning, however:
WARNING: The same color styles are generated multiple times. Read more about how style duplication can be avoided in a dedicated guide. https://github.com/angular/components/blob/master/guides/duplicate-theming-styles.md
node_modules/#angular/material/_theming.scss 1648:7 -mat-check-duplicate-theme-styles()
node_modules/#angular/material/_theming.scss 7010:3 angular-material-theme()
stdin 29:3 example-theme()
stdin 57:3 root stylesheet
I've checked the provided documentation, but it doesn't appear to cover this case, and I'm unsure on how to better structure this to avoid duplicating the styles.
The only solution I think would work is to detect the preference with JavaScript, and then set the data-theme attribute if a theme isn't configured in the application.
Is there a better solution than this?
What I've tried:
To see if I could string a media query and selector together like [data-theme="dark-theme"], #media (prefers-color-scheme: dark), which I don't believe is possible.
If I can use SASS #if and #else to check if the data-theme selectors match any elements, else include the #media queries.

You should include mat.all-component-colors instead of mat.all-component-themes
Example: below is wrong
// Generates styles for all systems configured in the theme. In this case, color styles
// and default density styles are generated. Density is in themes by default.
#include mat.all-component-themes($light-theme);
.dark-theme {
// Generates styles for all systems configured in the theme. In this case, color styles
// and the default density styles are generated. **Note** that this is a problem because it
// means that density styles are generated *again*, even though only the color should change.
#include mat.all-component-themes($dark-theme);
}
Use this instead:
#use '#angular/material' as mat;
...
#include mat.all-component-themes($light-theme);
.dark-theme {
// This mixin only generates the color styles now.
#include mat.all-component-colors($dark-theme);
}
For more information, Official Docs

I also find my self fighting same issue today.
The problem with your styling is you have two theme light and dark and in
#include angular-material-theme('Your-Theme');
you have to provide a theme ( light or dark).
so you should overwrite only one of theme because other is already included
so overwrite media query dark if you provided light one or vice versa.
here is my code sample
#import "~#angular/material/theming";
#include mat-core();
$kyc-web-primary: mat-palette($mat-pink);
$kyc-web-accent: mat-palette($mat-pink, A200, A100, A400);
$kyc-web-warn: mat-palette($mat-red);
$kyc-web-theme-light: mat-light-theme(
(
color: (
primary: $kyc-web-primary,
accent: $kyc-web-accent,
warn: $kyc-web-warn,
),
)
);
$kyc-web-theme-dark: mat-dark-theme(
(
color: (
primary: $kyc-web-primary,
accent: $kyc-web-accent,
warn: $kyc-web-warn,
),
)
);
#include angular-material-theme($kyc-web-theme-dark);
#media (prefers-color-scheme: light) {
#include angular-material-color($kyc-web-theme-light);
}

mat.$theme-ignore-duplication-warnings: true;

Related

Angular Material Theme Styles not applying in Migrated Angular Project

I am trying to migrate an older Angular v9 project to the latest version (as of writing) v13. I have successfully migrated everything except the styles, particularly the Angular Material theme styles, which do not appear to apply at all.
Here is a comparison of the same Login page:
Angular v9
Angular v13
Apologies for not being able to post the whole page, but these are the relevant parts. Ignoring the increased height and input fields, which I understand need to be done differently in HTML, the buttons look completely wrong. Inspecting the items reveals that the basic styling for the shape and form are being applied from Angular Material, but seemingly the custom theme styles do not work at all. When investigating further, I can see that it appears that the custom theme styles, defined in a SCSS file, are being generated, but the CSS class names are being mangled:
material.module.scss:
#use "#angular/material" as mat;
#import "environment.scss";
#import "./material.variables";
#import "../Scss/variables";
// Include the common styles for Angular Material. We include this here so that you only have to load a single css file for Angular Material in your app.
// Be sure that you only ever include this mixin once!
$typography: mat.define-typography-config($font-family: $primary-font-face);
#include mat.core($typography);
// Theme Configuration
$primary-colour: $primary;
$primary-lighter-colour: #bde8f9;
$primary-darker-colour: #1498e1;
$accent-colour: $green;
$accent-lighter-colour: $green-pastel;
$accent-darker-colour: $green-dark;
$warn-colour: $red;
$warn-lighter-colour: $red-pastel;
$warn-darker-colour: $red-dark;
#mixin mat-body-variables($name, $main-colour, $lighter-colour, $darker-colour, $text-colour) {
--#{$name}-color: #{$main-colour};
--#{$name}-lighter-color: #{$lighter-colour};
--#{$name}-darker-color: #{$darker-colour};
--text-#{$name}-color: #{$text-colour};
--text-#{$name}-lighter-color: #{$text-colour};
--text-#{$name}-darker-color: #{$text-colour};
}
body {
#include mat-body-variables(primary, $primary-colour, $primary-lighter-colour, $primary-darker-colour, $light-text);
#include mat-body-variables(accent, $accent-colour, $accent-lighter-colour, $accent-darker-colour, $light-text);
#include mat-body-variables(warn, $warn-colour, $warn-lighter-colour, $warn-darker-colour, $light-text);
}
#function get-material-palette($main-colour, $light-colour, $dark-colour, $text-colour) {
$mat: (
50: $light-colour,
500: $main-colour,
900: $dark-colour,
text: $text-colour,
contrast: (
50: $text-colour,
500: $text-colour,
900: $text-colour,
)
);
#return mat.define-palette($mat, 500, 50, 900, text);
}
$theme-primary: get-material-palette($primary-colour, $primary-lighter-colour, $primary-darker-colour, $light-text);
$theme-accent: get-material-palette($accent-colour, $accent-lighter-colour, $accent-darker-colour, $light-text);
$theme-warn: get-material-palette($warn-colour, $warn-lighter-colour, $warn-darker-colour, $light-text);
$theme: mat.define-dark-theme((
color: (
primary: $theme-primary,
accent: $theme-accent,
warn: $theme-warn
)
));
#include mat.all-component-themes($theme);
$lightTheme: mat.define-light-theme((
color: (
primary: $theme-primary,
accent: $theme-accent,
warn: $theme-warn
)
));
.light-theme {
#include mat.all-component-colors($lightTheme);
}
.mat-raised-button,
.mat-form-field {
width: 100%;
}
mat-tab-group,
.mat-tab-body-wrapper {
height: 100%;
}
button,
.mat-flat-button,
.mat-raised-button {
transition: background-color 0.2s, color 0.2s;
}
Seeming output in Chrome Inspector of generated styles.css (partial for brevity):
And scrolling further up I can see the custom colours interspersed in the mangled CSS classes, so I assume these are the theme styles.
My question is then, how do I correctly import & apply these styles to the project?
The theme stylesheet is currently included in the angular.json file, as recommended by the Theming Angular Material Guide (emphasis mine):
To include the emitted styles in your application, add your theme file to the styles array of your project's angular.json file.
although I have tried including it in our main stylesheet for this project, to no avail.
I have also tried changing the optimisation settings in angular.json, as well as messing around with supposed environment variables that control the mangling of the CSS names, but neither of those worked, nor particularly appeal as solutions.
Fwiw, what does seem to work is when I include one of the prebuilt theme stylesheets into the styles array, then they apply correctly. I have also tried creating separate projects outside of this one to see if it was my environment messing with things, however with both the default Angular Material themes, and our custom theme, the theming worked correctly.
So please, can anyone help with this frustrating issue?
Thank you, Will

Modify a Bootstrap variable within a mixin

Using Bootstrap 4, I'm trying the change the value of brand-primary in a mixin:
// variables
$theme-colors: (
"main": #9fa28b,
"another-section": #ec008c,
"yet-another-section": #00b0d8
);
// main scss
#mixin theme($color){
$theme-colors: (
"brand-primary": $color
);
}
body {
#include theme(theme-color($key: "main"));
&.another-section {
#include theme(theme-color($key: "another-section"));
}
&.yet-another-section {
#include theme(theme-color($key: "yet-another-section"));
}
}
This compiles without an error but nothing with the classes another-section or yet-another-section are in the compiled css.
Is there a way of overriding brand-primary within a mixin (or any variable)?
If i understand you correct you want to change the color. All you have to do is override primary color check this link How to change the bootstrap primary color?
Take a look here:
https://sass-lang.com/documentation/variables#advanced-variable-functions
use map.get($theme-colors, "warning"); instead!

Dynamically override Bootstrap CSS variables in React?

So what I have is a React project with Bootstrap.css loaded. I'd like to somehow override the variables, so for instance I have a bunch of buttons like
<button className="btn btn-primary">Hello</button>
Which basically 'inherit' the color from:
:root {
--primary: somecolor;
}
Is there a way to somehow override this? I've tried passing it in as inline style to components, like <Component style={{"--primary" : "red"}} /> which will override the :root { --primary }, but the button colors will remain the same. What's the easiest way to do this, considering I'm supporting dynamic colors, so I can't create a few CSS files, and it would be good if I didn't have to rewrite every single button I have to be a styled-component that minds props!
There's not really an easy way to do this. You could generate the CSS for the "custom" primary colors in SASS, and then add a "root" primary color class to the component like this...
SASS to generate "primary" color Bootstrap CSS
/* import the necessary Bootstrap files */
#import "bootstrap";
#mixin build-primary-classes($color) {
/* background classes */
#include bg-variant(".bg-primary", $color);
/* btn classes */
.btn-primary {
#include button-variant($color, $color);
}
.btn-outline-primary {
#include button-outline-variant($color);
}
/* text- classes */
#include text-emphasis-variant(".text-primary", $color);
/* badge classes */
.badge-primary {
#include badge-variant($color);
}
/* borders */
.border-primary {
border-color: $color !important;
}
}
$customprimarycolors: (
"purple": purple,
"red": red,
"orange": orange
);
#each $colorName, $color in $customprimarycolors {
.#{$colorName} {
#include build-primary-classes($color);
}
}
JS
class Hello extends React.Component {
constructor(props) {
super(props);
}
render() {
return(
<div className={this.props.className}>Hello <button className="btn btn-primary">Button</button></div>
);
}
}
ReactDOM.render(<Hello className="red" />, document.getElementById('root'));
Codeply demo: https://codeply.com/go/R4X5x8taiH
Not sure if this will work, but an idea...
Could you edit your bootstrap.css file like this
:root { --primary: unset; }
That way you wouldn't have to overwrite any bootstrap styles?
HONESTLY ... I am not sure if it is a good idea to try to overwrite in CSS a few part of the classes bootstrap builds on a color var like $primary but let's the rest of the classes build on the same var as it is.
That's the way trouble raises up ...
An IMHO better way would be to do it the way Bootstrap provides it:
Changing in SASS the basic var $primary to the new wanted color in Bootstrap the color will change and the classes are there. Or adding a new color $additional-color and adding the color to map $theme-color and all the additional classes are build up on the fly ... Just have a look to the docs ... it is much more easier than it seems to be on the first look:
https://getbootstrap.com/docs/5.0/customize/color/
So yes: in SASS theming is possible just setting/adding the color vars.
But if in a project it is not possible to use SASS or the direct setup is not wanted for some reasons ... there are many free easy to use theming tools in the web which do the job for you. Than you are able to import a clean and consistend Bootstrap CSS with your wanted colors. Because that it is as easy Bootstrap is as successful (what not means I/you need to like it). As Bootstrap 5 still is Beta here an example for BS4 ...
http://pikock.github.io/bootstrap-magic/app/index.html#!/editor
NOTE: only changing/overwriting the CSS color vars in CSS file is not enough as the colors in the classes are hard coded to the original hex colors. You indeed would have to overwrite the original classes which leads to doubled code structures.

What is best fit to use for theming variables or mixins in sass

I am creating a UI library in which I want to provide the mechanism to theme all the UI components like button, cards, slider and all. I am confused between variables and mixins.
One way is to provide the no. of variables that user can update and based on that variables component classes will be derived. The same concept is used in the materialzecss library. And user will use like
//variables that are used to create component css classes
$primary : "blue";
$btn-primary :"green";
//then include the ui library
#import "_ui-variables";
#import "ui-library";
_ui-variables.scss
$primary : "red" !default;
$btn-primary: $primary !default;
// and other variables
and the _btn.scss will be like
.btn {
// other rule sets
color:$btn-primary;
}
Other way could be to use mixins. There will be a theme file for every component that will contain the theme mixin for that component and at the library level, there will be theme mixin that will include all the mixin of the individual component. As the angular-material has done
_btn.scss
#import "_btn-theme.scss";
.btn {
// some rules
}
_btn-theme.scss
#mixin btn-theme($theme) {
// if user has added the btn-primary then use btn-primary otherwise primary
#if map-has-key($theme,btn-primary) {
$btn-primary : map-get($theme,primary);
} #else {
$btn-primary : map-get($theme,primary);
}
.btn {
color:$btn-primary;
}
}
and the ui-library.scss
#import "_btn.scss";
#import "_card.scss";
#mixin ui-theme($theme) {
#include btn-theme($theme);
#include card-theme($theme); // include all component theme
}
and the consumer will call this as
consumer-theme.scss
#import "ui-library";
$theme :(primary:"blue",accent:"yellow");
#include ui-theme($theme);
What are the pros and cons of these approaches? Is there any other way to do this?
If you can use CSS custom properties (CSS variables) that would be really easy. You would just need to add a class to the body change your all your variables at once. So you just need a default theme and then just some classes changing your theme.
I have a small example in one of my project, if you click on "invert theme" it will change the page theme to invert: https://vinceumo.github.io/atomic-bulldog-style-guide-demo/styleguide/section-organisms.html#kssref-organisms-accessibility-settings
The issue with CSS custom properties is that not every brother support it yet :/
https://caniuse.com/#feat=css-variables
Otherwise, I would highly recommend using sass maps. It is easier to maintain when you have few themes, and you can quickly generate your components using #each loop
For example, if you want to generate background color classes:
$color-themes: (
primary:
(
base: #4c5c8c,
dark: darken(#4c5c8c, 15%),
light: lighten(#4c5c8c, 15%),
transparent: transparentize(#4c5c8c, 0.5),
contrast: #ffffff
),
secondary:
(
base: #212529,
dark: darken(#212529, 15%),
light: lighten(#212529, 15%),
transparent: transparentize(#212529, 0.5),
contrast: #ffffff
)
}
#each $name, $theme in $color-themes {
.has-bg-#{$name} {
background-color: map-get($name, base);
color: map-get($name, contrast);
}
}
So here we will get two new classes .has-bg-primary, .has-bg-secondary
If you add new entries to your map it will automatically generate new classes :)
I have created a Scss boilerplate using CSS custom properties (This one can be disabled) with Sass variables. It is optimized for themes creation. Most components are linked to variables (using map). Check it out https://github.com/vinceumo/atomic-bulldog
Variables are going to be your initial best bet.
Creating a theme story isn't something you should rush through, but rather take the time to integrate solid, well thought variables for a base set of colors. After that, you can extend them with things like lighten(), darken(), and other tools built into SASS.
Then, use that base set of variables to establish component specific variables to scale the theme story as needed.

SASS Multiple levels of variable precedence/inheritance

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.

Resources