How do I get it to use my theme's primary
and accent
colours for example?
I have set up my theme using material 2 custom palette as such. I have excluded the palette setup for brevity
$theme: mat.m2-define-dark-theme(
(
color: (
primary: $primary,
accent: $accent,
warn: $warn,
),
typography: mat.m2-define-typography-config(),
density: -2,
)
);
@include mat.core();
@include mat.all-component-themes($theme);
@include shared.shared-themes($theme);
@include components.component-themes($theme);
Angular material mat-menu items use the foreground and background colours of the theme. This is why when you toggle the theme on the angular material website it does not use primary
or accent
colours, but toggles between a white and grey colour depending on if it's a light or dark theme selected:
When looking at the select when choosing the purple theme on their site you can see each option is either a grey colour in light mode or a black colour in dark mode:
Turning our attention back to the menu-items this, can be corroborated by looking at the source in @node_modules/@angular/material/core/tokens/m2/mat_menu.scss
(Angular 18 if using Material 2)
We see in the file that the colours are generally dependent on the foreground and background colours:
// Tokens that can be configured through Angular Material's color theming API.
@function get-color-tokens($theme) {
$is-dark: inspection.get-theme-type($theme) == .dark;
$active-state-layer-color: inspection.get-theme-color($theme, foreground, base,
if($is-dark, .08, .04));
$text-color: inspection.get-theme-color($theme, foreground, text);
@return (
item-label-text-color: $text-color,
item-icon-color: $text-color,
item-hover-state-layer-color: $active-state-layer-color,
item-focus-state-layer-color: $active-state-layer-color,
container-color: inspection.get-theme-color($theme, background, card),
divider-color: inspection.get-theme-color($theme, foreground, divider),
);
}
These colours can be altered application-wide via the theme's contrast of your $primary
palette. Not recommended due to the number of component's dependent on it. This is mentioned in one of the answers here: Angular 2 Material Foreground
There is also a github issue on how to make a sweeping change to this: https://github.com/angular/components/issues/6244
The above might help for you in some circumstances where you want a very sweeping change.
But lets look at modifying a only, by way of example, a mat-menu-item.
For now, let's also assume you want to make the change on a particular component, and not application wide, such as a navbar that uses the mat-menu-item
Many solutions give up on being clean, if it can't be done using the palettes, and try to override the background-color
directly. However, you now have a dependency on angular material's classes in your CSS. So if angular material changes their classnames in version updates, your code will break. Additionally, you would need to use the deprecated ng-deep
.
To solve this, you might add a class to the mat-menu-item
component, and override the background-color
or color
on the class. This is better. But you could end up still needing to write very long CSS to make sure your override has higher specificity than angular material's CSS to take effect. Again, if Angular Material's internal code changes, it's CSS specificity might trump yours.
The trick is to use your own class and override the CSS variables that are at the html
level and so do not have specificity you need to compete with.
So if you inspect the css of your mat-menu-item you'll see the a focussed state colour, for example is set like this:
If we trawl further down our CSS on the mat-menu-item
we find the below section on the HTML level:
So lets say we want to alter the background color. We're probably interested in changing --mat-menu-item-focus-state-layer-color
and --mat-menu-item-hover-state-layer-color
And so the final solution to this problem is as follows:
<button class="custom-menu-list-item" mat-menu-item>
My item
</button>
And in our *component.theme.scss:
.custom-menu-list-item{
--mat-menu-item-focus-state-layer-color: red;
--mat-menu-item-hover-state-layer-color: red;
}
Or preferably, from a palette an example would be:
--mat-menu-item-hover-state-layer-color: #{mat.m2-get-color-from-palette($blue-primary, 500)}
And that's all there is to it. A final solution could look something like the following:
.custom-menu-list-item {
--mat-menu-item-focus-state-layer-color: #{mat.m2-get-color-from-palette($purple-primary, 500)};
--mat-menu-item-hover-state-layer-color: #{mat.m2-get-color-from-palette($purple-primary, 500)};
--mat-menu-item-label-text-color: #{mat.m2-get-color-from-palette($blue-primary 500)};
}
.custom-menu-list-item:hover {
--mat-menu-item-label-text-color: white;
}
It is worth mentioning in some scenarios Angular material doesn't have a CSS variable for a circumstance you have in mind. For example in a nested mat-menu
the text color is defined via a single variable, that is used for both highlighted and unhighlighted menu items on the path you've traversed through the menu. If you want a different color for highlighted items you'd have to fall back on using the angular material component selector, or in the worst case the classes combined with your class to achieve higher specificity:
mat-menu-item{
--mat-menu-item-label-text-color: white;
}
.custom-menu-list-item.nav-menu-list-item.mat-mdc-menu-item-highlighted {
--mat-menu-item-label-text-color: white;
}
If you want to make the change system-wide on all mat-menu
then you can put it on the body
element in one of your application's root theme files, to override the CSS variables on html
element.
If you've defined a particular theme class due to having multiple themes, which is quite common, overriding the variable there won't work right away.
This is because your theme class will be defined on a div
such as the themeClass$ observable below
<div class="mat-app-background" [class]="themeClass$ | async">
<app-nav-menu></app-nav-menu>
<div class="scroll-container">
<router-outlet></router-outlet>
</div>
</div>
.my-light-theme{
--mat-menu-item-hover-state-layer-color: red;
}
This is because components that use the CDK overlay like the mat-option
in a select, modals and menu-items render outside of the angular application.
So you will have to use one of the solutions that put your theme class on the CDKOverlay as well. There are other stackoverflow solutions out there that explain how to do this.
It involves injecting private overlayContainer: OverlayContainer
into a high level component and adding classes to the overlayContainers classList