Play

Dimension: Color

In this chapter, we will learn about the color dimension. We will also learn how to use pre-built themes and create our own custom themes

Pre-built theme

Angular Material includes several pre-built theme CSS files, each with different palettes selected. You can use one of these pre-built themes if you don’t want to define a custom theme with Sass.

How to use pre-built theme

There are 2 ways to use pre-built themes:

  1. Through schematic - when you run ng add @angular/material, select the theme you want.
  2. Through manually importing CSS file

When we installed Angular Material, we selected Custom in theme selection. If you want any pre-built theme, you can select any theme instead of Custom. There are 4 pre-built themes provided:

ThemeLight or dark?Primary SeedTertiary Seed
azure-blue.cssLight#005cbb#343dff
rose-red.cssLight#ba005c#c00100
cyan-orange.cssDark#006a6a#964900
magenta-violet.cssDark#a900a9#7d00fa

For instance, if you want to use azure-blue’s theme, you just need to include that file in the styles array of your project’s angular.json file:

"styles": [
"@angular/material/prebuilt-themes/azure-blue.css",
// other styles
],

Custom theme

A theme file is a SASS file that uses Angular Material SASS mixins to produce color and typography CSS styles.

Let’s jump to src/styles.scss file and have a look at our theme:

src/styles.scss
@use "@angular/material" as mat;
html {
@include mat.theme(
(
color: (
theme-type: light,
primary: mat.$azure-palette,
tertiary: mat.$blue-palette,
),
typography: Roboto,
density: 0,
)
);
}
html,
body {
height: 100%;
}
body {
margin: 0;
font-family: Roboto, "Helvetica Neue", sans-serif;
}

Defining a theme

Angular Material represents a theme as a SASS map that contains your color, typography and density choices, as well as some base design system settings.

The mat.theme mixin

The mat.theme mixin takes a map that defines color, typography, and density values and outputs a set of CSS variables that control the component appearance and layout. These variables are based on Design Tokens.

The color variables are defined using the CSS color function light-dark so that your theme can switch between light and dark mode using the CSS property color-scheme.

The theme file in our application applies a azure and blue colors for primary and tertiary palettes, Roboto font, and standard density to the application’s Angular Material components. It targets the html selector to ensure the CSS variables are applied across the entire application.

The configuration object may have the following properties.

PropertyDescriptionOptional?
colorA single color palette, or a color mapYes, but required to declare CSS variables
typographyA single font family value, or a typography mapYes, but required to declare CSS variables
densityIntegers from 0 to -5Yes, defaults to 0

Each property is nothing but a dimension in Angular Material theming. For this chapter, we will focus on color property.

Color

The theme’s color determines the component color styles, such as the fill color of checkboxes or ripple color of buttons. It depends on color palettes of varying tones to build a color scheme. Check out the Palettes section to learn about available prebuilt palettes, or how to create custom palettes.

You can set the color in one of two ways: as a single color palette, or as a color map.

Single Color Palette

If you provide a single color palette, Angular Material uses its values for the theme’s primary, secondary, and tertiary colors. The CSS color values will be defined using light-dark CSS color function. Your application styles should define an explicit value declaration for the color-scheme CSS property.

A single color palette can be defined as below:

html {
@include mat.theme(
(
color: mat.$azure-palette,
)
);
}

Color Map

If you provide a color map, then the tertiary color palette can be configured separately from the primary palette. The tertiary palette can be used to add a distinct accent color to some components.

You can also set the theme-type to determine the colors. theme-type can be one of the following:

  • color-scheme - include both light and dark colors using the light-dark CSS color function
  • light - only define the light color values
  • dark - only define the dark color values

A color map can be defined as below:

html {
@include mat.theme(
(
color: (
theme-type: light,
primary: mat.$azure-palette,
tertiary: mat.$blue-palette,
)
)
);
}

Color Palettes

A color palette is a set of similar colors with different hues ranging from light to dark. The Angular Material theme uses color palettes to create a color scheme to communicate an application’s hierarchy, state, and brand.

