# Create a dark theme
Now we will add a dark theme in the application. You can do this either in the same theme file or in separate theme files.
## Multiple themes in one file
Defining multiple themes in a single file allows you to support multiple themes without having to manage loading of multiple CSS assets. The downside, however, is that your CSS will include more styles than necessary.
To control which theme applies when, `@include` the mixins only within a context specified via CSS rule declaration. See the [documentation for Sass mixins](https://sass-lang.com/documentation/at-rules/mixin) for further background.
Let's modify `src/styles/themes/_all.scss` to add a dark theme:
```scss
// src/styles/themes/_all.scss
// rest remains same
// Define a dark theme
$my-app-dark-theme: mat.define-dark-theme(
(
color: (
primary: mat.define-palette(mat.$pink-palette),
accent: mat.define-palette(mat.$blue-grey-palette),
),
)
);
```
Finally, make changes in `src/styles.scss` to apply dark theme when user prefers dark theme:
```scss
// src/styles.scss
// rest remains same
@media (prefers-color-scheme: dark) {
@include mat.core-color(themes.$my-app-dark-theme);
@include mat.button-color(themes.$my-app-dark-theme);
}
```
## Multiple themes across separate files
Now, instead of having all themes in one file, we can also have separate files for each theme. Let's see how we can achieve it.
First, create a `dark.scss` file under `styles/themes` folder with below content:
```scss
@use "@angular/material" as mat;
@use "./all" as themes;
.dark-theme {
@include mat.core-color(themes.$my-app-dark-theme);
@include mat.button-color(themes.$my-app-dark-theme);
}
```
Notice that we are using a class selector `.dark-theme` to render a dark theme.
### Avoiding duplicated theming styles
While creating `dark-theme`, instead of `core-theme` and `button-theme`, which we used in the original theme, we are using `core-color` and `button-color`. The reason behind that is we only want to change colors in `dark-theme` and every other style should remain the same. If we use theme mixins, it would generate all the styles again, which are not required.
### Changes for background and font color
To complete the theme setup for background and font color, we will need to add class `mat-app-background` to the `` tag in `index.html`:
```html
```
### Lazy load dark theme
We don't want `dark-theme`` to be loaded with our application, instead we want it based on user preferences. Let's first exclude it from our bundle.
To exclude `dark-theme` from bundle, we will set `inject` as `false` in `angular.json`'s `styles` array:
```json
"styles": [
"src/styles.scss",
{
"bundleName": "dark-theme",
"inject": false,
"input": "src/styles/themes/dark.scss"
},
]
```
Next, to load the `dark-theme` based on user's selection, we will simply implement a service called `theme-manager` and whenever we want to change theme, we will simply call `changeTheme` from this service:
```typescript
import { DOCUMENT } from '@angular/common';
import { Injectable, inject } from '@angular/core';
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
import { take } from 'rxjs/operators';
import { BrowserStorageService } from './browser-storage.service';
const LOCAL_STORAGE_KEY = 'angular-material.dev';
@Injectable({ providedIn: 'root' })
export class ThemeManager {
private document = inject(DOCUMENT);
private browserStorage = inject(BrowserStorageService);
private _isDarkSub = new BehaviorSubject(false);
isDark$ = this._isDarkSub.asObservable();
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());
}
});
}
}
getStoredTheme = () =>
JSON.parse(this.browserStorage.get(LOCAL_STORAGE_KEY) ?? '{}').theme;
setStoredTheme = (theme: string) => {
const meta = JSON.parse(this.browserStorage.get(LOCAL_STORAGE_KEY) ?? '{}');
meta.theme = theme;
this.browserStorage.set(LOCAL_STORAGE_KEY, JSON.stringify(meta));
};
getPreferredTheme = (): 'dark' | 'light' => {
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';
};
setTheme = (theme: string) => {
if (this._window !== null && this._window.matchMedia) {
if (
theme === 'auto' &&
this._window.matchMedia('(prefers-color-scheme: dark)').matches
) {
this.document.documentElement.setAttribute('data-bs-theme', 'dark');
this._isDarkSub.next(true);
} else {
this.document.documentElement.setAttribute('data-bs-theme', theme);
this._isDarkSub.next(theme === 'dark');
}
this.setMaterialTheme();
}
};
setMaterialTheme() {
this.isDark$.pipe(take(1)).subscribe((isDark) => {
if (isDark) {
const href = 'dark-theme.css';
getLinkElementForKey('dark-theme').setAttribute('href', href);
this.document.documentElement.classList.add('dark-theme');
} else {
this.removeStyle('dark-theme');
this.document.documentElement.classList.remove('dark-theme');
}
});
}
removeStyle(key: string) {
const existingLinkElement = getExistingLinkElementByKey(key);
if (existingLinkElement) {
this.document.head.removeChild(existingLinkElement);
}
}
changeTheme(theme: string) {
this.setStoredTheme(theme);
this.setTheme(theme);
}
}
function getLinkElementForKey(key: string) {
return getExistingLinkElementByKey(key) || createLinkElementWithKey(key);
}
function getExistingLinkElementByKey(key: string) {
return document.head.querySelector(
`link[rel="stylesheet"].${getClassNameForKey(key)}`
);
}
function createLinkElementWithKey(key: string) {
const linkEl = document.createElement('link');
linkEl.setAttribute('rel', 'stylesheet');
linkEl.classList.add(getClassNameForKey(key));
document.head.appendChild(linkEl);
return linkEl;
}
function getClassNameForKey(key: string) {
return `style-manager-${key}`;
}
```
With above, we will also need a `browser-storage` service, it's code is given below:
```typescript
import { Inject, Injectable, InjectionToken } from '@angular/core';
export const BROWSER_STORAGE = new InjectionToken('Browser Storage', {
providedIn: 'root',
factory: () => localStorage,
});
@Injectable({ providedIn: 'root' })
export class BrowserStorageService {
constructor(@Inject(BROWSER_STORAGE) public storage: Storage) {}
get(key: string) {
return this.storage.getItem(key);
}
set(key: string, value: string) {
this.storage.setItem(key, value);
}
remove(key: string) {
this.storage.removeItem(key);
}
clear() {
this.storage.clear();
}
}
```
Codes for both, `theme-manager` and `browser-storage` are self-explanatory. Please modify it according to need of your application.
### Output after creating a dark-theme
Now, let’s utilize the above service in `app.component.ts`:
```typescript
import { Component, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu';
import { ThemeManager } from './theme-manager.service';
@Component({
selector: 'app-root',
standalone: true,
imports: [CommonModule, MatButtonModule, MatIconModule, MatMenuModule],
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent {
title = 'my-app';
themeManager = inject(ThemeManager);
isDark$ = this.themeManager.isDark$;
changeTheme(theme: string) {
this.themeManager.changeTheme(theme);
}
}
```
As you have noticed, with `ThemeManager` service, we have also imported `MatIcon` and `MatMenu` modules. Let's make a few changes in template:
```html
Angular Material Theming System: Complete Guide
{{ (isDark$ | async) === true ? "dark_mode" : "light_mode" }}
System
Light
Dark
Raised
Accent
Warn
```
Next, let's add `icon-theme` and `menu-theme` mixins in our styles:
```scss
// src/styles.scss
// rest remains same
@include mat.icon-theme(themes.$my-app-light-theme);
@include mat.menu-theme(themes.$my-app-light-theme);
```
```scss
// src/styles/themes/dark.scss
// imports...
.dark-theme {
// rest remains same
@include mat.icon-color(themes.$my-app-dark-theme);
@include mat.menu-color(themes.$my-app-dark-theme);
}
```
Let’s look at the output now:

Notice that when we change theme, it changes colors and background colors of buttons and text. And also notice that `dark-theme.css`` is only included when the user switches to the dark theme.