Angular Material Theming with CSS Variables
Tags: angular, angularmaterial, webdev, materialdesign
Published: 2024-06-05 09:34:23

---
title: Angular Material Theming with CSS Variables
published: true
description: In this quick guide, we will learn how to modify theme for Angular Material 18 with CSS variables
tags: angular,angularmaterial,webdevelopment,materialdesign
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dbe3cl5lagw4qcwnt7ng.png
canonical_url: https://angular-material.dev/articles/angular-material-theming-css-vars
---

> In this quick guide, we will learn how to modify theme for Angular Material 18 with CSS variables.

## Creating Project with Angular Material 18

```bash
npm i -g @angular/cli
ng new angular-material-theming-css-vars --style scss --skip-tests --defaults
cd angular-material-theming-css-vars
ng add @angular/material
```

And select answers as below:

```bash
? Choose a prebuilt theme name, or "custom" for a custom theme: Custom
? Set up global Angular Material typography styles? Yes
? Include the Angular animations module? Include and enable animations
```

## The define-theme mixin

Take a look at `src/styles.scss`. Notice the usage of `define-theme` mixin:

```scss
// Define the theme object.
$angular-material-theming-css-vars-theme: mat.define-theme(
  (
    color: (
      theme-type: light,
      primary: mat.$azure-palette,
      tertiary: mat.$blue-palette,
    ),
    density: (
      scale: 0,
    ),
  )
);
```

We are going to make changes in above code later on to achieve customizations through CSS custom properties.

## CSS custom properties emitted by theme mixins

To further customize your UI beyond the `define-theme` API, you can manually set these custom properties in your styles.

For example, take a look at below code snippets:

```html
<mat-sidenav-container>
  Some content...
  <mat-sidenav>
    Some sidenav content...
    <mat-checkbox class="danger">Enable admin mode</mat-checkbox>
  </mat-sidenav>
</mat-sidenav-container>
```

```scss
@use '@angular/material' as mat;

$light-theme: mat.define-theme();
$dark-theme: mat.define-theme((
  color: (
    theme-type: dark
  )
));

html {
  // Apply the base theme at the root, so it will be inherited by the whole app.
  @include mat.all-component-themes($light-theme);
}

mat-sidenav {
  // Override the colors to create a dark sidenav.
  @include mat.all-component-colors($dark-theme);
}

.danger {
  // Override the checkbox hover state to indicate that this is a dangerous setting. No need to
  // target the internal selectors for the elements that use these variables.
  --mdc-checkbox-unselected-hover-state-layer-color: red;
  --mdc-checkbox-unselected-hover-icon-color: red;
}
```

Notice that we are change colors of checkbox through `--mdc-checkbox-unselected-hover-state-layer-color` and `--mdc-checkbox-unselected-hover-icon-color` CSS properties in `.danger` class.

