Create angular material theme with CSS variables
Asked Answered
G

5

14

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?

Gremlin answered 8/2, 2019 at 15:35 Comment(9)
I just tested my project using the hsl() scss function and it works correctly. Do you have the "stylePreprocessorOptions": { "includePaths": [ "src","src/assets/scss" ] } property in your angular.json so your var.scss can be picked up globally?Megohm
are you sure you are using mat-palette correctly ? i can't find an example where the pass an array to itButterfly
@Megohm My var.scss is included perfectly fine. I use it all over the place.Gremlin
@Megohm did you pass in a CSS variable to the hsl function?Gremlin
Can you show me what the contents of the $var you are passing into the hsl() function? @Butterfly He is using the mat-palette function correctly, that's exactly how my custom theme is setup.Megohm
@Megohm Yes, that's the first code snipped on this page Its in the` :root`Gremlin
@Megohm Can you please show me your snipped that did work?Gremlin
It didn't work using your inputs, further digging revealed this as an issue though. github.com/sass/node-sass/issues/2237 Although it does say it has been resolved...Megohm
@Megohm This issue is indeed resolved as this is not the error being thrown for me. The error is from Material design.Gremlin
B
14

I created a little library to make this a little easier.

You can use it like so:

  1. Install:

    npm i angular-material-css-vars -S
    
  2. Then remove any existing @import '~@angular/material/theming'; from your main stylesheet file.

  3. Add this to your main stylesheet instead:

    @import '~angular-material-css-vars/main';
    @include initMaterialCssVars();
    
  4. 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);
      }
    }
    
Blau answered 15/8, 2019 at 0:2 Comment(1)
Hi there, can we apply font-styles with the library you mentioned ?Theatrical
E
10

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 :/

Erfert answered 22/3, 2019 at 19:34 Comment(2)
Wow! Sounds good, I did not have time to check it out, but this seems legit!Gremlin
works pretty well together with @function colorToVariable($hexColor) { @return "#{red($hexColor)},#{green($hexColor)},#{blue($hexColor)}"; } and later --color-primary-lighter: #{colorToVariable(#ff0000)}; to avoid the manual rgb conversionAllard
G
3

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

Garrulous answered 16/3, 2021 at 10:8 Comment(0)
R
0

This is fairly easy to achieve out of the box.

Define a palette and variables for Primary, Accent, and Warn, based on the angular palette in your themes.scss file. See this for reference.

@import '@angular/material/theming';
@include mat.core();

$dark-primary-text: rgba(black, 0.87);
$dark-secondary-text: rgba(black, 0.54);
$dark-disabled-text: rgba(black, 0.38);
$dark-dividers: rgba(black, 0.12);
$dark-focused: rgba(black, 0.12);
$light-primary-text: white;
$light-secondary-text: rgba(white, 0.7);
$light-disabled-text: rgba(white, 0.5);
$light-dividers: rgba(white, 0.12);
$light-focused: rgba(white, 0.12);

$primary-var-palette: (
  50: var(--primary-50, #fafafa),
  100: var(--primary-100, #f5f5f5),
  200: var(--primary-200, #eeeeee),
  300: var(--primary-300, #e0e0e0),
  400: var(--primary-400, #bdbdbd),
  500: var(--primary-500, #9e9e9e),
  600: var(--primary-600, #757575),
  700: var(--primary-700, #616161),
  800: var(--primary-800, #424242),
  900: var(--primary-900, #212121),
  A100: var(--primary-A100, #ffffff),
  A200: var(--primary-A200, #eeeeee),
  A400: var(--primary-A400, #bdbdbd),
  A700: var(--primary-A700, #616161),
  contrast: (
    50: $dark-primary-text,
    100: $dark-primary-text,
    200: $dark-primary-text,
    300: $dark-primary-text,
    400: $dark-primary-text,
    500: $light-primary-text,
    600: $light-primary-text,
    700: $light-primary-text,
    800: $light-primary-text,
    900: $light-primary-text,
    A100: $dark-primary-text,
    A200: $light-primary-text,
    A400: $light-primary-text,
    A700: $light-primary-text,
  )
);

Then add the palettes to the theme.

$var-primary: mat.define-palette($primary-var-palette);
$var-accent:  mat.define-palette($accent-var-palette, 400, 900, A100);
$var-warn:    mat.define-palette($warn-var-palette);
$var-theme: mat.define-light-theme($var-primary, $var-accent, $var-warn);
@include mat.all-component-themes($var-theme);

You can not set or change the css variable in the html so change the theme dynamically.

document.documentElement.style.setProperty('--primary-50', myPrimary50Value);
...
Rejuvenate answered 14/6, 2023 at 16:58 Comment(0)
M
-2

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%);
Megohm answered 8/2, 2019 at 16:44 Comment(1)
I don't need SCSS to know what to do with it, SCSS will just place it at the assigned value and the browser will interpret it. Second of all, the reason why I want this is that I should be able to change the theme at run time.Gremlin

© 2022 - 2024 — McMap. All rights reserved.