Angular Material Theming with CSS Variables

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

4th June, 2024 — 5 min read

Open in StackBlitz
> 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 Some content... Some sidenav content... Enable admin mode ``` ```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://ik.imagekit.io/en2qz3t5n/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). ![embedded video](https://ik.imagekit.io/en2qz3t5n/angular-material.dev/Angular%20Material%20Theming%20with%20CSS%20Variables/sys_vars_wmtyc9.mp4) ### 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 Flat 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: ![embedded video](https://ik.imagekit.io/en2qz3t5n/angular-material.dev/Angular%20Material%20Theming%20with%20CSS%20Variables/change_sys_vars_sz4lfc.mp4) ### 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 Change Seed Color ``` Now, if you look at the output, observe that all the `--sys-*` colors are generated dynamically according to Material 3 design specs. ![embedded video](https://ik.imagekit.io/en2qz3t5n/angular-material.dev/Angular%20Material%20Theming%20with%20CSS%20Variables/material_helper_library_ats3ny.mp4) ## 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
Dharmen Shah
Written by Dharmen Shah

I have around 8+ years of experience in IT industry. I have got opportunity to work at different companies with different technologies, mostly focused on Front-end, like Angular, React, Next, vanilla web stack (HTML, CSS, JavaScript).

You can find me on Twitter, Linkedin and Github.

Discuss online

Share this article

Read more

View all articles

Learn more

View courses page

Support Free Content Creation

Contributions & Support

Even though the courses and articles are available at no cost, your support in my endeavor to deliver top-notch educational content would be highly valued. Your decision to contribute aids me in persistently improving the course, creating additional resources, and maintaining the accessibility of these materials for all. I'm grateful for your consideration to contribute and make a meaningful difference!

Envelop
Don't miss any update

Stay up to date

Subscribe to the newsletter to stay up to date with articles, courses and much more!

Angular Material Dev

Angular Material Dev is one place stop for developers to learn about integrating Material Design in Angular applications like a pro.

Find us on X (Twitter) and LinkedIn