A dark mode is a must-have little feature for your Angular web application. It is easier on the eyes and less draining for your smartphone battery.
Here is how you can add a dark mode to your Angular material app in three simple steps. The end result will be a minimal Angular Material app with an option for a dark mode for users. Here is how it will look like.

Let’s get started!
Video tutorial
If you’d like to read the tutorial, continue below 🙂
Step 1: Add a custom angular material theme
The Angular Material components library comes bundled with a few themes which we can use for your app. But for adding a dark mode, you need to add a custom theme file instead. A sample custom theme looks like the following.
@use '~@angular/material' as mat;
@import "~@angular/material/theming";
@include mat.core();
$angular-primary: mat.define-palette(mat.$teal-palette, 500, 100, 900);
$angular-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);
$angular-warn: mat.define-palette(mat.$red-palette);
$angular-default-theme: mat.define-light-theme(
(
color: (
primary: $angular-primary,
accent: $angular-accent,
warn: $angular-warn,
),
)
);
@include mat.all-component-themes($angular-default-theme);
Add this to the styles.scss file of your project.
The important bits here are the primary, accent and warn colors. The colors are actually fetched from material design palettes which consist of 19 base colors and their light, dark and contrast variations. For more details, please refer to the material design color page.
In our case, according to the theme above, we have a teal color as the primary color, pink as the accent color and red as the warn color. This will be our default ‘light’ theme and looks like this.

I’ve just added some basic material components, so we can see the effect of the theme changes. Feel free to customize as you want (the code link is available at the end of this post). In case you’re wondering how to layout components using the awesome Flex Layout library, check out my other post on creating a responsive card grid here.
Do you want to use colors not there in the material design color palettes? You can create a theme with custom colors using this super useful online tool and just copy paste the generated theme in your styles as above!
Add dark mode custom theme
For adding the dark mode theme, we just need to add an alternate theme to the theme file.
$angular-dark-theme: mat.define-dark-theme(
(
color: (
primary: $angular-primary,
accent: $angular-accent,
warn: $angular-warn,
),
)
);
.darkMode {
@include mat.all-component-colors($angular-dark-theme);
}
To add a dark theme, we used material’s predefined function mat-dark-theme, instead of mat-light-theme in our default theme. This ensures that background and foreground colors are set correctly for the dark mode. Then we include the color configuration changes inside of a class name (which we named ourselves) i.e. darkMode.
One last tweak to get the background and foreground color working is to add the class mat-app-background to our parent container in app.component.html.
Now whenever we add this class name to any HTML container, it will apply the dark theme to all of its children. So if we apply the class to the parent div in our app.component.html, this is what we’ll get.

Step 2: Adding the dark mode toggle behavior
We need to add the toggle button to allow the users to switch between the light and dark mode at runtime. Let’s add the toggle button first.
<mat-toolbar color="primary">
Angular Dark Mode
<div class="flex-stretch"></div>
<mat-icon class="mr-8">brightness_5</mat-icon>
<mat-slide-toggle [formControl]="toggleControl" class="mr-8">
</mat-slide-toggle>
<mat-icon>bedtime</mat-icon>
</mat-toolbar>
We’ve added a material slide toggle component and used two material icons to indicate light and dark mode on either side. We’ve bound it to a reactive form control in our component.
toggleControl = new FormControl(false);
We also introduce a HostBinding for our root app component, so we can use the same for setting the class dynamically. The HostBinding allows us to add the class to the component itself, rather than any of its children, thus removing the need for us to add any explicit parent container inside.
@HostBinding('class') className = '';
Then, it’s just a matter of listening to the toggle component’s changes and modifying our class name. We do this in the ngOnInit lifecycle event.
ngOnInit(): void {
this.toggleControl.valueChanges.subscribe((darkMode) => {
const darkClassName = 'darkMode';
this.className = darkMode ? darkClassName : '';
});
}
Great! If you test out your code now, you’ll see a toggle nicely switching between the light and dark mode. The background, foreground and component colors should be adjusting to the dark mode automatically.
Step 3: Fixing the material dialog and other overlays
Some Angular Material components such as the dialog and floating menu are rendered in an overlay container, instead of the root component’s hierarchy. So when you apply the dark mode, it won’t apply to them.
To fix this, we need to add the darkMode to the overlay container dynamically through code. Here is how we can add to our listener for the toggle control.
constructor(private overlay: OverlayContainer) { }
ngOnInit(): void {
this.toggleControl.valueChanges.subscribe((darkMode) => {
const darkClassName = 'darkMode';
this.className = darkMode ? darkClassName : '';
if (darkMode) {
this.overlay.getContainerElement().classList.add(darkClassName);
} else {
this.overlay.getContainerElement().classList.remove(darkClassName);
}
});
}
Firstly, we include the OverlayContainer in your app component. Then, we simply add and remove our darkMode class as the toggle is switched on and off.
To test this, we’ve added a simple material dialog in our app, which opens up with the first primary button. The dialog now follows the dark mode theme just like the other components.

