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?

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? Commented Feb 8, 2019 at 15:52
  • are you sure you are using mat-palette correctly ? i can't find an example where the pass an array to it Commented Feb 8, 2019 at 16:02
  • @Budhead2004 My var.scss is included perfectly fine. I use it all over the place. Commented Feb 8, 2019 at 16:11
  • @Budhead2004 did you pass in a CSS variable to the hsl function? Commented Feb 8, 2019 at 16:12
  • Can you show me what the contents of the $var you are passing into the hsl() function? @Dirk He is using the mat-palette function correctly, that's exactly how my custom theme is setup. Commented Feb 8, 2019 at 16:18

5 Answers 5

15

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);
      }
    }
    
Sign up to request clarification or add additional context in comments.

1 Comment

Hi there, can we apply font-styles with the library you mentioned ?
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 :/

2 Comments

Wow! Sounds good, I did not have time to check it out, but this seems legit!
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 conversion
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

Comments

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);
...

Comments

-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%);

1 Comment

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.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.