When building bigger applications, we always strive for flexibility, extensibility and reusability. This applies not only to the actual application logic, but also to style sheets. Especially nowadays, where things like CSS variables and modules exist. These tools are great and they solve many different problems in a very elegant way. However, one thing that’s still super hard to do these days is theming. Being able to use existing, or create new components, but easily changing their look and feel without changing their code. This is often required when we build things that can be reused across different projects, or if the project we’re working on should simply enable the user to change the color scheme.
The Angular Material project comes with a built-in story for theming, including using any of Material Design’s own predefined themes, but also creating custom themes that will be used not only by components provided by Angular Material, but also our own custom ones.
In this article we’ll explore how theming is implemented, how pre-built themes can be used and how we can make our own custom components theme-able so they pick up the configured theme as well!
TABLE OF CONTENTS
What is a theme?
“Theme” can mean many different things to different people, so it’s good to clarify what a theme in the context of Angular Material means. Let’s get right into it.
The official theming guide is pretty much to the point here:
A theme is a set of colors that will be applied to the Angular Material components.
To be more specific, a theme is a composition of color palettes. That’s right, not just a single color palette, multiple color palettes. While this might sound unnecessary first, it turns out that this is a very powerful setup to define themes in a very flexible way.
Alright, but what color palettes are needed to compose a theme? As for Angular Material, it boils down to five different color palettes with each being used for different parts of the design:
- Primary - Main colors most widely used across all screens and components.
- Accent - Also known as the secondary color. Used for floating action buttons and interactive elements.
- Warn - Colors to convey error state.
- Foreground - Used for text and icons.
- Background - Colors used for element backgrounds.
If you want to dive deeper into the whole color usuability story in Material Design, we recommend checking out the Material Design Specification for colors, as it describes the topic in very deep detail.
Using pre-built themes
As mentioned earlier, Angular Material already comes with a set of pre-built themes that can be used right out of the box. Available pre-built themes are:
Using them is as easy as including or importing the dedicated CSS file that comes with all Angular Material builds. So assuming we’ve installed Angular Material in our Angular CLI project using:
We can go ahead and add any of the pre-built CSS files to our global styles by configuring our
Or, if we don’t want to fiddle around in our
angular-cli.json file, we can also import any pre-built theme right into the projects
styles.scss file like this:
We can easily try it out by having our application using Angular Material components. So first we add
MaterialModule to our
Then we go ahead and render, for example, Angular Material’s tool bar component:
Looks cool right? Another thing that’s worth mentioning is that some Material components offer properties to configure whether they use the current theme’s primary, accent or warn color:
Custom theme using built-in color palettes
Alright, using pre-built themes is a pretty cool thing as we get good looking components right away without doing any serious work. Let’s talk about how to create a custom theme using Angular Material’s predefined color palettes.
In order to create a custom theme, we need to do a couple of things:
- Generate core styles - These are theme independent styles, including styles for elevation levels, ripple effects, styles for accessibility and overlays
- Primary color palette - Generate color palette for the theme’s primary color
- Accent color palette - Generate color palette for the theme’s accent color
- Warn color palette - Generate color palette for the theme’s warn color
- Theme generation - Given the color palettes we generated, we create a theme, which can be used by Angular Material, or custom components
While this looks like a lot of work, it turns out Angular Material gives us many tools to make these tasks a breeze. Let’s start off by creating a new
custom-theme.scss file and import it in our root
styles.scss instead of the pre-built theme. After that we’ll go through the list step by step:
Generate core styles
This is a pretty easy one. Angular Material provides many very powerful SCSS mix-ins that do all the ground work for us. The mix-in that generates Material’s core styles is called
mat-core. All we have to do is to import and call it.
Here’s what that looks like (custom-theme.scss):
Generate color palettes
The next thing we need to do is to generate color palettes, which can then be composed to an actual theme. To generate a color palette, we can use Angular Material’s
mat-palette takes a base palette (yes, that’s another palette, more on that in a second), and returns a new palette that comes with Material specific hue color values for “light”, “dark” and “contrast” colors of the given base palette.
But what is this base palette? The base palette is just another color palette that compromises primary and accent colors of a single color. Wait, this sounds super confusing! Let’s take Material Design’s red color palette as an example:
Here we see all color codes for lighter and darker versions of the color red, as part of the Material Design specification. The values 50 - 900 represent the hue values or the “strength” of the color, or how light or dark it is. 500 is the recommended value for a theme’s primary color. There are much more defined color palettes and they are very nicely documented right here.
So now that we know what a base palette is, we need to figure out how to create such a thing. Do we have to define and write them ourselves? The answer is yes and no. If we want to use our own custom color palettes, we need to define them manually. However, if we want to use any of Material Design colors, Angular Material comes with predefined palette definitions for all of them! If we take a quick look at the source code, we can see how to palette for the color red is implemented:
It’s basically just a map where each key (tone value) maps to a color code. So if we ever want to define our own custom color palette, this is what it could look like.
Okay, let’s create a palette for our primary, accent and warn colors. All we have to do is to call the
mat-palette mix-in with a base color palette. Let’s use
$mat-light-blue for primary,
$mat-orange for accent and
$mat-red for warn colors. We can simply reference these variables because we imported Angular Material’s theming capabilities in the previous step:
Oh wait, what’s that? Why do we pass additional values to
mat-palette when generating our accent color palette? Well… Let’s take a closer look at what
mat-palette actually does.
We’ve already mentioned that
mat-palette generates a Material Design color palette out of a base color palette. But what does that actually mean? In order to get a better picture of what’s going on in that mix-in, let’s take a look at its source code:
A mix-in is just a function - it takes arguments and returns something.
mat-palette takes a base color palette (which is a map like
$mat-red) and optional default values for the generated color palette’s default, lighter and darker colors. Eventually it returns a new color palette that has some additional map values. Those additional values are the mentioned
darker colors, as well as their corresponding
darker-contrast colors. On top of that it generates keys for contrast values for each base hue tone (50 - 900).
As we can see, we basically end up with a color palette that comes with everything the base palette provides, plus some additional keys for easy accessibility. So coming back to the question why we pass additional values to
mat-palette for our accent color, we now know that all we do is configuring the
darker color tone.
A theme lets us apply a consistent tone to our application. It specifies the darkness of the surfaces, level of shadow and appropriate opacity of ink elements. The Material Design specification describes two different variations of themes - dark and light.
Angular Material implements another set of mix-ins to generate either light or dark themes using
mat-dark-theme respectively. Now that we have all of our color palettes in place, we can do exactly that. Let’s create a light theme object like this:
If we take a quick look at
mat-light-theme’s source code, we can see that ti really just prepares another map object that can be later easily consumed for theming:
That’s it! We can now use that generated theme object and feed it to Angular Material’s
angular-material-theme mix-in, which really just passes that theme object to other mix-ins for each component, so they can access the color values from there:
Here’s the complete code for our custom theme, using
Theming custom components
There’s one thing we haven’t talked about yet: theming custom components. So far we’ve only changed the look and feel of Angular Material’s components. That’s because we’re calling the
angular-material-theme mix-in with our custom theme object. If we’d remove that call, we’d end up with all Material components in their base colors. This becomes more clear when we take a look at what
Every component in Angular Material comes with a dedicated theme mix-in, that takes a theme object to access its values for theme specific styles. We can use exactly the same pattern to theme our own custom components. This turns out to be very powerful because it enables us to easily change a theme in our entire application just by changing the theme object!
Let’s say we have a custom component
FileTreeComponent as we created it in MachineLabs (a project you might want to check out!).
FileTreeComponent renders a list of files and we want that component to respond to the configured theme. Here’s what its template looks like (simplified):
It also comes with a base CSS file that introduces just enough styles so that the component is usable and accessible. No colors applied though. We won’t go into much detail here because there’s nothing new to learn. However, just to give a better idea, here are some corresponding base styles for
The component looks something like this:
We want to add theming capabilities to the following elements inside
FileTreeComponent when a theme is applied:
.ml-file-listneeds a border in the “foreground” color of the configured theme
.ml-file-list-itemneeds the theme’s background hover color when hovering over it
ml-file-list-itemis selected, we need to give it a lighter version of the theme’s primary color
These rules can be easily implemented, simply by following the same pattern that Angular Material is using for its own components. We define a mix-in for
FileTreeComponent that takes a theme object and uses that to access theme values using
Let’ start off by creating a
ml-file-tree-theme mix-in and pull out the color palettes from the given theme we’re interested in (file-tree-theme.scss):
mat-light-theme created additional values for
background for our theme? With
map-get we can access any value by its key of a given map. In other words, we’re pulling out color palettes for the theme’s primary, warn, background and foreground colors.
Once that is done, we can start using color values of these color palettes in our style sheets using the
mat-color takes a color palette and a hue value (or one of the descriptive names like
lighter) returns the color corresponding color. If we want
.ml-file-list to have a border in the divider foreground color of the given theme, it’d look something like this:
We use exactly the same technique to theme the background color of
.ml-file-list-item like this:
One thing to note here is that
mat-color takes an optional third argument to configure the color’s opacity.
FileTreeComponent is now fully theme-aware and its look and feel responds to the configured theme. Here’s the complete code:
And here’s what our component looks like now:
Last but not least, we have to call the
ml-file-tree-theme mixing with our custom theme object. We do that by importing the mix-in in our
custom-theme.scss file and execute it like this:
In fact, we can take it one level further and create a meta theme mix-in that executes all theme mix-ins for our custom components, the same way Angular Material does it with
angular-material-theme. To do that we create a new mix-in
custom-theme, which would look like this:
Here again, the complete code of our
Angular Material’s theming capabilities are very powerful and as of right now, it seems to be the only UI component library that gets it fairly right. Color palettes can be easily changed and reused and custom components can be enabled to consume a configured theme to match the look and feel of the entire application.
Angular Master Class at Shopware
Join our upcoming public training!Get a ticket →
Get updates on new articles and trainings.
Join over 1400 other developers who get our content first.
Custom Overlays with Angular's CDK - Part 2
In this follow-up post we demonstrate how to use Angular's CDK to build a custom overlay that looks and feels...
Custom Overlays with Angular's CDK
The Angular Material CDK provides us with tools to build awesome and high-quality Angular components without adopting the Material Design...
Easy Dialogs with Angular Material
Building modals and dialogs isn't easy - if we do it ourselves. Angular Material comes with a powerful dialog service...
RxJS Master Class and courseware updates
If you've been following us for a while, you're quite aware that we're always striving to provide up-to-date and high-quality...
Advanced caching with RxJS
When building web applications, performance should always be a top priority. One very efficient way to optimize the performance of...
A web animations deep dive with Angular
Angular comes with a built-in animation system that lets us create powerful animations based on the Web Animations API. In...