DEV Community

Cover image for Extending Angular Material Theme System: Additional Palettes
Martin McWhorter
Martin McWhorter

Posted on • Updated on

Extending Angular Material Theme System: Additional Palettes

The Angular Material project provides a powerful theming system. This theme system provides a few palettes: Primary, Accent and Warn. It may not be obvious how to extend the theme system to provide additional palettes -- or how to then use those extended palettes.

Here we will go through how to

  1. Add palettes to a theme
  2. Use these additional palettes

Theme System

First we have to familiarize ourselves with how the theme system works before we can extend it.

The theme is constructed as a set of SCSS maps. A map in SCSS is just an array of key-value pairs. Similar to a Map in Java, a Dictionary in C# or a Record in TypeScript.

If we take a look at the source we can see the functions for creating a light or dark theme just return a map containing the three material palates, a foreground and background maps, and an is-dark boolean.

@function mat-light-theme($primary, $accent, $warn: mat-palette($mat-red)) {
  @return (
    primary: $primary,
    accent: $accent,
    warn: $warn,
    is-dark: false,
    foreground: $mat-light-theme-foreground,
    background: $mat-light-theme-background,
  );
}
Enter fullscreen mode Exit fullscreen mode

So we can add our successpalette simply by wrapping the function and adding our new bit in.

@function my-light-theme($primary, $accent, $warn: mat-palette($mat-red), $success: mat-palette($mat-green)) {
  @return map-merge(mat-light-theme($primary, $accent, $warn), (success: $success));
}
Enter fullscreen mode Exit fullscreen mode

We can now repeat this for the dark theme.

@function my-dark-theme($primary, $accent, $warn: mat-palette($mat-red), $success: mat-palette($mat-green)) {
  @return map-merge(mat-dark-theme($primary, $accent, $warn), (success: $success));
}
Enter fullscreen mode Exit fullscreen mode

In our style.scss where we would include a custom theme using mat-light-theme(..) or mat-dark-theme(..), we now use my-light-theme(..) and my-dark-theme(..).

At this point we will be be able to use this theme palette in our own components. These components can now easily be re-themed.

@import '~@angular/material/theming';

@mixin awesome-component-theme($theme) {
  $success: map-get($theme, success);
  $success-color: mat-color($success);
  $success-contrast: mat-color($success, default-contrast);

  .app-awesome {
    background: $success-color;
    color: $success-contrast;
  }
}
Enter fullscreen mode Exit fullscreen mode

What about Material components?

This is well and good, we can use our new theme palette in our own components. But how can we use this in Angular Material components?

Lets add this to the MatButton component.

First we need to override the _mat-button-theme-property mixin to add the mat-success classes.

@mixin _mat-button-theme-property($theme, $property, $hue) {
  $primary: map-get($theme, primary);
  $accent: map-get($theme, accent);
  $warn: map-get($theme, warn);
  $success: map-get($theme, success);
  $background: map-get($theme, background);
  $foreground: map-get($theme, foreground);

  &.mat-primary {
    #{$property}: mat-color($primary, $hue);
  }
  &.mat-accent {
    #{$property}: mat-color($accent, $hue);
  }
  &.mat-warn {
    #{$property}: mat-color($warn, $hue);
  }
  &.mat-success {
    #{$property}: mat-color($success, $hue);
  }

  &.mat-primary, &.mat-accent, &.mat-warn, &.mat-success, &[disabled] {
    &[disabled] {
      $palette: if($property == 'color', $foreground, $background);
      #{$property}: mat-color($palette, disabled-button);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Now we cannot simply use:

<button mat-button color="success">SUCCESS</button>
Enter fullscreen mode Exit fullscreen mode

This will work ok, until it doesn't.

It only works because of regression bug in Angular that fails to type check string literals. This will be fixed in the future. This means that we can try to use the mat-button color="success" to apply our new color to the Angular Material components -- but it only works because of a bug. So let's not do this.

Using a CSS Class

One solution would be to simply add a class to the element:

<button mat-button class="mat-success">SUCCESS</button>
Enter fullscreen mode Exit fullscreen mode

Using a Directive to Extend Angular Material

Or we can create a directive to do this for us.

type ColorClasses = 'mat-primary' | 'mat-accent' | 'mat-warn' | 'mat-success' | undefined;

@Directive({
  selector: '[myColor]'
})
export class MyColorDirective {

  @Input() set myColor(value: ColorClasses) {
    this.renderer.addClass(this.element.nativeElement, `mat-${value}`);
  }

  constructor(private element: ElementRef, private renderer: Renderer2) { }
}
Enter fullscreen mode Exit fullscreen mode
<button mat-button myColor="success">SUCCESS</button>
Enter fullscreen mode Exit fullscreen mode

When the regression bug that is preventing string literals from being type checked in templates is fixed, this approach will improve developer experience.

Conclusion

Angular Material is powerful, though it may seem too prescriptive at times. That is until you look into the internals of the SCSS themes, typography and see there are many extension points.

One of the strengths of Angular is being able to extend using attribute directives. Adding behaviours and palettes using directives is simply the least obtrusive method for extension.

Oldest comments (5)

Collapse
 
vikramkadiam profile image
Vikram Kadiam

This is nice !

Collapse
 
martinmcwhorter profile image
Martin McWhorter

Feel free to repost this everywhere! 😊

Collapse
 
pedy711 profile image
pedy711

nice article, I want to change the color of "mat-icon" by overriding "$mat-light-theme-foreground" variable inside "_theming.scss", but after changing the value for "icon" or "icons" nothing happen. But when I change the "text" value, then all texts and icons colors change which is not intended. I just need to change the icon color. Like below:

$mat-light-theme-foreground {
icon: green,
icons: green,
}

Collapse
 
fireflysemantics profile image
Firefly Semantics Corporation

Awesome article!

Collapse
 
rsadocchi profile image
Riccardo Sadocchi

Hi Martin, i try to apply your code in my app, with angular-core 14.2.0 and material 14.2.7 but doesn't work.
It could be due to the angular/material version?
Thanks!