Conclusion
And that’s it! We now have “proper” dark mode for our Angular material app, which spans all of our material components, the background and the foreground colors.
If you got the gist of this, however, you can use this method to actually add any number of alternate themes to your Angular material app. For instance, you could allow the user to choose which of the themes he/she may want to use depending their preferences (you could store those in localStorage e.g).
You could also use different themes for different areas of your application, giving a nice touch of branding to different sections of the app! Now that you understand the basics, you can go on and develop it as you see fit.
The complete code for the sample app is available on this github repository.
Updated code and demo app to Angular 12. There has been a change in the sass file syntax which has been updated in the post as well!
Thanks for reading!
Bye 🙂
Brilliant! A link to github repo would have been more helpful. I was using bootstrap earlier and now I am planning to use materials. This helped me.
Hey Parthiban!
Thanks for the compliment 🙂
The link to the github repo is already there at the very end. Posting it again here as well.
https://github.com/thisiszoaib/angular-dark-mode
Regards
Error: src/app/app.module.ts:121:1 – error TS2304: Cannot find name ‘toggleControl’.
121 toggleControl = new FormControl(false)
~~~~~~~~~~~~~
Error: src/app/app.module.ts:121:21 – error TS2304: Cannot find name ‘FormControl’.
121 toggleControl = new FormControl(false)
~~~~~~~~~~~
Hey Dave!
Check out the complete source code linked at the end of the post. You need to import the FormControl class from the angular core I believe.
Regards
Hi,
great tutorial. However, I ran into a problem trying to combine app style switching in one component with a responsive side menu from your other tutorial. If I add @Hostbinding, I lose reading of window width changes. Code below.
HTML:
menu
close
{{title}}
brightness_5
bedtime
dashboard
TAB
TS:
import { Component, ViewChild, HostBinding } from ‘@angular/core’;
import { BreakpointObserver, BreakpointState } from ‘@angular/cdk/layout’;
import { MatSidenav } from ‘@angular/material/sidenav’;
import { LoadingService } from ‘./shared/services/loading.service’;
import { FormControl } from ‘@angular/forms’;
import { OverlayContainer } from ‘@angular/cdk/overlay’;
import { Subscription } from ‘rxjs’;
@Component({
selector: ‘app-root’,
templateUrl: ‘./app.component.html’,
styleUrls: [‘./app.component.scss’],
})
export class AppComponent {
@ViewChild(MatSidenav)
@HostBinding(‘class’) className = ”; // have to disable this line to get to work BreakpointObserver
sidenav!: MatSidenav;
title = ‘TITLE’;
loading$ = this.loader.loading$;
toggleControl = new FormControl(false);
subscriptions: Subscription[] = [];
constructor(private observer: BreakpointObserver, public loader: LoadingService, private overlay: OverlayContainer) { }
ngAfterViewInit() {
this.subscriptions.push(
this.toggleControl.valueChanges.subscribe((darkMode) => {
const darkClassName = ‘darkMode’;
this.className = darkMode ? darkClassName : ”; // have to disable this line to get to work BreakpointObserver
if (darkMode) {
this.overlay.getContainerElement().classList.add(darkClassName);
} else {
this.overlay.getContainerElement().classList.remove(darkClassName);
}
}));
this.subscriptions.push(
this.observer.observe([‘(max-width: 800px)’]).subscribe((res: BreakpointState) => {
if (res.matches) {
this.sidenav.mode = ‘over’;
this.sidenav.close();
} else {
this.sidenav.mode = ‘side’;
this.sidenav.close();
}
}));
}
ngOnDestroy() {
this.subscriptions.forEach(subscription => subscription.unsubscribe());
}
}
Hey Sebastian,
Thanks for the compliment!
About your issue, there seems to be a syntax problem. The @ViewChild decorator needs to have a variable besides it which will contain the reference to the sidenav. So it should be:
@ViewChild(MatSidenav) sidenav!: MatSidenav; // this is one statement
@HostBinding(‘class’) className = ""; // This can be placed anywhere, also one statement
Hope that helps!
This is great, thanks! I used it to add a dark mode toggle to this personal project I’m working on 🙂
https://pjpscriv.github.io/ng-fb-message/
Great to hear that Peter! Though I’m getting an Angular Injector error when I open the link 🙂
Ah whoops, the dangers of a work in progress! 😅 If you’re interested to see how it was *meant* to look, it should be in a working state now
how to set primary color to text in angular material 12
Thanks for the great article! I’m using your solution in my project. There’s just one problem with the inputs autocomplete not applying dark mode. I know this is -webkit-autofill not related to angular. But it’s annoying and solutions on internet didn’t seem to work. Do you have any idea?
hello, great tutorial thank you for sharing, i’m getting a problem when setting the theme from local storage using @HostBinding(‘class’) className = localStorage.getItem(‘theme’) ?? ” ;
if the initial state is light theme everything works fine, but if its dark then the theme gets stuck in darkmode
This is simple and helpful thank you
Hello Zoaib, Thank you so much for this tutorial, I’m running to an issue that the background isn’t turning dark, but the palettes are changing (primary, accent, etc…).
How does the background get changed? what am I missing
There is a class called “mat-app-background” that needs to be added to the body (if I recall correctly).
Thanks a lot! It turned out the element that had mat-app-background didn’t take the height of the whole page, only the toolbar. but it works now, thanks.
Glad to help! 🙂
Hello, Can you please share your code with me in my email or share github repo please🤲
The code is already linked to at the very end. Hope that helps!