{ "community_link": "https://github.com/indepth-dev/content/discussions/36" }

Theming Angular App & Its Libraries

The journey from SASS based theming to CSS3 variables for Angular app and its libraries.

Theming Angular App & Its Libraries

Journey from SASS based theming to CSS3 variables for Angular app and its libraries.

I work on a large Angular app (~100+ modules) that offers dynamic themes: light and dark mode.

We had been using SASS variables and angular’s :host-context selector to theme our components but recently moved to CSS3 variable based themes which worked out great. This article talks about how one can use CSS3 variables for theming and its potential benefits. The article talks about the implementation in Angular, but the approach can be used for any Web App.

SASS based approach

In this approach a top level root element, <body> tag in our case had one of the classes applied to it to identify the current theme.

:host-context(.light-theme)::ng-deep {
  my-component-selector {
    color: light-theme-color
  }
}

:host-context(.dark-theme)::ng-deep {
  my-component-selector {
    color: dark-theme-color
  }
}
SASS based light/dark theme handling

This approach was working fine in terms of changing visual aspects on theme switch. But with our app growing significantly there were few issues we realised:

  1. Challenging for developer: It was difficult to make sure all our components work on both themes. We had to depend on code reviews , testing efforts to hope things are fine across themes. As you can probably guess, its way more efforts and still chances of bugs hitting are more.
  2. Performance hit: Increase in bundle size as similar styles for each theme were being bundled together. Only one of these styles were actually being applied at once.
  3. Consistent Colour Set: Making sure that component colours are from specific palette only and not outside of it.

CSS3 Variables (Custom Properties) to the rescue

With some research on alternative approaches to tackle these issues, we stumbled upon CSS3 variables.

In comparison to SASS variables which are compile time variables, these are supported natively in browsers.

This brings several benefits:

  1. Easy maintainability: Unlike SASS, we can override the value of existing custom properties & do not need separate variables per theme.
  2. Fast: Adding new theme to your App becomes super fast. You just need a file with new values for defined set of variables in your app.
  3. Extensibility: Variables could be loaded from Ts file or external source also like an API call. This could be really helpful for product applications that allows defining custom themes for partners or customers.
  4. Reliability: Making sure all components that use CSS3 variables are behaving as per variable values for theme. No chance of missing handling specific themes.
  5. Performance: Only one set of variables in browser at once. Variables can be swapped on theme change to load specific set of variables.

Note: If you are still supporting IE11, this won't work for you. Please have a look at browser support for CSS variables.

Implementation details

Our app is structured in a way where we have a parent app and couple of shared libraries (Angular library). For our app to be 100% theme compatible we needed to make sure our app & libraries also have support for light & dark themes.
Note: We apply the variables only on root and not at local component levels

Theme variable files. Below are the example files we have for Parent App. Similar files exist in shared libraries also.

Light Theme: Set of colours for light theme

:root {
    --primary-background-color: #FFFFFF
    --secondary-background-color: #FFFFFF
    --primary-border-color: #CCCCCC
    --error-border-color: #FF0000
    --text-color: #000000
    --sub-text-color: #737373
}

Dark theme: Set of colours for dark theme

:root {
    --primary-background-color: #000000
    --secondary-background-color: #566572
    --primary-border-color: #17242B
    --error-border-color: #F54F47
    --text-color: #FFFFFF
    --sub-text-color: #ADBBC4
}

Sample Component Style:

:host {
  background-color: var(--primary-background-color);
  
  p {
    color: var(--text-color);
  }
}

Importing styles from shared libraries into Parent App:

For theming the components that exists in shared libraries, the libraries need to output a file per theme with list of CSS3 variables used across the library.
These files should then be imported in parent app in light/dark theme files.

Our light-theme.scss should look like this:

@import "shared-library/library-light-theme.scss";

:root {
    --primary-background-color: #FFFFFF
    --secondary-background-color: #FFFFFF
    --primary-border-color: #CCCCCC
    --error-border-color: #FF0000
    --text-color: #000000
    --sub-text-color: #737373
}

Note: We need to configure assets from our libraries that should be part of distribution output.

If you are on Angular 9 or above, configure assets to be distributed in ng-package.json.

"assets": ["./src/lib/style"]

Till Angular 8: Use cpx module to simply copy files in the build directory.

cpx './projects/shared-library/src/lib/style/*' './dist/shared-library/style'

For each shared library, we have a set files with CSS3 variables for each theme in the main application.

Present Day State

  1. We have removed all our SASS code for handling the theming stuff & are now fully based on CSS3 variables.
  2. Consistent colour set: We have much better control on what colours are being used in the application & are standardised.
  3. Compile time safety: We have integrated style lint rule to have compile time checks to make sure only CSS3 variables are used for defining colours across application.
  4. Developer experience is great: New component development is not impacted and there is no overhead for the developers to make sure app works on all themes.
  5. Framework Agnostic: We have used this approach in multiple different applications (Angular & non-angular both). CSS3 variables are supported by modern browsers.
  6. Production ready: Application went into production and we are getting really good feedback from users. We haven't heard of any bugs around theming yet.
  7. Future scope: Compile time checks to make sure variable set is same across the themes, there exists fallback colours, no variable usage that is undefined.