Let’s understand how a palette is structured, which are used for primary and tertiary properties as mentioned above.

Palette’s structure

A palette is a map of below properties:

PropertyRequiredDescription
primaryYesUsed for the most prominent components across the UI, such as the FAB, high-emphasis buttons, and active states
secondaryYesUsed for less prominent components in the UI such as filter chips
tertiaryNoUsed for contrasting accents that balance primary and secondary colors or bring heightened attention to an element such as an input field.
neutralYesUsed for foreground elements, as these colors provided less emphasis
neutral-variantYesUsed for medium emphasis and variants
errorYesUsed to communicate error states, such as an incorrect password entered into a text field

To understand more about these properties take a look at Material 3 Color roles.

Palette HCT Tonal Values

Each property of a palette is a map, which should have a valid CSS color associated with each of these keys, these are also referred as HCT Tonal Values: 0, 10, 20, 25, 30, 35, 40, 50, 60, 70, 80, 90, 95, 98, 99, 100 for all, plus 4, 6, 12, 17, 22, 24, 87, 92, 94, 96 extra colors for neutral.

For example, take a look at below map of $azure-palette:

$azure-palette: (
0: #000000,
10: #001b3f,
20: #002f65,
25: #003a7a,
30: #00458f,
35: #0050a5,
40: #005cbb,
50: #0074e9,
60: #438fff,
70: #7cabff,
80: #abc7ff,
90: #d7e3ff,
95: #ecf0ff,
98: #f9f9ff,
99: #fdfbff,
100: #ffffff,
secondary: (
// Colors map for the range 0, 10, 20, 25, 30, 35, 40, 50, 60, 70, 80, 90, 95, 98, 99, 100
),
tertiary: (
// Colors map for the range 0, 10, 20, 25, 30, 35, 40, 50, 60, 70, 80, 90, 95, 98, 99, 100
),
neutral: (
// Colors map for the range 0, 10, 20, 25, 30, 35, 40, 50, 60, 70, 80, 90, 95, 98, 99, 100
// plus additional colors for 4, 6, 12, 17, 22, 24, 87, 92, 94, 96
),
neutral-variant: (
// Colors map for the range 0, 10, 20, 25, 30, 35, 40, 50, 60, 70, 80, 90, 95, 98, 99, 100
),
error: (
// Colors map for the range 0, 10, 20, 25, 30, 35, 40, 50, 60, 70, 80, 90, 95, 98, 99, 100
),
);

azure palette primary HCT tonals

Theme Color Property vs Palette Map Property

It can be confusing having primary and tertiary at two places: theme configuration and palette property.

  1. primary/tertiary in theme configuration refers to the color roles
  2. primary/tertiary properties in palettes help define the whole color role.

Theme Configuration for different inputs

Let’s understand how the final theme will look like for different inputs to mat.theme mixin.

For any input, the final theme is constructed like below:

Color roleHCT Tonal Values
primaryprimary property from primary palette
secondarysecondary property from primary palette
tertiaryprimary property from tertiary palette
neutralneutral property from primary palette
neutral-variantneutral-variant property from primary palette
errorerror property from primary palette

Input 1: Not providing color property

When you don’t provide color property in theme configuration object, it will not generate CSS variables for color.

html {
@include mat.theme();
}

Input 2: Not providing any palettes

When neither primary nor tertiary palettes are assigned in theme configuration object, it will take default values.

html {
@include mat.theme(
(
color: (),
)
);
}
Palette/PropertyDefault Value
theme-typecolor-scheme
primary$violet-palette
tertiarysame as primary

Final theme looks like below:

default theme with violet palette

You can view the theme in action on Theme Builder for Angular Material.

Input 2: Providing only primary palette

When you provide only primary palette, Angular Material will use same for tertiary too:

html {
@include mat.theme(
(
color: (
primary: mat.$azure-palette,
),
)
);
}
Palette/PropertyProvided ValueInterpreted Value
theme-typeNonecolor-scheme
primary$azure-palette$azure-palette
tertiarysame as primary$azure-palette

