Light and dark themes with Angular, Bootstrap and SASS

Let's say you have an Angular single-page app and use Bootstrap for styling. You want to let your users choose between multiple themes (e.g. a light and dark theme). You also want the option to use custom SASS for each theme, as well as for all themes at once.

The solution

If you just want the code, it's on github. It's CC-0, so feel free to copy it into your project :)

There's also a demo here

1. Install bootstrap

If you haven't included bootstrap yet, or if you've been using it via a CDN, install it with npm:

npm install bootstrap

2. Create your themes

Create a separate file for each theme you want to include. I'm using SASS here, but if you prefer SCSS, it's basically the same process. As a simple example, let's create two files in the src directory of our app, light.sass and dark.sass.

The light theme simply imports bootstrap with no changes:

@import '~bootstrap/scss/bootstrap'

The dark theme overrides bootstrap's default color variables:

$body-bg:       #060606
$body-color:    #d6d6d6

@import '~bootstrap/scss/bootstrap'

You can create as many themes as you like, just make sure to always @import bootstrap at the end.

If you don't fancy making your own themes, you can find free ones at bootswatch. Simply download the _variables.scss file for each theme you want, give it a unique name and place it inside your app!

3. Configure Angular

We want Angular to turn each of our themes into a separate CSS file, so we can switch between them at runtime. Add the following to your angular.json:

{
    //...
    "projects": {
        "my-project": {
            "architect": {
                "build": {
                    "styles": [
                        "src/styles.sass",
                        {
                            "input": "src/light.sass",
                            "bundleName": "light",
                            "inject": true
                        },
                        {
                            "input": "src/dark.sass",
                            "bundleName": "dark",
                            "inject": false
                        }
                    ]
                }
            }
        }
    }
}

For each theme, add a bundle to the styles array. This will tell angular to turn that SASS into a separate CSS file. The "inject" property should be set to true on your default theme (the one used for new visitors), and false on all others.

4. Implement a ThemeService

Let's create a service to change the theme at runtime:

ng generate service Theme

theme.service.ts

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class ThemeService {

  public static default = 'light';

  public get current(): string {
    return localStorage.getItem('theme') ?? ThemeService.default;
  }

  public set current(value: string) {
    localStorage.setItem('theme', value);
    this.style.href = `/${value}.css`;
  }

  private readonly style: HTMLLinkElement;

  constructor() {
    this.style = document.createElement('link');
    this.style.rel = 'stylesheet';
    document.head.appendChild(this.style);

    if (localStorage.getItem('theme') !== undefined) {
        this.style.href = `/${this.current}.css`;
    }
  }
}

This service lets us set the current theme, and then downloads the corresponding CSS file. The theme is saved to localStorage, so the next time the user opens the page, their selected theme will be applied automatically.

If your app has a user login system, you could even store the selection in a database, so that it's synced across devices.

If you're using Angular CDK, the MediaMatcher lets you check if the user has enabled a dark theme in their browser or operating system. You could then set your app to dark right away. However, not all browsers implement this feature, so it's a good idea to also let your users switch manually.

5. Add the theme switcher UI

Finally, we need some sort of UI that allows our users to select their favorite theme. In this case, since we only have two themes, a simple toggle button is enough. Let's generate a component for this:

ng generate component theme-switcher

theme-switcher.component.ts

import { Component, OnInit } from '@angular/core';
import { ThemeService } from "../theme.service";

@Component({
  selector: 'app-theme-switcher',
  templateUrl: './theme-switcher.component.html',
  styleUrls: ['./theme-switcher.component.sass']
})
export class ThemeSwitcherComponent implements OnInit {

  constructor(private theme: ThemeService) { }

  ngOnInit(): void {
  }

  public switchTheme(): void {
    if (this.theme.current === 'light') {
        this.theme.current = 'dark';
    } else {
        this.theme.current = 'light';
    }
  }

}

theme-switcher.component.html

<button class="btn btn-secondary" (click)="switchTheme()">Switch theme</button>

We can now use this component anywhere on our page, for example on the homepage or in the navigation bar.

Example

This post shows only the essential steps. I've published a more complete example on GitHub, which uses 4 different themes, and mixes SASS and SCSS.

If something isn't working, let me know by opening an issue there!

Have fun coding :)