64
loading...
This website collects cookies to deliver better user experience
In the latest releases of Angular Material, the SASS theming API has been reworked. In this article, we will learn about custom themes, modifying typography and much more using new SASS mixins.
core
, define-palette
, define-light-theme
and we will apply custom theme to Angular Material components. And we will also learn how to use a pre-built theme.<h1>
, <h2>
, etc.) for the application.define-dark-theme
. We will implement lazy loading for dark theme, so that it only loads when needed.alert
component and apply themes to it.MatButton
and add new variants for it.@angular/material
is Material Design UI components for Angular applications.@import
to @use
in 2019. The @use rule loads mixins, functions, and variables from other SASS stylesheets, and combines CSS from multiple stylesheets together. Stylesheets loaded by @use
are called "modules".@use
syntax, we can more easily determine what CSS is unused, and reduce the size of the compiled CSS output. Each module is included only once no matter how many times those styles are loaded.@import
usage to @use
for all imports into the Angular Material SASS styles. They updated their code base for all styles with version 12. You can check out that particular release for more information.core-theme
, all-components-theme
, define-palette
, etc. To summarize, below are main tasks which we will be doing:ng new my-app --style=scss --defaults
ng add @angular/material
ng add
command will install Angular Material, the Component Dev Kit (CDK), Angular Animations and ask you the following questions to determine which features to include:src/styles.scss
file and have a look at our theme:// src/styles.scss
@use "@angular/material" as mat;
@include mat.core();
$my-app-primary: mat.define-palette(mat.$indigo-palette);
$my-app-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);
$my-app-warn: mat.define-palette(mat.$red-palette);
$my-app-theme: mat.define-light-theme(
(
color: (
primary: $my-app-primary,
accent: $my-app-accent,
warn: $my-app-warn,
),
)
);
@include mat.all-component-themes($my-app-theme);
html,
body {
height: 100%;
}
body {
margin: 0;
font-family: Roboto, "Helvetica Neue", sans-serif;
}
@include mat.core();
core
mixin. Angular Material defines a mixin named core
that includes prerequisite styles for common features used by multiple components, such as ripples. The core mixin must be included exactly once for your application, even if you define multiple themes.$my-app-primary: mat.define-palette(mat.$indigo-palette);
$my-app-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);
$my-app-warn: mat.define-palette(mat.$red-palette);
primary
and accent
, and warn
palette is optional.define-palette
SASS function accepts a color palette, as well as four optional hue numbers. These four hues represent, in order: the "default" hue, a "lighter" hue, a "darker" hue, and a "text" hue.MatButton
’s theme uses the hues to generate font colors:// src/material/button/_button-theme.scss
// content reduced for brevity
// Applies a property to an mat-button element for each of the supported palettes.
@mixin _theme-property($theme, $property, $hue) {
$primary: map.get($theme, primary);
$accent: map.get($theme, accent);
$warn: map.get($theme, warn);
$background: map.get($theme, background);
$foreground: map.get($theme, foreground);
&.mat-primary {
#{$property}: theming.get-color-from-palette($primary, $hue);
}
&.mat-accent {
#{$property}: theming.get-color-from-palette($accent, $hue);
}
&.mat-warn {
#{$property}: theming.get-color-from-palette($warn, $hue);
}
&.mat-primary,
&.mat-accent,
&.mat-warn,
&.mat-button-disabled {
&.mat-button-disabled {
$palette: if($property == "color", $foreground, $background);
#{$property}: theming.get-color-from-palette($palette, disabled-button);
}
}
}
@mixin color($config-or-theme) {
$config: theming.get-color-config($config-or-theme);
$foreground: map.get($config, foreground);
.mat-button,
.mat-icon-button,
.mat-stroked-button {
@include _theme-property($config, "color", text);
}
}
$indigo-palette
, $pink-palette
and $red-palette
. You can check out other palettes at the Angular Material GitHub repo’s file:// src/material/core/theming/_palette.scss
// content reduced for brevity
$red-palette: (
50: #ffebee,
100: #ffcdd2,
200: #ef9a9a,
300: #e57373,
// ...
contrast: (
50: $dark-primary-text,
100: $dark-primary-text,
200: $dark-primary-text,
300: $dark-primary-text,
// ...
)
);
$pink-palette: (
50: #fce4ec,
100: #f8bbd0,
200: #f48fb1,
300: #f06292,
// ...
contrast: (
50: $dark-primary-text,
100: $dark-primary-text,
200: $dark-primary-text,
300: $dark-primary-text,
// ...
)
);
$indigo-palette: (
50: #e8eaf6,
100: #c5cae9,
200: #9fa8da,
300: #7986cb,
// ... continues to 900
contrast: (
50: rgba(black, 0.87),
100: rgba(black, 0.87),
200: rgba(black, 0.87),
300: white,
// ... continues to 900
)
);
$my-app-theme: mat.define-light-theme(
(
color: (
primary: $my-app-primary,
accent: $my-app-accent,
warn: $my-app-warn,
),
)
);
define-light-theme
or define-dark-theme
with the result from define-palette
. The choice of a light versus a dark theme determines the background and foreground colors used throughout the components.@include mat.all-component-themes($my-app-theme);
all-component-themes
mixin.src/material/core/theming/_all-theme.scss
to see the mixin all-component-themes
:// src/material/core/theming/_all-theme.scss
@mixin all-component-themes($theme-or-color-config) {
$dedupe-key: 'angular-material-theme';
@include theming.private-check-duplicate-theme-styles($theme-or-color-config, $dedupe-key) {
@include core-theme.theme($theme-or-color-config);
@include autocomplete-theme.theme($theme-or-color-config);
@include badge-theme.theme($theme-or-color-config);
@include bottom-sheet-theme.theme($theme-or-color-config);
@include button-theme.theme($theme-or-color-config);
// other material components' themes...
}
}
all-component-colors
and all-component-typographies
mixins.// src/material/core/color/_all-color.scss
@mixin all-component-colors($config-or-theme) {
$config: if(theming.private-is-theme-object($config-or-theme),
theming.get-color-config($config-or-theme), $config-or-theme);
@include all-theme.all-component-themes((
color: $config,
typography: null,
density: null,
));
}
all-components-typography
mixin is present at src/material/core/typography/_all-typography.scss
:// src/material/core/typography/_all-typography.scss
@mixin all-component-typographies($config-or-theme: null) {
$config: if(theming.private-is-theme-object($config-or-theme),
theming.get-typography-config($config-or-theme), $config-or-theme);
@include badge-theme.typography($config);
@include typography.typography-hierarchy($config);
@include autocomplete-theme.typography($config);
@include bottom-sheet-theme.typography($config);
@include button-theme.typography($config);
// other components' typographies
}
styles
size after the build
command and then I’ll show you how to reduce it:all-component-colors
, all-component-typographies
and all-component-themes
, each Angular Material component has a color
, a typography
and a theme
mixin.// src/material/button/_button-theme.scss
// content reduced for brevity
@mixin color($config-or-theme) {
$config: theming.get-color-config($config-or-theme);
$primary: map.get($config, primary);
$accent: map.get($config, accent);
$warn: map.get($config, warn);
// sets up color for buttons
}
@mixin typography($config-or-theme) {
$config: typography.private-typography-to-2014-config(
theming.get-typography-config($config-or-theme));
.mat-button, .mat-raised-button, .mat-icon-button, .mat-stroked-button,
.mat-flat-button, .mat-fab, .mat-mini-fab {
font: {
family: typography-utils.font-family($config, button);
size: typography-utils.font-size($config, button);
weight: typography-utils.font-weight($config, button);
}
}
}
@mixin theme($theme-or-color-config) {
$theme: theming.private-legacy-get-theme($theme-or-color-config);
@include theming.private-check-duplicate-theme-styles($theme, 'mat-button') {
$color: theming.get-color-config($theme);
$typography: theming.get-typography-config($theme);
@if $color != null {
@include color($color);
}
@if $typography != null {
@include typography($typography);
}
}
}
all-component-themes
from styles.scss
and instead, add core-theme
:// @include mat.all-component-themes($my-app-theme); <-- removed
@include mat.core-theme($my-app-theme);
core-theme
emits theme-dependent styles for common features used across multiple components, like ripples.MatButton
, so we will add button-theme
:@include mat.button-theme($my-app-theme);
theme
’s in the same way. But, core-theme
is only needed once per theme. Let’s look at the styles
size now after build.styles/themes/_light.scss
:// src/styles/themes/_light.scss
@use "sass:map";
@use "@angular/material" as mat;
$my-app-light-primary: mat.define-palette(mat.$indigo-palette);
$my-app-light-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);
$my-app-light-warn: mat.define-palette(mat.$red-palette);
$my-app-light-theme: mat.define-light-theme(
(
color: (
primary: $my-app-light-primary,
accent: $my-app-light-accent,
warn: $my-app-light-warn,
),
)
);
styles.scss
:// styles.scss
@use "@angular/material" as mat;
@use "./styles/themes/light";
@include mat.core();
@include mat.core-theme(light.$my-app-light-theme);
@include mat.button-theme(light.$my-app-light-theme);
html,
body {
height: 100%;
}
body {
margin: 0;
font-family: Roboto, "Helvetica Neue", sans-serif;
}
[mat-raised-button]
in application and see how it looks:<button mat-raised-button color="primary">Raised</button>
<button mat-raised-button color="accent">Accent</button>
<button mat-raised-button color="warn">Warn</button>
Theme | Light or dark? | Palettes (primary, accent, warn) |
---|---|---|
deeppurple-amber.css | Light | deep-purple, amber, red |
indigo-pink.css | Light | indigo, pink, red |
pink-bluegray.css | Dark | pink, bluegray, red |
purple-green.css | Dark | purple, green, red |
indigo-pink.css
’s theme, you just need to include that file in the styles
array of your project’s angular.json
file:"styles": [
"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
// other styles
],
index.html
:<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
Roboto
, it also added some global styles in styles.scss
:body {
font-family: Roboto, "Helvetica Neue", sans-serif;
}
Name | CSS Class | Native Element | Description |
---|---|---|---|
display-4 | .mat-display-4 |
None | 112px, one-off header, usually at the top of the page (e.g. a hero header). |
display-3 | .mat-display-3 |
None | 56px, one-off header, usually at the top of the page (e.g. a hero header). |
display-2 | .mat-display-2 |
None | 45px, one-off header, usually at the top of the page (e.g. a hero header). |
display-1 | .mat-display-1 |
None | 34px, one-off header, usually at the top of the page (e.g. a hero header). |
headline |
.mat-h1 or .mat-headline
|
<h1> |
Section heading corresponding to the <h1> tag. |
title |
.mat-h2 or .mat-title
|
<h2> |
Section heading corresponding to the <h2> tag. |
subheading-2 |
.mat-h3 or .mat-subheading-2
|
<h3> |
Section heading corresponding to the <h3> tag. |
subheading-1 |
.mat-h4 or .mat-subheading-1
|
<h4> |
Section heading corresponding to the <h4> tag. |
-- | .mat-h5 |
<h5> |
-- |
-- | .mat-h6 |
<h6> |
-- |
body-1 |
.mat-body or .mat-body-1
|
Body text | Base body text. |
body-2 |
.mat-body-strong or .mat-body-2
|
None | Bolder body text. |
caption |
.mat-small or .mat-caption
|
None | Smaller body and hint text. |
button | -- | -- | Buttons and anchors. |
input | -- | -- | Form input fields. |
define-typography-config
SASS function. This function accepts, in order, CSS values for font-size
, line-height
, font-weight
, font-family
, and letter-spacing
. You can also specify the parameters by name, as demonstrated in the example below.@use '@angular/material' as mat;
$my-custom-level: mat.define-typography-level(
$font-family: Roboto,
$font-weight: 400,
$font-size: 1rem,
$line-height: 1,
$letter-spacing: normal,
);
define-typography-config
SASS function. Every parameter for define-typography-config
is optional; the styles for a level will default to Material Design's baseline if unspecified.font-family
. Let’s see how.<head>
in index.html
:<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Work+Sans:wght@300;400;500&display=swap">
styles/typography/_config.scss
and create a variable in it:// src/styles/typography/_config.scss
$heading-font-family: "'Work Sans', sans-serif";
define-typography-config
in styles/typography/_config.scss
:$my-app-typography: mat.define-typography-config(
$display-4: mat.define-typography-level(112px, $font-family: $heading-font-family),
$display-3: mat.define-typography-level(56px, $font-family: $heading-font-family),
$display-2: mat.define-typography-level(45px, $font-family: $heading-font-family),
$display-1: mat.define-typography-level(34px, $font-family: $heading-font-family),
$headline: mat.define-typography-level(24px, $font-family: $heading-font-family),
$title: mat.define-typography-level(20px, $font-family: $heading-font-family),
);
core
mixin in styles.scss
:// src/styles.scss
@use "@angular/material" as mat;
@use "./styles/themes/light";
@use "./styles/typography/config" as typography;
@include mat.core(typography.$my-app-typography);
// rest remains same
core
will emit the default Material Design typography styles.core
mixin, we can specify your typography config when including any theme
mixin, like below:$custom-theme: mat.define-light-theme((
color: (
primary: $custom-primary,
accent: $custom-accent,
),
typography: $custom-typography,
));
core
mixin always emits typography styles, specifying a typography config to a theme
mixin results in duplicate typography CSS. You should only provide a typography config when applying your theme if you need to specify multiple typography styles that are conditionally applied based on your application's behavior.CSS class. If you check the
index.htmlfile,
mat-typographyclass is added to the
tag. It was done when we ran
mat-typography
class, you can also use individual classes listed in the levels table.<body>
in index.html
:
html
<body>
<!-- This header will *not* be styled because it is outside
.mat-typography` --><!-- This paragraph will be styled as `body-1` via the `.mat-body` CSS class applied -->
<p class="mat-body">Introductory text</p>
<div class="mat-typography">
<!-- This header will be styled as `title` because it is inside `.mat-typography` -->
<h2>Inner header</h2>
<!-- This paragraph will be styled as `body-1` because it is inside `.mat-typography` -->
<p>Some inner text</p>
<app-root></app-root>
</div>
src/styles/typography/_config.scss
:
`scss
style.scss
:
`scss
dark.scss
in the styles/themes
folder with the following content:
`scss.dark-theme
to render a dark theme.dark-theme
, instead of core-theme
and button-theme
, which we used in the original theme, we are using core-color
and button-color
. The reason behind that is we only want to change colors in dark-theme
and every other style should remain the same. If we use theme
mixins, it would generate all the styles again, which are not required.mat-app-background
to the <body>
tag in index.html
:
html
<body class="mat-typography mat-app-background">
<app-root></app-root>
</body>
dark-theme
is an additional theme and can be loaded based on user preferences. So, instead of making it part of the default application, we will lazy load it.angular.json
:
json
"styles": [
"src/styles.scss",
{
"input": "src/styles/themes/dark.scss",
"bundleName": "dark-theme",
"inject": false
}
],
dark-theme
based on user’s selection, we will simply implement a service called style-manager.service.ts
and whenever we want to change theme, we will simply call toggleDarkTheme
from this service:
`typescriptlink[rel="stylesheet"].${getClassNameForKey(key)}
style-manager-${key}
;
app.component.ts
:
`typescript
app.component.html
:
`html
dark-theme.css
is only included when the user switches to the dark theme.alert
component with the template below:
`html
ng version
inside the project's folder, you will notice that version 10.1
is used. And we want to upgrade it to version 13
.Could not resolve dependency
or Conflicting peer dependency
, do the following:package.json
npm i
--force
bash
npx @angular/cli@11 update @angular/core@11 @angular/cli@11
bash
npx @angular/cli@11 update @angular/material@11
npm start
. Now, we will upgrade the project to version 12.
bash
npx @angular/cli@12 update @angular/core@12 @angular/cli@12
bash
npx @angular/cli@12 update @angular/material@12
@import
to @use
. So in all .scss
files, below @import
scss
@import "~@angular/material/theming";
@use
:
scss
@use "~@angular/material" as mat;
@use
rule loads mixins, functions, and variables from other SASS stylesheets, and combines CSS from multiple stylesheets together. Stylesheets loaded by @use
are called "modules".@import
rule. SASS will gradually phase it out over the next few years, and eventually remove it from the language entirelymat-get-color-config
is changed to mat.get-color-config
. mat-color
is changed to mat.get-color-from-palette
.sidenav.component.scss-theme.scss
:
bash
7 │ $config: mat-get-color-config($config-or-theme);
│ ^^^^^^^^^^^^^^^^
mat-get-color-config
to mat.get-color-config
. And make the same change in dialog.component.scss-theme.scss
:
scss
$config: mat.get-color-config($config-or-theme);
bash
28 │ @include _mat-toolbar-color($val);
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
MatToolbar
’s color mixin, we will use it’s SASS code. So change above line with below 2 lines in both, sidenav.component.scss-theme.scss
and dialog.component.scss-theme.scss
files:
scss
background-color: mat.get-color-from-palette($val);
color: mat.get-color-from-palette($val, default-contrast);
map
module functions should be used in the new module system. For that, first we will use the SASS:map
module using the @use
rule:
scss
@use "sass:map";
map-get
to map.get
in both, sidenav.component.scss-theme.scss
and dialog.component.scss-theme.scss
files:
scss
$primary: map.get($config, primary);
$accent: map.get($config, accent);
$warn: map.get($config, warn);
$foreground: map.get($config, foreground);
$background: map.get($config, background);
bash
npx @angular/cli@13 update @angular/core@13 @angular/cli@13
bash
npx @angular/cli@13 update @angular/material@13
.scss
files is the removal of ~
(tilde) from @use "~@angular/material" as mat;
.~
and it’s recommended that it’s removed from code.@use
as a relative path. If it cannot be resolved, then the loader will try to resolve @use
inside node_modules
.@import
rule migrated to @use
and SASS APIs were refactored for better developer experience.core
mixin, define-palette
function, palettes and define-light-theme
function and we created a custom theme. And then we applied our custom theme to first all components using all-components-theme
and at last we optimized it to use only core-theme
and button-theme
and reduced final styles size.styles
array of angular.json
. For example, we can add ./node_modules/@angular/material/prebuilt-themes/indigo-pink.css
to use the indigo-pink
theme in our application.define-typography-level
. Next, we learned that Angular Material handles all those levels using typography config, and Angular Material represents this configuration as a SASS map. We created a custom config using define-typography-config
and applied it to core
mixin so that custom typography is applied to the whole application.themes/dark-theme.scss
. Then we used only color mixins, i.e. core-color
and button-color
, and not theme mixing to avoid duplicate style generation. And at last, we made changes in angular.json
so that dark theme is loaded on demand only when needed.MatButton
. In this, we mainly followed the approach from its source code and we added two new variants to it: success
and info
.