Extending Angular Material Theme System: Additional Palettes
Tags: angular, angularmaterial, theming, scss
Published: 2019-12-17 09:57:27

---
title: Extending Angular Material Theme System: Additional Palettes
published: true
description: Angular Material provides a powerful theme system, this is how you can extend it to add additional palettes. 
tags: angular, angularmaterial, theming, scss
cover_image: https://thepracticaldev.s3.amazonaws.com/i/vi6vzorreetdpphe9rfg.jpg
series: Extending Angular Material Theme System
---


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
 1. 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](https://github.com/angular/components/blob/2adf6294b52088e0f4b6284672cc2ca5e9163c20/src/material/core/theming/_theming.scss#L68) 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. 

```scss
@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,
  );
}
```

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

```scss
@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));
}
```

We can now repeat this for the dark theme. 

```scss
@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));
}
```

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.


```scss
@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;
  }
}
```

## 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.

```scss
@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);
    }
  }
}
```

Now we cannot simply use:
```html
<button mat-button color="success">SUCCESS</button>
``` 

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:
```html
<button mat-button class="mat-success">SUCCESS</button>
```

### Using a Directive to Extend Angular Material

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

```typescript
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) { }
}
```

```html
<button mat-button myColor="success">SUCCESS</button>
```

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.