Final theme looks like below:

theme with azure palette

You can view the theme in action on Theme Builder for Angular Material.

Input 3: Providing both primary and tertiary palette

html {
@include mat.theme(
(
color: (
primary: mat.$azure-palette,
tertiary: mat.$blue-palette,
),
)
);
}
Palette/PropertyProvided ValueInterpreted Value
theme-typeNonecolor-scheme
primary$azure-palette$azure-palette
tertiary$blue-palette$blue-palette

Final theme looks like below:

theme with azure blue palettes

You can view the theme in action on Theme Builder for Angular Material.

Summary

To summarize, below is how whole theme is constructed:

  1. You pass theme palettes for primary and tertiary
  2. A palette should contain these color roles: primary, secondary, tertiary (optional), neutral, neutral-variant, error
  3. Each color role should contain valid CSS color values for the range 0, 10, 20, 25, 30, 35, 40, 50, 60, 70, 80, 90, 95, 98, 99, 100, plus 4, 6, 12, 17, 22, 24, 87, 92, 94, 96 extra colors for neutral.

theme construction summary

For example, one could create a custom palette for golden brown (#B17F32) color like below:

$golden-brown-palette: (
primary: (
0: #000000,
10: #291800,
20: #452b00,
25: #533500,
30: #624000,
35: #724a00,
40: #815606,
50: #9d6e22,
60: #ba8739,
70: #d8a151,
80: #f6bc69,
90: #ffddb3,
95: #ffeedc,
98: #fff8f4,
99: #fffbff,
100: #ffffff,
),
secondary: (
0: #000000,
10: #291801,
20: #402d10,
25: #4c381a,
30: #584324,
35: #654e2f,
40: #725a3a,
50: #8d7350,
60: #a88c68,
70: #c4a680,
80: #e1c29a,
90: #feddb4,
95: #ffeedc,
98: #fff8f4,
99: #fffbff,
100: #ffffff,
),
tertiary: (
0: #000000,
10: #191e00,
20: #2c3400,
25: #364000,
30: #414c01,
35: #4c580e,
40: #58641a,
50: #717d31,
60: #8a9748,
70: #a5b260,
80: #c0ce78,
90: #dcea91,
95: #eaf99e,
98: #f6ffc0,
99: #fcffdd,
100: #ffffff,
),
neutral: (
0: #000000,
10: #201b15,
20: #352f29,
25: #403a34,
30: #4c463f,
35: #58514a,
40: #645d56,
50: #7e766e,
60: #988f87,
70: #b3aaa1,
80: #cfc5bb,
90: #ebe1d7,
95: #faefe5,
98: #fff8f4,
99: #fffbff,
100: #ffffff,
4: #120d08,
6: #17130d,
12: #241f19,
17: #2e2923,
22: #3a342d,
24: #3e3832,
87: #e3d8cf,
92: #f1e6dd,
94: #f7ece2,
96: #fdf2e8,
),
neutral-variant: (
0: #000000,
10: #221a0f,
20: #382f23,
25: #443a2d,
30: #504538,
35: #5c5143,
40: #685c4e,
50: #827566,
60: #9c8e7e,
70: #b8a998,
80: #d4c4b2,
90: #f1e0ce,
95: #ffeedc,
98: #fff8f4,
99: #fffbff,
100: #ffffff,
),
error: (
0: #000000,
10: #410002,
20: #690005,
25: #7e0007,
30: #93000a,
35: #a80710,
40: #ba1a1a,
50: #de3730,
60: #ff5449,
70: #ff897d,
80: #ffb4ab,
90: #ffdad6,
95: #ffedea,
98: #fff8f7,
99: #fffbff,
100: #ffffff,
),
);

Prebuilt Color Palettes

Angular Material provides twelve prebuilt color palettes that can be used for your application’s theme:

  • $red-palette
  • $green-palette
  • $blue-palette
  • $yellow-palette
  • $cyan-palette
  • $magenta-palette
  • $orange-palette
  • $chartreuse-palette
  • $spring-green-palette
  • $azure-palette
  • $violet-palette
  • $rose-palette

In our application, $azure-palette is used for primary and $blue-palette for tertiary.

Custom Color Palettes

The Angular Material palette generation schematic builds custom color palettes based on a single color input for the primary color, and optionally color inputs to further customize secondary, tertiary, and neutral palettes.

Let’s run the schematic to generate a custom color palette:

Terminal window
ng generate @angular/material:theme-color

After running the above command answer the prompts as below:

PromptAnswer
What HEX color should be used to generate the M3 theme?#6750a4
What HEX color should be used represent the secondary color palette?Leave blank
What HEX color should be used represent the tertiary color palette?Leave blank
What HEX color should be used represent the neutral color palette?Leave blank
Do you want to generate high contrast value override mixins for your themes?Yes
What is the directory you want to place the generated theme file in?src/styles/

Or you can run the schematic without answering any prompts by passing the color value as a parameter:

Terminal window
ng generate @angular/material:theme-color --primary-color 6750a4 --include-high-contrast --directory src/styles/ --interactive false

Generated file output

The result of running the schematic is a new file with the generated custom colors. Color palettes get defined in the generated file that you can pass into the theme() mixin in your own theme file. See the Sass themes section for more information.

Sass themes

The output of the schematic will create a file named _theme-colors.scss at the src/styles directory. The exported palettes ($primary-palette and $tertiary-palette) can be provided to the theme mixin within your theme file to use the custom colors.

Let’s import the generated file and use the palettes in our src/styles.scss file:

src/styles.scss
@use "@angular/material" as mat;
@use "./styles/theme-colors" as my-app-theme;
html {
@include mat.theme(
(
color: (
primary: mat.$azure-palette,
primary: my-app-theme.$primary-palette,
tertiary: mat.$blue-palette,
tertiary: my-app-theme.$tertiary-palette,
),
typography: Roboto,
density: 0,
)
);
}
// rest remains same

High contrast override mixins

High contrast override theme mixins are also generated in the file because we selected it in the prompts. These mixins override the system level variables with high contrast equivalent values from your theme. This is helpful for users who prefer more contrastful colors for either preference or accessibility reasons.

To show the high contrast values when user’s specify based on their color system preferences, apply the high-contrast-overrides() mixin from the generated file wrapped inside @media (prefers-contrast: more) in your theme file. You can pass in light, dark, or color-scheme. To see the high contrast values in your application locally, you can use Chrome DevTools to emulate the CSS media features.

src/styles.scss
html {
// rest remains same
@media (prefers-contrast: more) {
@include my-app-theme.high-contrast-overrides(light);
}
}

Supporting Light and Dark Mode

By default, the mat.theme mixin defines colors using the CSS color function light-dark to make it easy for your application to switch between light and dark mode. The light-dark function depends on the value of color-scheme declared in your application’s global styles. If your application does not define a value for color-scheme, then the light colors will always be applied.

You can define color-scheme: light or color-scheme: dark to explicitly define your application’s mode. To set the mode depending on the user’s system preferences, use color-scheme: light-dark. Let’s make that change in our src/styles.scss:

src/styles.scss
html {
color-scheme: light dark;
@media (prefers-contrast: more) {
@include my-app-theme.high-contrast-overrides(light);
@include my-app-theme.high-contrast-overrides(color-scheme);
}
}

Light and Dark themes based on user preferences

Instead of defining the mode in the theme file, you can define it based on user’s system preferences. We will use the strategy of defining color-scheme under a CSS selector so that the mode depends on whether that class has been applied. In our case, the application should display the light mode theme unless the class .dark-mode is added to the HTML body.

Let’s make that change in our src/styles.scss:

src/styles.scss
html {
color-scheme: light dark;
color-scheme: light;
// rest remains same
}
body.dark-mode {
color-scheme: dark;
}

Default background and text colors

We will use the following styles in src/index.html to apply the theme’s surface background and on-surface text colors as a default across the application:

src/index.html
<!doctype html>
<html lang="en">
<head>
<!-- rest remains same -->
<style>
.app-background {
background: var(--mat-sys-surface);
color: var(--mat-sys-on-surface);
}
</style>
</head>
<body class="mat-typography">
<body class="mat-typography app-background">
<app-root></app-root>
</body>
</html>

Theme toggle

To implement theme toggle, we will create a service and a component.

ThemeChangerService

To load the dark-theme based on user’s selection, we will simply implement a service called ThemeChangerService and whenever we want to change theme, we will call changeTheme from this service:

theme-changer.service.ts
import { DOCUMENT } from '@angular/common';
import { Injectable, inject, signal, effect } from '@angular/core';
import { LocalStorageService } from './local-storage.service';
const LOCAL_STORAGE_THEME_KEY = 'my-app-theme';
export type Theme = 'dark' | 'light' | 'auto';
export type ThemeWithoutAuto = Exclude<Theme, 'auto'>;
const DARK_MODE_CLASS = 'dark-mode';
@Injectable({ providedIn: 'root' })
export class ThemeChangerService {
private _document = inject(DOCUMENT);
private browserStorage = inject(LocalStorageService);
private _isDark = signal(false);
public isDark = this._isDark.asReadonly();
private _window = this._document.defaultView;
constructor() {
this.setTheme(this.getPreferredTheme());
if (this._window !== null && this._window.matchMedia) {
this._window
.matchMedia('(prefers-color-scheme: dark)')
.addEventListener('change', () => {
const storedTheme = this.getStoredTheme();
if (storedTheme !== 'light' && storedTheme !== 'dark') {
this.setTheme(this.getPreferredTheme());
}
});
}
effect(() => {
if (this._isDark()) {
this._document.documentElement.classList.add(DARK_MODE_CLASS);
this.setStoredTheme('dark');
} else {
this._document.documentElement.classList.remove(DARK_MODE_CLASS);
this.setStoredTheme('light');
}
});
}
private getStoredTheme = () =>
this.browserStorage.load<ThemeWithoutAuto>(LOCAL_STORAGE_THEME_KEY);
private setStoredTheme = (theme: ThemeWithoutAuto) => {
this.browserStorage.save(LOCAL_STORAGE_THEME_KEY, theme);
};
private getPreferredTheme = (): Theme => {
const storedTheme = this.getStoredTheme();
if (storedTheme) {
return storedTheme;
}
if (this._window !== null && this._window.matchMedia) {
return this._window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light';
}
return 'light';
};
private setTheme = (
theme: Theme,
document = this._document,
isDarkSignal = this._isDark
) => {
if (document.defaultView !== null && document.defaultView.matchMedia) {
if (
theme === 'auto' &&
document.defaultView.matchMedia('(prefers-color-scheme: dark)').matches
) {
isDarkSignal.set(true);
} else {
isDarkSignal.set(theme === 'dark');
}
}
};
public changeTheme(theme: ThemeWithoutAuto) {
this.setStoredTheme(theme);
this.setTheme(theme);
}
}

Codes for both, ThemeManager and BrowserStorage are self-explanatory. Please modify it according to need of your application.

ThemeToggleComponent

Now, we will create a component called ThemeToggleComponent which will be used to toggle the theme:

theme-toggle.component.html
<div class="track [class.is-dark-mode]="isDark()">
<div class="dark-mode switch" role="button" (click)="changeTheme('dark')">
<mat-icon>dark_mode</mat-icon>
</div>
<div class="light-mode switch" role="button" (click)="changeTheme('light')">
<mat-icon>light_mode</mat-icon>
</div>
</div>

Next, we will add this component to the AppComponent:

app.component.html
<mat-toolbar color="primary">
<span>Todo App</span>
<app-theme-toggle class="theme-toggle"></app-theme-toggle>
</mat-toolbar>
Next arrow_forward Dimension: Typography Learn how to configure typography in your Angular Material application.