These CSS custom properties emitted by the theme mixins are derived from [M3's design tokens](https://m3.material.io/foundations/design-tokens/overview).

This approach requires you to inspect each and every component, find out the needed CSS custom properties and then change them.

But, there is a better and scalable way to achieve theme customizations.

## Using `sys` variables

There are total 3 properties (a.k.a. dimensions) allowed in `define-theme` mixin.

1. `color` - [Optional] A map of color options
2. `typography` - [Optional] A map of typography options.
3. `density` - [Optional] A map of density options.

With `color` and `typography` maps, apart from main properties, Angular Material team has introduced a new property called `use-system-variables` of type boolean.

Let's use the in our theme mixin:

```scss
$angular-material-theming-css-vars-theme: mat.define-theme(
  (
    color: (
      theme-type: light,
      primary: mat.$azure-palette,
      tertiary: mat.$blue-palette,
      use-system-variables: true, // 👈 Added
    ),
    typography: (
      use-system-variables: true, // 👈 Added
    ),
    density: (
      scale: 0,
    ),
  )
);
```

After above, we will also need to include 2 more mixins:

```scss
:root {
  @include mat.all-component-themes($angular-material-theming-css-vars-theme);
  @include mat.system-level-colors($angular-material-theming-css-vars-theme); // 👈 Added
  @include mat.system-level-typography($angular-material-theming-css-vars-theme); // 👈 Added
}
```

If you inspect the output in browser, you will notice that majority of the Angular Material CSS Custom Properties (`--mat-*` and `--mdc-*`) now read values from `--sys-*` CSS variables. Take a look at below screenshot for example:

![browser inspector showing usage of sys variables](https://res.cloudinary.com/dbgsyjnmu/image/upload/v1717490677/angular-material.dev/Angular%20Material%20Theming%20with%20CSS%20Variables/sys_variables_odwnvu.png)

This means that we can simply change a particular set of `--sys-*` CSS variables to achieve the theme we want. But, what are all the possible sys variables?

### All possible sys variables

The `--sys-*` variables are generated for 2 dimensions: color and typography. So, all the sys variables should be supporting all possible values of color and typography. And to get all the possible values, we can simply take a look at [Reading color roles](https://material.angular.io/guide/theming-your-components#reading-color-roles) and [Reading typescale properties](https://material.angular.io/guide/theming-your-components#reading-typescale-properties).

{% embed https://dev.to/shhdharmen/unpublished-video-4cbn-2e25-temp-slug-3127604?preview=82ac59eb30e1d6fa684bbf0523a92740d1ebca1085539da0db1f85ac2532afc6908ff20bfdd23460547aa9a50f368e97308ca022eeff1761fcac0bcb %}

### Finding and modifying right sys variable

So, if you want to modify color role `primary`, you would modify `--sys-primary` variables. Similarly, for `surface`, `secondary`, `on-primary`, you would modify `--sys-surface`, `--sys-secondary` and `--sys-on-primary`.

And for typography, to change `body-large` level's `font`, we would modify `--sys-body-large-font` variable.

### Changing `mat-flat-button`'s color and background color

Let's take an example of `mat-flat-button`. Let's use it in `app.component`:

```html
<button mat-flat-button>Flat Button</button>

```

```ts
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button'; // 👈 Added

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [MatButtonModule], // 👈 Added
  templateUrl: './app.component.html',
  styleUrl: './app.component.scss',
})
export class AppComponent {
}

```

Now, you can simply go to browser, open the inspector, and simply change `--sys-primary` and `--sys-on-primary` variables to see the changes:

{% embed https://dev.to/shhdharmen/unpublished-video-53c8-45bl-temp-slug-5857619?preview=97b9072cef0c755aef0bd6f6fe5780313a6b80896c98131d4fd305a3981d483669625e12ab9a87c6257cfd267653e281bdd244c5aa587f007a3c20e1 %}

### Using `@material/material-color-utilities` library

Another way to change sys variables is using the [`@material/material-color-utilities`](https://www.npmjs.com/package/@material/material-color-utilities).

Let's install it:

```bash
npm i @material/material-color-utilities
```

Next, we will use it's `argbFromHex`,`themeFromSourceColor` and `applyTheme` functions to generate all sys variables.

```ts
generateDynamicTheme(ev: Event) {
    const fallbackColor = '#005cbb';
    const sourceColor = (ev.target as HTMLInputElement).value;

    let argb;
    try {
      argb = argbFromHex(sourceColor);
    } catch (error) {
      // falling to default color if it's invalid color
      argb = argbFromHex(fallbackColor);
    }

    const targetElement = document.documentElement;

    // Get the theme from a hex color
    const theme = themeFromSourceColor(argb);

    // Print out the theme as JSON
    console.log(JSON.stringify(theme, null, 2));

    // Identify if user prefers dark theme
    const systemDark = window.matchMedia(
      '(prefers-color-scheme: dark)'
    ).matches;

    // Apply theme to root element
    applyTheme(theme, {
      target: targetElement,
      dark: systemDark,
      brightnessSuffix: true,
    });

    const styles = targetElement.style;

    for (const key in styles) {
      if (Object.prototype.hasOwnProperty.call(styles, key)) {
        const propName = styles[key];
        if (propName.indexOf('--md-sys') === 0) {
          const sysPropName = '--sys' + propName.replace('--md-sys-color', '');
          targetElement.style.setProperty(
            sysPropName,
            targetElement.style.getPropertyValue(propName)
          );
        }
      }
    }
  }
```

Lastly, we will add the `input` to allow user to change the colors:

```html
<mat-form-field>
  <mat-label>Change Seed Color</mat-label>
  <input
    type="text"
    matInput
    placeholder="#XXXXXX"
    (change)="generateDynamicTheme($event)"
  />
</mat-form-field>

```

Now, if you look at the output, observe that all the `--sys-*` colors are generated dynamically according to Material 3 design specs.

{% embed https://dev.to/shhdharmen/unpublished-video-35l7-3h5k-temp-slug-5002742?preview=4e40808dafc42d86b2e1d606f3fa587edd992ddc0378a2b55e2f21cdc0d3c3622b664a2a4f86aaa7655d3032a7e31548a32cd722277b74dc10ea20bb %}

> **Heads up!**
> `@material/material-color-utilities` library generates colors based on Material 3 design guidelines, and hence, it maybe possible that the seed color user enters, may not be available in generated `--sys-*` variables.

## Conclusion

We learned that `define-theme` mixin emits custom CSS properties like `--mdc-checkbox-unselected-hover-state-layer-color` and `--mdc-checkbox-unselected-hover-icon-color`. And we can change them to modify the theme. But, a drawback would be you will have to find out such properties for each and every components.

Next, we saw that it is also possible to modify a set of `--sys-*` variables to achieve the desired customizations in theme. With `--sys-*`, we have access to color roles and typescale properties for all typography levels.

Lastly, we learned the usage of `@material/material-color-utilities` library and how it is very much helpful in creating the dynamic themes.

## Live Playground

{% embed https://stackblitz.com/github/Angular-Material-Dev/angular-material-theming-css-vars/tree/stackblitz %}