Mastering Angular Material & Angular Material Theming (Latest Version)
Welcome to this comprehensive guide on Angular Material and its robust theming system! This document is designed for absolute beginners, taking you on a journey from understanding the foundational concepts to implementing advanced theming techniques and building real-world projects. By the end of this guide, you will be well-equipped to integrate Material Design into your Angular applications effectively and create visually stunning and accessible user interfaces.
1. Introduction to Angular Material & Angular Material Theming (Latest version: Angular 20)
Angular Material is a UI component library built by the Angular team, implementing Google’s Material Design principles. It provides a rich set of pre-built, high-quality UI components that are optimized for Angular applications, ensuring consistency, responsiveness, and accessibility out-of-the-box. Angular 20, released on May 28, 2025, brings significant performance enhancements, developer-centric features, and architectural refinements, including stabilized signal-based reactivity and improved server-side rendering.
What is Angular Material?
Angular Material is a collection of UI components designed to work seamlessly with Angular. These components adhere to Material Design, a design system created by Google that emphasizes a consistent user experience across different platforms and devices. From buttons and form fields to navigation and data tables, Angular Material provides ready-to-use components that are highly customizable and performant.
Why learn Angular Material? (Benefits, use cases, industry relevance)
Learning Angular Material offers numerous benefits for Angular developers:
- Design Consistency: Ensures a uniform and professional look and feel across your application, adhering to the well-regarded Material Design guidelines.
- Developer Productivity: Provides a vast library of pre-built, tested, and accessible components, significantly reducing development time and effort.
- Responsiveness: Components are built with responsive design in mind, adapting smoothly to various screen sizes and resolutions without extra CSS work.
- Accessibility: Prioritizes accessibility with built-in ARIA support, keyboard navigation, and focus management, making your applications usable by a wider audience.
- Seamless Integration with Angular: Developed by the Angular team, it integrates perfectly with Angular’s core features like dependency injection and modules.
- Industry Relevance: Material Design is widely adopted across many modern web applications, making Angular Material a highly sought-after skill in the industry.
Angular Material is ideal for:
- Building dashboards and administrative interfaces.
- Developing single-page applications (SPAs) with a clean and modern UI.
- Creating highly interactive web applications that require a consistent user experience.
- Any Angular project where a professional and polished UI is a priority.
A brief history
Angular Material has evolved alongside the Angular framework itself. It started as an implementation of Material Design for AngularJS and then transitioned to Angular (2+). Each major Angular release brings updates and improvements to Angular Material, leveraging new Angular features like the recent focus on signals and zoneless change detection in Angular 20. The latest versions have focused on leveraging CSS custom properties and SCSS mixins for more powerful and flexible theming.
Setting up your development environment
Before we dive into Angular Material, you need to have a working Angular development environment.
Prerequisites:
- Node.js: Angular requires Node.js (version 20.19 or higher for Angular 20). You can download it from the official Node.js website (nodejs.org). It’s recommended to use the latest LTS (Long Term Support) version.
- npm (Node Package Manager): npm is installed automatically with Node.js.
- Angular CLI: The Angular Command Line Interface is a powerful tool to create, develop, and maintain Angular applications.
Step-by-step instructions:
Install Angular CLI: Open your terminal or command prompt and run the following command:
npm install -g @angular/cli@20This command installs the Angular CLI globally on your system. The
@20ensures you get the latest CLI version compatible with Angular 20.Create a new Angular project: Navigate to the directory where you want to create your project and run:
ng new my-material-app --style scss --defaultsmy-material-app: This is the name of your new Angular project.--style scss: This flag tells Angular to use SCSS (Sass) for styling, which is essential for Angular Material theming.--defaults: This flag uses the default options for routing and other configurations, making the setup quicker.
The CLI will create a new directory named
my-material-appwith all the necessary project files.Navigate into your project directory:
cd my-material-appAdd Angular Material to your project: Once inside your project, run the following command to add Angular Material:
ng add @angular/material@20The
ng addcommand will:- Install the necessary Angular Material and Angular CDK (Component Dev Kit) packages.
- Prompt you to choose a pre-built theme or a custom theme. For this guide, choose “Custom” when prompted, as we will be exploring custom theming in detail.
- Ask if you want to set up global Angular Material typography styles (select “Yes”).
- Ask if you want to include and enable Angular animations (select “Include and enable animations”).
After these steps, your environment is ready, and Angular Material is integrated into your project. You can now start the development server to see your application:
ng serve --openThis command compiles your application and launches it in your default web browser at
http://localhost:4200/.
2. Core Concepts and Fundamentals
In this section, we’ll break down the fundamental building blocks of Angular Material and introduce you to its basic components and the essence of theming.
2.1 Understanding Angular Material Components
Angular Material provides a rich set of UI components, each designed for a specific purpose and adhering to Material Design specifications. These components are Angular modules that you import into your application.
Detailed Explanation: Modules and Importing Components
Each Angular Material component (e.g., MatButton, MatCard) is part of its own Angular module (e.g., MatButtonModule, MatCardModule). To use a component, you need to import its corresponding module into the NgModule where you intend to use it. Typically, you’ll import these into your app.module.ts or a shared MaterialModule for better organization in larger applications.
Angular 20 is also heavily pushing for Standalone Components. If you’re using standalone components, you can import these modules directly into your component’s imports array.
Code Example: Using a Simple Button
Let’s start by adding a basic Material Button to our application.
Open
src/app/app.module.ts(if not using standalone components) and importMatButtonModule:import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { MatButtonModule } from '@angular/material/button'; // Import MatButtonModule import { AppComponent } from './app.component'; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, BrowserAnimationsModule, MatButtonModule // Add MatButtonModule to imports ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }Alternatively, for Standalone Components (Angular 17+ and favored in Angular 20): If your
app.component.tsis a standalone component (check forstandalone: truein@Component), you would importMatButtonModuledirectly there:import { Component } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; // Other imports if necessary (e.g., BrowserAnimationsModule in main.ts or app.config.ts) @Component({ selector: 'app-root', standalone: true, imports: [MatButtonModule], // Import directly into the component templateUrl: './app.component.html', styleUrl: './app.component.scss' }) export class AppComponent { title = 'my-material-app'; }Open
src/app/app.component.htmland add the Material button:<h1>Welcome to Angular Material!</h1> <button mat-button>Basic Button</button> <button mat-raised-button color="primary">Primary Button</button> <button mat-fab color="accent"> <mat-icon>home</mat-icon> </button>mat-button: This is a directive provided byMatButtonModulethat transforms a standard<button>element into a Material Design button.mat-raised-button: Creates a button with a raised appearance.color="primary": Applies the primary color from your theme.mat-fab: Creates a floating action button.<mat-icon>: To use Material icons, you’ll also needMatIconModuleand to link the Material Icons font. We’ll cover this soon! For now, you might just see the text “home”.
To get the Material Icons working, open
src/index.htmland add the following line within the<head>section:<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">You’ll also need to import
MatIconModulein yourapp.module.tsor standalone component:// In app.module.ts or standalone component import { MatIconModule } from '@angular/material/icon'; // ... inside @NgModule or @Component imports imports: [ // ... MatIconModule ]Now, if your
ng serveis running, you should see the buttons rendered with Material Design styles.
Exercise/Mini-Challenge: Add a Checkbox
Try to add a Material Design checkbox to your app.component.html.
Hint: You’ll need to find and import the correct Angular Material module for checkboxes.
Hint: The module for checkboxes is MatCheckboxModule.
Solution (in app.module.ts or standalone component imports):
import { MatCheckboxModule } from '@angular/material/checkbox';
// ...
imports: [
// ...
MatCheckboxModule
]
Solution (in app.component.html):
<mat-checkbox>Remember me</mat-checkbox>
2.2 Introduction to Angular Material Theming
Theming is one of the most powerful features of Angular Material, allowing you to customize the look and feel of your application to match your brand’s identity without writing extensive custom CSS. Angular Material theming is primarily done using SCSS (Sass).
Angular Material 19+ (and therefore Angular 20) has significantly improved its theming approach, leveraging CSS custom properties (CSS variables) and new SCSS mixins based on Material Design 3 (M3) design tokens. This makes theming more consistent and easier to manage, especially for light and dark modes.
Detailed Explanation: Theme Structure
An Angular Material theme is a Sass map that defines the colors, typography, and density for your application. The ng add @angular/material command typically creates a src/styles.scss file with a basic theme setup.
The core of Angular Material theming revolves around the mat.theme() mixin. This mixin takes a Sass map as an argument, where you define your color, typography, and density settings. It then outputs a set of CSS variables (also known as System Variables) that Angular Material components use to apply styles.
Key concepts in Angular Material theming:
- Palettes: A Material Design color palette consists of a set of 13 colors (a base color, 10 lighter/darker shades, and 2 accent colors). Angular Material provides a set of pre-built palettes (e.g.,
$mat-indigo,$mat-pink), or you can generate your own custom palettes based on a single seed color. - Primary, Accent, and Warn Colors: These are the three main color roles in a Material theme:
- Primary: Your application’s main brand color.
- Accent: A color used for elements like floating action buttons, toggles, and highlights.
- Warn: A color used for errors and warnings.
- Typography: Defines the font families, sizes, and weights for various text elements in your application.
- Density: Controls the visual spacing and compactness of components.
Code Example: Basic Theme Structure (src/styles.scss)
When you ran ng add @angular/material, it configured your src/styles.scss (or a similar global style file) to include a theme. It typically looks something like this (Angular 19+ syntax):
@use '@angular/material' as mat;
@include mat.core(); // Includes common styles that apply to all components
html {
// Define a light theme (default)
@include mat.theme((
color: (
theme-type: light,
primary: mat.$indigo-palette,
accent: mat.$pink-palette,
warn: mat.$red-palette,
),
typography: mat.define-typography-config(), // Uses default Material typography
density: 0, // Default density
));
}
// Optionally, define a dark theme for a class (e.g., for toggling dark mode)
.dark-theme {
@include mat.theme((
color: (
theme-type: dark,
primary: mat.$deep-purple-palette,
accent: mat.$cyan-palette,
warn: mat.$red-palette,
),
typography: mat.define-typography-config(),
density: 0,
));
}
// Apply default body colors based on the theme (crucial for proper background/text colors)
body {
background: var(--mat-sys-surface);
color: var(--mat-sys-on-surface);
}
@use '@angular/material' as mat;: Imports the Angular Material Sass module, aliased asmat.@include mat.core();: Includes global baseline styles that are needed for all components. This should be included only once in your application.mat.theme(): The main mixin to define your theme.theme-type: Specifies whether it’s alightordarktheme. This works with the CSScolor-schemeproperty.primary,accent,warn: These are set using pre-built palettes likemat.$indigo-palette.
body { background: var(--mat-sys-surface); color: var(--mat-sys-on-surface); }: This is crucial! Angular Material components rely on CSS variables for their colors. Setting these on thebodyensures that your application’s background and default text colors align with your chosen theme.
Exercise/Mini-Challenge: Change the Default Theme Colors
Modify src/styles.scss to use a different pre-built color palette for the primary and accent colors. For example, try mat.$purple-palette for primary and mat.$amber-palette for accent. Observe how your buttons (and other Material components you might have added) change color.
Solution (in src/styles.scss):
@use '@angular/material' as mat;
@include mat.core();
html {
@include mat.theme((
color: (
theme-type: light,
primary: mat.$purple-palette, // Changed
accent: mat.$amber-palette, // Changed
warn: mat.$red-palette,
),
typography: mat.define-typography-config(),
density: 0,
));
}
body {
background: var(--mat-sys-surface);
color: var(--mat-sys-on-surface);
}
3. Intermediate Topics
Now that you have a grasp of the fundamentals, let’s explore more advanced aspects of Angular Material, including custom palettes, responsive design, and common components.
3.1 Custom Color Palettes
While pre-built palettes are convenient, you’ll often need to create custom color palettes to perfectly match your brand’s colors. Angular Material provides a schematic and a way to define custom palettes.
Detailed Explanation: Generating and Using Custom Palettes
Angular Material 19+ (and Angular 20) encourages a modern approach to custom palettes, allowing you to generate a palette based on a single “seed” color. The ng generate @angular/material:theme-color schematic helps with this. It creates an SCSS file with the generated color palette which you can then @use in your main theme file.
Code Example: Creating and Applying a Custom Palette
Generate a custom theme color file: In your terminal, run:
ng generate @angular/material:theme-colorThe CLI will prompt you for a primary HEX color. Enter your desired primary color (e.g.,
#007bfffor a custom blue). You can leave secondary, tertiary, and neutral colors blank for now or provide them. It will also ask for the directory, defaultsrc/styles/is good.This command will generate a file like
src/styles/_theme-colors.scss(the name might vary slightly based on your input). It will contain a Sass map like$my-custom-primary-palette(the exact name depends on the input).Use the custom palette in
src/styles.scss: Opensrc/styles.scssand import your newly generated theme colors file. Then, use the generated palette in yourmat.thememixin.@use '@angular/material' as mat; @use './styles/_theme-colors.scss' as custom-theme; // Adjust path if needed @include mat.core(); html { @include mat.theme(( color: ( theme-type: light, primary: custom-theme.$primary-palette, // Use your custom primary palette accent: custom-theme.$tertiary-palette, // Use custom tertiary or another pre-built warn: mat.$red-palette, ), typography: mat.define-typography-config(), density: 0, )); } body { background: var(--mat-sys-surface); color: var(--mat-sys-on-surface); } // You can also define a dark theme using custom palettes .dark-theme { @include mat.theme(( color: ( theme-type: dark, primary: custom-theme.$dark-primary-palette, // Schematic generates dark versions too accent: custom-theme.$dark-tertiary-palette, warn: mat.$red-palette, ), typography: mat.define-typography-config(), density: 0, )); }Your components should now reflect your custom primary color!
Exercise/Mini-Challenge: Experiment with Secondary and Tertiary Colors
Re-run the ng generate @angular/material:theme-color command, but this time, provide HEX values for secondary and tertiary colors as well. Update your src/styles.scss to use these new custom palettes for the accent and potentially other roles.
3.2 Responsive Design with Angular Material Layout
Angular Material components are inherently responsive, but you often need to control the layout of your application for different screen sizes. Angular Material doesn’t provide a grid system like Bootstrap out-of-the-box, but it integrates well with CSS Flexbox or external layout libraries like flex-layout (though flex-layout is deprecated and modern CSS Grid/Flexbox are preferred).
Detailed Explanation: Flexbox and Media Queries
For responsive layouts, leverage CSS Flexbox (or Grid) directly. Angular components are designed to work well within these modern CSS layout approaches. For fine-grained control or specific component adjustments based on screen size, traditional CSS media queries are used within your component’s SCSS files.
Code Example: Responsive Card Layout
Let’s create a simple responsive layout using Angular Material cards and Flexbox.
Import
MatCardModuleandMatGridListModule(if you want to trymat-grid-listlater) into yourapp.module.tsor standalone component.// In app.module.ts or standalone component import { MatCardModule } from '@angular/material/card'; // import { MatGridListModule } from '@angular/material/grid-list'; // If you want to use mat-grid-list // ... imports: [ // ... MatCardModule, // MatGridListModule ]Update
src/app/app.component.html:<div class="container"> <mat-card class="example-card"> <mat-card-header> <mat-card-title>Card 1</mat-card-title> </mat-card-header> <mat-card-content> <p>This is the content of Card 1.</p> </mat-card-content> <mat-card-actions> <button mat-button>LIKE</button> <button mat-button>SHARE</button> </mat-card-actions> </mat-card> <mat-card class="example-card"> <mat-card-header> <mat-card-title>Card 2</mat-card-title> </mat-card-header> <mat-card-content> <p>This is the content of Card 2.</p> </mat-card-content> <mat-card-actions> <button mat-button>LIKE</button> <button mat-button>SHARE</button> </mat-card-actions> </mat-card> <mat-card class="example-card"> <mat-card-header> <mat-card-title>Card 3</mat-card-title> </mat-card-header> <mat-card-content> <p>This is the content of Card 3.</p> </mat-card-content> <mat-card-actions> <button mat-button>LIKE</button> <button mat-button>SHARE</button> </mat-card-actions> </mat-card> </div>Add styles to
src/app/app.component.scss:.container { display: flex; flex-wrap: wrap; // Allows cards to wrap to the next line gap: 20px; // Spacing between cards justify-content: center; // Center cards horizontally padding: 20px; } .example-card { width: 300px; // Default width } /* Media queries for responsiveness */ // For screens smaller than 900px (e.g., tablets) @media (max-width: 900px) { .example-card { width: 45%; // Two cards per row } } // For screens smaller than 600px (e.g., mobile phones) @media (max-width: 600px) { .example-card { width: 90%; // One card per row } }Resize your browser window to see how the cards adapt to different screen sizes.
Exercise/Mini-Challenge: Responsive Text Size
Using media queries, try to change the font size of your <h1> element in app.component.html (or a dedicated component) to be smaller on mobile screens (e.g., font-size: 1.5rem on screens smaller than 600px, 2.5rem otherwise).
Solution (in src/app/app.component.scss):
h1 {
font-size: 2.5rem; // Default size
}
@media (max-width: 600px) {
h1 {
font-size: 1.5rem; // Smaller on mobile
}
}
3.3 Common Angular Material Components
Angular Material offers a wide array of components. Here’s a quick look at a few common and highly useful ones.
Detailed Explanation: Form Fields, Dialogs, and Toolbars
mat-form-field: A wrapper that applies Material Design styling and behaviors to a common form field. It’s used with inputs, textareas, selects, etc., to provide consistent labels, hints, and error messages.MatDialog: A service for opening Material Design modal dialogs. Dialogs are an essential part of rich user interfaces for confirming actions, displaying additional information, or inputting data.mat-toolbar: A container for displaying titles, actions, and navigation for an application. Often used at the top of an application for branding and global navigation.
Code Examples: Using Common Components
Form Field (Input): First, import the necessary modules:
// In app.module.ts or standalone component import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; // ... imports: [ // ... MatFormFieldModule, MatInputModule ]Then, add to
src/app/app.component.html:<mat-form-field appearance="fill"> <mat-label>Enter your name</mat-label> <input matInput placeholder="e.g. John Doe"> </mat-form-field>Toolbar: Import
MatToolbarModule:// In app.module.ts or standalone component import { MatToolbarModule } from '@angular/material/toolbar'; // ... imports: [ // ... MatToolbarModule ]Then, add to
src/app/app.component.html(preferably at the top):<mat-toolbar color="primary"> <span>My Awesome App</span> <span class="spacer"></span> <!-- Pushes content to the right --> <button mat-icon-button><mat-icon>favorite</mat-icon></button> <button mat-icon-button><mat-icon>share</mat-icon></button> </mat-toolbar> <style> .spacer { flex: 1 1 auto; } </style>(Note: The
<style>tag is for quick demonstration. In a real app, putspacerin yourapp.component.scss).Dialog (requires more setup): For dialogs, you need to:
- Import
MatDialogModule. - Create a separate Angular component for the dialog’s content.
- Inject
MatDialogservice into your component to open the dialog.
First, import
MatDialogModule:// In app.module.ts or standalone component import { MatDialogModule } from '@angular/material/dialog'; // ... imports: [ // ... MatDialogModule ]Next, let’s create a simple dialog component. Generate a new component for the dialog content:
ng generate component dialog-overviewsrc/app/dialog-overview/dialog-overview.component.ts(Make it standalone for simplicity if your app supports it, otherwise useNgModuledeclarations/imports)import { Component } from '@angular/core'; import { MatDialogRef } from '@angular/material/dialog'; // Import MatDialogRef import { MatButtonModule } from '@angular/material/button'; // For the button inside the dialog @Component({ selector: 'app-dialog-overview', standalone: true, // Example with standalone component imports: [MatButtonModule], template: ` <h2 mat-dialog-title>Confirm Action</h2> <mat-dialog-content> <p>Are you sure you want to proceed?</p> </mat-dialog-content> <mat-dialog-actions align="end"> <button mat-button mat-dialog-close>No</button> <button mat-button [mat-dialog-close]="true" color="primary">Yes</button> </mat-dialog-actions> `, styles: [` // Optional styles for the dialog content `] }) export class DialogOverviewComponent { constructor(public dialogRef: MatDialogRef<DialogOverviewComponent>) {} }src/app/app.component.ts(to open the dialog)import { Component } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; // Import MatDialog import { DialogOverviewComponent } from './dialog-overview/dialog-overview.component'; // Import your dialog component // ... other imports @Component({ selector: 'app-root', standalone: true, imports: [ // ... other modules MatButtonModule, // For the button that opens the dialog DialogOverviewComponent // If standalone, import directly ], templateUrl: './app.component.html', styleUrl: './app.component.scss' }) export class AppComponent { title = 'my-material-app'; constructor(public dialog: MatDialog) {} // Inject MatDialog openDialog(): void { const dialogRef = this.dialog.open(DialogOverviewComponent, { width: '250px', }); dialogRef.afterClosed().subscribe(result => { console.log(`Dialog result: ${result}`); // 'true' if Yes was clicked }); } }src/app/app.component.html(add a button to open the dialog)<button mat-raised-button (click)="openDialog()">Open Dialog</button>Now, when you click “Open Dialog”, a Material Design dialog should appear.
- Import
Exercises/Mini-Challenges:
- Customize Form Field: Change the
appearanceof themat-form-fieldtooutlineorstandard. - Add Menu to Toolbar: Add a
mat-menuto your toolbar with a few menu items. Hint: You’ll needMatMenuModuleandmat-icon-buttonto toggle the menu.
4. Advanced Topics and Best Practices
This section will delve into more complex theming scenarios, overriding component styles, and general best practices for Angular Material development.
4.1 Advanced Theming: Dark Mode and Custom Typography
Modern applications often feature light and dark mode toggles. Angular Material’s new theming approach simplifies this significantly using theme-type and CSS variables.
Detailed Explanation: Toggling Themes and Custom Typography
As shown in Section 2.2, you can define multiple themes in your styles.scss (e.g., html for light, html.dark-theme for dark). Toggling between these themes involves simply adding or removing a CSS class (like dark-theme) from the html or body element.
For typography, the mat.define-typography-config() mixin allows you to specify font families, sizes, and weights for different typography levels (headline, title, body, etc.).
Code Example: Implementing Dark Mode Toggle
Ensure your
src/styles.scsshas bothlightanddarkthemes defined, as shown in Section 2.2.@use '@angular/material' as mat; @use './styles/_theme-colors.scss' as custom-theme; @include mat.core(); html { // Light theme @include mat.theme(( color: ( theme-type: light, primary: custom-theme.$primary-palette, accent: custom-theme.$tertiary-palette, warn: mat.$red-palette, ), typography: mat.define-typography-config(), density: 0, )); } // Dark theme applied when 'dark-theme' class is present on html html.dark-theme { @include mat.theme(( color: ( theme-type: dark, primary: custom-theme.$dark-primary-palette, // Use dark versions of your custom palette accent: custom-theme.$dark-tertiary-palette, warn: mat.$red-palette, ), typography: mat.define-typography-config(), density: 0, )); } body { background: var(--mat-sys-surface); color: var(--mat-sys-on-surface); }Add a
MatSlideToggleto yourapp.component.html:<mat-toolbar color="primary"> <span>My Awesome App</span> <span class="spacer"></span> <mat-slide-toggle (change)="onThemeChange($event)">Dark Mode</mat-slide-toggle> <button mat-icon-button><mat-icon>favorite</mat-icon></button> <button mat-icon-button><mat-icon>share</mat-icon></button> </mat-toolbar>Implement the
onThemeChangelogic insrc/app/app.component.ts:import { Component, Inject, Renderer2 } from '@angular/core'; import { DOCUMENT } from '@angular/common'; // Import DOCUMENT token import { MatSlideToggleChange, MatSlideToggleModule } from '@angular/material/slide-toggle'; // For the toggle component @Component({ selector: 'app-root', standalone: true, imports: [ // ... other imports MatSlideToggleModule, // Import MatSlideToggleModule // ... MatToolbarModule, MatIconModule, etc. as needed ], templateUrl: './app.component.html', styleUrl: './app.component.scss' }) export class AppComponent { title = 'my-material-app'; constructor( @Inject(DOCUMENT) private document: Document, // Inject Document private renderer: Renderer2 // Inject Renderer2 for DOM manipulation ) {} onThemeChange(event: MatSlideToggleChange): void { if (event.checked) { this.renderer.addClass(this.document.documentElement, 'dark-theme'); // Add class to html element } else { this.renderer.removeClass(this.document.documentElement, 'dark-theme'); // Remove class from html element } } }This approach directly manipulates the
htmlelement’s class, triggering the SCSS rules defined for.dark-theme.
Custom Typography Example
Define a custom typography config in
src/styles.scss:@use '@angular/material' as mat; // Define your custom typography configuration $my-custom-typography: mat.define-typography-config( $font-family: 'Roboto, "Helvetica Neue", sans-serif', $display-4: mat.define-typography-level(112px, 112px, 300), // Larger fonts if needed $headline-4: mat.define-typography-level(48px, 48px, 400), $body-1: mat.define-typography-level(16px, 24px, 400), // ... and so on for other typography levels ); html { @include mat.theme(( color: ( /* ... your colors ... */ ), typography: $my-custom-typography, // Use your custom typography density: 0, )); } // ... rest of your stylesYou would typically import a custom font (e.g., from Google Fonts) into your
index.htmlas well.
Exercise/Mini-Challenge: Add a custom font
Find a custom font on Google Fonts (e.g., “Open Sans”), import it into your index.html, and then update your $my-custom-typography map in src/styles.scss to use this new font family.
4.2 Overriding Component Styles
Sometimes, the default Material Design styling or even your custom theme isn’t enough, and you need to fine-tune a specific component’s appearance.
Detailed Explanation: Mixin Overrides and CSS Variables
Directly overriding Angular Material’s internal CSS classes with !important is generally discouraged because these internal classes can change in future updates, breaking your styles.
Angular Material 19+ (and 20) provides a much better approach: mixin overrides and CSS custom properties (CSS variables).
- Mixin Overrides (
mat.component-overrides): Each component’s styling documentation (material.angular.io/components/component-name/styling) now lists specific SCSS mixins (e.g.,mat.button-overrides(),mat.form-field-overrides()) that allow you to modify component properties like shapes, colors, and sizes in a safe and future-proof way. These are applied globally or scoped to a specific selector. - CSS Variables: For more granular control, you can inspect a component in your browser’s developer tools to find the underlying CSS variables (e.g.,
--mdc-filled-text-field-container-shape) that control its appearance. You can then override these variables directly in your SCSS.
Code Example: Customizing Button Shape
Let’s change the border-radius of all mat-raised-buttons to be more rounded or square.
Open
src/styles.scss:@use '@angular/material' as mat; // ... other @use statements html { @include mat.theme(( /* ... */ )); // Globally override button shape @include mat.button-overrides(( filled-container-shape: 24px // Makes buttons more rounded // filled-container-shape: 4px // Makes buttons more square )); // Or, directly override the CSS variable (less recommended for future compatibility than mixin) // --mdc-filled-button-container-shape: 24px; } body { /* ... */ }You can also scope these overrides to a specific component or class. For instance, to only affect buttons inside a
my-custom-sectionclass:.my-custom-section { @include mat.button-overrides(( filled-container-shape: 0px // Square buttons only in this section )); }And in your
app.component.html:<div class="my-custom-section"> <button mat-raised-button color="primary">Section Button</button> </div>
Exercise/Mini-Challenge: Customize Form Field Border Radius
Using the mat.form-field-overrides mixin, change the border radius of all mat-form-fields to a custom value (e.g., 16px). Consult the Angular Material documentation for the form-field component’s “Styling” tab to find the correct token name.
Hint: The property is container-shape.
Solution (in src/styles.scss):
@use '@angular/material' as mat;
// ...
html {
@include mat.theme(( /* ... */ ));
@include mat.form-field-overrides((
container-shape: 16px // Apply a custom border radius
));
}
4.3 Best Practices and Common Pitfalls
Adhering to best practices ensures your Angular Material application is maintainable, performant, and scales well.
Best Practices:
- Use SCSS for Theming: Always use SCSS for Angular Material theming. It’s built for it and provides the necessary features like mixins and variables.
- Centralize Theme Logic: Keep your core theme definitions (palettes, typography, density) in a single global SCSS file (e.g.,
src/styles.scss). - Use
mat.theme()andmat.component-overrides(): Prefer these new mixins for customizing styles over direct CSS class overrides with!important. - Lazy Load Modules: For larger applications, lazy load Angular modules that contain many Material components. This reduces initial bundle size.
- Modularize Material Imports: For larger applications, consider creating a
SharedMaterialModule(or similar) where you import and export all the Angular Material modules your application uses. This keepsapp.module.tsclean. - Accessibility (A11y): Angular Material components are built with accessibility in mind. Ensure you follow best practices when adding custom content or interactions (e.g., using
aria-label, correct heading structures). - Performance:
- Animations: While pleasant, overuse of animations can impact performance. Be mindful of complex animations, especially on low-end devices.
- Change Detection: Angular 20’s focus on Signals and Zoneless Change Detection is a major performance improvement. Embrace signals where appropriate for more targeted updates.
- Optimized Images: Use responsive images and optimize their file size.
Common Pitfalls:
- Overriding with
!important: As discussed, this leads to brittle and hard-to-maintain styles. Avoid it. - Not including
mat.core(): Forgetting@include mat.core();will result in basic styling issues across your Material components. It should be included once in your globalstyles.scss. - Incorrect
styles.scssorder: If you have multiple style files, ensure that your Angular Material theme is processed before any component-specific overrides that might rely on its variables. The order inangular.jsonmatters for global styles. - Missing
BrowserAnimationsModule: WithoutBrowserAnimationsModuleimported, many Angular Material components’ animations and some functionalities will not work correctly. - Not applying
backgroundandcolortobody: Forgetting to setbackground: var(--mat-sys-surface);andcolor: var(--mat-sys-on-surface);on yourbody(orhtml) will result in your application’s background and default text not matching your Material theme. - Z-index conflicts: Dialogs, menus, and tooltips use
z-index. Custom elements with highz-indexvalues can sometimes appear on top of Material components. Be mindful ofz-indexin your custom CSS.
5. Guided Projects
These projects will help you apply the concepts learned in a practical context.
Project 1: Basic Product Catalog with Theming
Objective: Create a simple product catalog displaying several products using Angular Material cards. Implement a custom theme and a dark mode toggle.
Problem Statement: You need to build a web page that showcases products. Each product should have a name, description, and an image. The page should incorporate Material Design aesthetics and allow users to switch between light and dark themes.
Steps:
Setup (if not already done):
- Create a new Angular project with SCSS:
ng new product-catalog --style scss --defaults - Navigate into the project:
cd product-catalog - Add Angular Material (choose “Custom” theme):
ng add @angular/material@20
- Create a new Angular project with SCSS:
Generate Components & Services:
- Generate a
ProductCardComponent:ng generate component components/product-card - Generate a
ProductService(optional, for mock data):ng generate service services/product
- Generate a
Define Product Interface and Mock Data:
- Create
src/app/models/product.model.ts:export interface Product { id: number; name: string; description: string; imageUrl: string; price: number; } - In
src/app/services/product.service.ts, provide some mock product data:import { Injectable } from '@angular/core'; import { Product } from '../models/product.model'; import { Observable, of } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class ProductService { private products: Product[] = [ { id: 1, name: 'Wireless Headphones', description: 'High-quality sound with noise cancellation.', imageUrl: 'https://via.placeholder.com/150/0000FF/FFFFFF?text=Headphones', price: 99.99 }, { id: 2, name: 'Smartwatch', description: 'Track your fitness and receive notifications.', imageUrl: 'https://via.placeholder.com/150/FF0000/FFFFFF?text=Smartwatch', price: 149.99 }, { id: 3, name: 'Portable Speaker', description: 'Compact design, powerful sound.', imageUrl: 'https://via.placeholder.com/150/00FF00/FFFFFF?text=Speaker', price: 75.00 }, ]; getProducts(): Observable<Product[]> { return of(this.products); } }
- Create
Design
ProductCardComponent:- Import Modules: In
src/app/components/product-card/product-card.component.ts(if standalone) orapp.module.ts, importMatCardModule,MatButtonModule. - Component Template (
product-card.component.html): Usemat-cardto display product details.<mat-card class="product-card"> <img mat-card-image [src]="product.imageUrl" [alt]="product.name"> <mat-card-header> <mat-card-title>{{ product.name }}</mat-card-title> <mat-card-subtitle>${{ product.price | number:'1.2-2' }}</mat-card-subtitle> </mat-card-header> <mat-card-content> <p>{{ product.description }}</p> </mat-card-content> <mat-card-actions> <button mat-raised-button color="primary">Add to Cart</button> <button mat-button>Details</button> </mat-card-actions> </mat-card> - Component Logic (
product-card.component.ts): Define@Input()forproductproperty.import { Component, Input } from '@angular/core'; import { Product } from '../../models/product.model'; import { MatCardModule } from '@angular/material/card'; import { MatButtonModule } from '@angular/material/button'; import { CommonModule } from '@angular/common'; // For pipe @Component({ selector: 'app-product-card', standalone: true, // Or remove if using NgModule imports: [MatCardModule, MatButtonModule, CommonModule], templateUrl: './product-card.component.html', styleUrl: './product-card.component.scss' }) export class ProductCardComponent { @Input() product!: Product; } - Component Styles (
product-card.component.scss):.product-card { width: 300px; margin: 16px; display: flex; flex-direction: column; img { max-height: 200px; object-fit: cover; } mat-card-actions { margin-top: auto; // Pushes actions to the bottom } }
- Import Modules: In
Integrate into
AppComponent:- Import Modules/Components: In
src/app/app.component.ts, importProductCardComponent(if standalone),ProductService,MatToolbarModule,MatSlideToggleModule,MatIconModule,DOCUMENT,Renderer2. - Logic (
app.component.ts): Fetch products and implement theme toggle.import { Component, OnInit, Inject, Renderer2 } from '@angular/core'; import { DOCUMENT, CommonModule } from '@angular/common'; import { MatToolbarModule } from '@angular/material/toolbar'; import { MatSlideToggleChange, MatSlideToggleModule } from '@angular/material/slide-toggle'; import { MatIconModule } from '@angular/material/icon'; import { Product } from './models/product.model'; import { ProductService } from './services/product.service'; import { ProductCardComponent } from './components/product-card/product-card.component'; import { FlexLayoutModule } from '@angular/flex-layout'; // Consider for layout, though native CSS is preferred now @Component({ selector: 'app-root', standalone: true, imports: [ CommonModule, MatToolbarModule, MatSlideToggleModule, MatIconModule, ProductCardComponent, // FlexLayoutModule // If you decide to use it ], templateUrl: './app.component.html', styleUrl: './app.component.scss' }) export class AppComponent implements OnInit { title = 'Product Catalog'; products: Product[] = []; constructor( private productService: ProductService, @Inject(DOCUMENT) private document: Document, private renderer: Renderer2 ) {} ngOnInit(): void { this.productService.getProducts().subscribe(products => { this.products = products; }); } onThemeChange(event: MatSlideToggleChange): void { if (event.checked) { this.renderer.addClass(this.document.documentElement, 'dark-theme'); } else { this.renderer.removeClass(this.document.documentElement, 'dark-theme'); } } } - Template (
app.component.html): Display the toolbar and loop through products.<mat-toolbar color="primary"> <span>{{ title }}</span> <span class="spacer"></span> <mat-slide-toggle (change)="onThemeChange($event)">Dark Mode</mat-slide-toggle> <button mat-icon-button><mat-icon>shopping_cart</mat-icon></button> </mat-toolbar> <div class="product-list-container"> <app-product-card *ngFor="let product of products" [product]="product"></app-product-card> </div> - Styles (
app.component.scss):.spacer { flex: 1 1 auto; } .product-list-container { display: flex; flex-wrap: wrap; justify-content: center; padding: 20px; gap: 20px; } /* Responsive adjustments for product cards if needed */ @media (max-width: 768px) { .product-list-container { justify-content: space-around; } app-product-card { width: 45%; // Two cards per row on smaller screens } } @media (max-width: 480px) { app-product-card { width: 90%; // One card per row on very small screens } }
- Import Modules/Components: In
Theming (
src/styles.scss):- Generate a custom palette (e.g.,
#3f51b5for primary,#ff4081for accent) usingng generate @angular/material:theme-color. - Update
src/styles.scsswith the custom palettes and dark theme as shown in previous sections. Ensurebodybackground/color are set via CSS variables.
@use '@angular/material' as mat; @use './styles/_theme-colors.scss' as custom-theme; // Adjust path as per generated file @include mat.core(); html { // Light theme @include mat.theme(( color: ( theme-type: light, primary: custom-theme.$primary-palette, accent: custom-theme.$secondary-palette, // Assuming schematic generated secondary warn: mat.$red-palette, ), typography: mat.define-typography-config(), density: 0, )); } html.dark-theme { // Dark theme @include mat.theme(( color: ( theme-type: dark, primary: custom-theme.$dark-primary-palette, accent: custom-theme.$dark-secondary-palette, warn: mat.$red-palette, ), typography: mat.define-typography-config(), density: 0, )); } body { background: var(--mat-sys-surface); color: var(--mat-sys-on-surface); margin: 0; // Remove default body margin }Encourage independent problem-solving: Now, try to:
- Add a “View Details” button to each card that, when clicked, opens a Material Dialog displaying more product information (you’ll need to reuse concepts from Section 3.3).
- Implement a search input in the toolbar that filters the displayed products based on name or description.
- Generate a custom palette (e.g.,
Project 2: Interactive Task Manager with Drag & Drop and Dynamic Theming
Objective: Build a simple task manager where users can add, categorize, and reorder tasks using Angular Material components, including drag-and-drop functionality and dynamic theme switching.
Problem Statement: You need a web application to manage tasks. Tasks should be displayable in categories, and users should be able to drag and drop tasks between categories and reorder them within a category. The application should also support switching themes dynamically.
Steps:
Setup:
- Create a new Angular project:
ng new task-manager --style scss --defaults - Navigate:
cd task-manager - Add Angular Material:
ng add @angular/material@20(choose “Custom” theme)
- Create a new Angular project:
Import Core Modules: In
app.module.ts(or relevant standalone componentimports):import { MatToolbarModule } from '@angular/material/toolbar'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { MatSlideToggleModule } from '@angular/material/slide-toggle'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; import { MatCardModule } from '@angular/material/card'; import { CdkDragDrop, DragDropModule, moveItemInArray, transferArrayItem } from '@angular/cdk/drag-drop'; // Import DragDropModule import { CommonModule, DOCUMENT } from '@angular/common'; // For NgFor and Document injection import { FormsModule } from '@angular/forms'; // For ngModel // ... in @NgModule imports array imports: [ // ... MatToolbarModule, MatButtonModule, MatIconModule, MatSlideToggleModule, MatFormFieldModule, MatInputModule, MatCardModule, DragDropModule, // Add DragDropModule CommonModule, FormsModule, ]Define Task Interface:
// src/app/models/task.model.ts export interface Task { id: string; title: string; description?: string; } export interface TaskCategory { id: string; name: string; tasks: Task[]; }AppComponentLogic (src/app/app.component.ts):- Initialize task categories.
- Implement drag-and-drop logic using
cdkDropListDroppedevent. - Implement theme toggle.
- Add a method to add new tasks.
import { Component, OnInit, Inject, Renderer2 } from '@angular/core'; import { CdkDragDrop, moveItemInArray, transferArrayItem, DragDropModule } from '@angular/cdk/drag-drop'; import { CommonModule, DOCUMENT } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { MatToolbarModule } from '@angular/material/toolbar'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { MatSlideToggleChange, MatSlideToggleModule } from '@angular/material/slide-toggle'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; import { MatCardModule } from '@angular/material/card'; import { Task, TaskCategory } from './models/task.model'; import { v4 as uuidv4 } from 'uuid'; // npm install uuid @types/uuid for unique IDs @Component({ selector: 'app-root', standalone: true, imports: [ CommonModule, FormsModule, MatToolbarModule, MatButtonModule, MatIconModule, MatSlideToggleModule, MatFormFieldModule, MatInputModule, MatCardModule, DragDropModule, ], templateUrl: './app.component.html', styleUrl: './app.component.scss' }) export class AppComponent implements OnInit { title = 'Task Manager'; newTaskTitle: string = ''; categories: TaskCategory[] = [ { id: 'todo', name: 'To Do', tasks: [] }, { id: 'inProgress', name: 'In Progress', tasks: [] }, { id: 'done', name: 'Done', tasks: [] } ]; constructor( @Inject(DOCUMENT) private document: Document, private renderer: Renderer2 ) {} ngOnInit(): void { // Load initial tasks (optional, could be from a service) this.categories[0].tasks.push( { id: uuidv4(), title: 'Plan project layout' }, { id: uuidv4(), title: 'Set up Angular Material' } ); this.categories[1].tasks.push( { id: uuidv4(), title: 'Develop user authentication' } ); } // Drag and Drop handler drop(event: CdkDragDrop<Task[]>) { if (event.previousContainer === event.container) { // Item moved within the same list moveItemInArray(event.container.data, event.previousIndex, event.currentIndex); } else { // Item moved to a different list transferArrayItem( event.previousContainer.data, event.container.data, event.previousIndex, event.currentIndex, ); } } addTask(categoryId: string): void { if (this.newTaskTitle.trim()) { const category = this.categories.find(c => c.id === categoryId); if (category) { category.tasks.push({ id: uuidv4(), title: this.newTaskTitle.trim() }); this.newTaskTitle = ''; // Clear input } } } onThemeChange(event: MatSlideToggleChange): void { if (event.checked) { this.renderer.addClass(this.document.documentElement, 'dark-theme'); } else { this.renderer.removeClass(this.document.documentElement, 'dark-theme'); } } // Helper to connect drag/drop lists getConnectedListIds(): string[] { return this.categories.map(c => c.id); } }(Note:
uuidis used for unique IDs. Install withnpm install uuid @types/uuid)AppComponentTemplate (src/app/app.component.html):- Use
mat-toolbarfor the header and theme toggle. - Use
mat-cardfor each task category. - Apply
cdkDropListandcdkDragdirectives for drag-and-drop.
<mat-toolbar color="primary"> <span>{{ title }}</span> <span class="spacer"></span> <mat-slide-toggle (change)="onThemeChange($event)">Dark Mode</mat-slide-toggle> </mat-toolbar> <div class="task-board"> <div *ngFor="let category of categories" class="task-category"> <mat-card> <mat-card-header> <mat-card-title>{{ category.name }}</mat-card-title> </mat-card-header> <mat-card-content cdkDropList [cdkDropListData]="category.tasks" (cdkDropListDropped)="drop($event)" [cdkDropListConnectedTo]="getConnectedListIds()" class="task-list"> <mat-card *ngFor="let task of category.tasks" cdkDrag class="task-item"> {{ task.title }} <div *cdkDragPlaceholder class="drag-placeholder"></div> </mat-card> <div *ngIf="category.tasks.length === 0" class="no-tasks-message"> No tasks here! </div> </mat-card-content> <mat-card-actions> <mat-form-field appearance="fill" class="task-input"> <mat-label>Add a new task</mat-label> <input matInput [(ngModel)]="newTaskTitle" (keyup.enter)="addTask(category.id)"> </mat-form-field> <button mat-icon-button color="primary" (click)="addTask(category.id)"> <mat-icon>add_circle</mat-icon> </button> </mat-card-actions> </mat-card> </div> </div>- Use
AppComponentStyles (src/app/app.component.scss):- Style the task board using Flexbox for responsive columns.
- Style the drag-and-drop elements.
.spacer { flex: 1 1 auto; } .task-board { display: flex; flex-wrap: wrap; justify-content: center; padding: 20px; gap: 20px; } .task-category { flex: 1; min-width: 280px; max-width: 350px; mat-card { display: flex; flex-direction: column; height: 100%; } .task-list { min-height: 100px; // Ensure drop zone is visible padding: 10px; background: var(--mat-sys-surface-container-low); // Lighter background for lists border-radius: 4px; margin-top: 10px; flex-grow: 1; // Allows list to expand .task-item { padding: 10px; margin-bottom: 8px; border-radius: 4px; background: var(--mat-sys-surface-container-high); box-shadow: var(--mat-sys-elevation-2); cursor: grab; transition: background-color 0.2s ease-in-out; &:hover { background: var(--mat-sys-surface-container-highest); } } .cdk-drag-placeholder { opacity: 0; } .cdk-drag-animating { transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); } } .task-input { width: 100%; margin-top: 10px; } .no-tasks-message { color: var(--mat-sys-on-surface-variant); text-align: center; padding: 20px; } } /* Styles for the dragged item */ .cdk-drag-preview { box-sizing: border-box; border-radius: 4px; box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12); } /* Styles for the drag placeholder */ .cdk-drag-placeholder { opacity: 0; } /* Styles for a receiving list while a dragged item is over it. */ .cdk-drop-list-dragging .cdk-drag:not(.cdk-drag-placeholder) { transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); }Theming (
src/styles.scss):- Ensure your
src/styles.scsshas both a light and a dark theme defined as covered in Section 4.1. Thebodybackground/color settings are crucial. - Encourage independent problem-solving:
- Add a delete button to each task card to remove tasks.
- Implement
MatTooltipon the add task button to show “Add Task”. - Refine the responsiveness of the task board for very small screens.
- Ensure your
6. Bonus Section: Further Learning and Resources
Congratulations on making it this far! Angular Material is a vast and powerful library. To continue your journey, here are some highly recommended resources:
Recommended Online Courses/Tutorials:
- Angular University: Offers in-depth courses on Angular, including comprehensive modules on Angular Material and theming. (angular-university.io)
- Udemy/Coursera: Search for “Angular Material” courses. Look for instructors with high ratings and recent course updates.
- Netanel Basal’s NG-MOMENTUM: Excellent practical examples and insights into advanced Angular concepts, often featuring Angular Material. (ng-momentum.com)
Official Documentation:
- Angular Material Official Documentation: The definitive source for all components, APIs, styling guides, and best practices. Always refer to this first for component-specific details. (material.angular.io)
- Material Design 3 Guidelines: Understand the design principles behind Angular Material. (m3.material.io)
Blogs and Articles:
- Angular Material Blog: Keep up-to-date with official announcements and new features.
- Medium (Angular/Angular Material Tags): Many community members share valuable insights, tutorials, and solutions. Look for articles by GDEs (Google Developer Experts) in Angular.
- dev.to: A vibrant community for developers, often featuring excellent Angular and Angular Material articles.
YouTube Channels:
- Angular Firebase: Great channel for full-stack Angular applications, often incorporating Angular Material.
- Decoded Frontend (Matias Niemelä): Advanced Angular topics and performance, sometimes including Material.
- Code with Dharmen: Provides quick guides and tutorials on Angular Material, including recent version updates.
- Official Angular Channel: Features conference talks, updates, and deep dives.
Community Forums/Groups:
- Stack Overflow (Angular Material tag): Your go-to place for specific questions and troubleshooting. (stackoverflow.com/questions/tagged/angular-material)
- Angular Discord Server: A very active community for real-time discussions and help.
- Reddit (r/Angular): General Angular discussions, news, and project showcases.
Next Steps/Advanced Topics:
After mastering the content in this document, consider exploring:
- Angular CDK (Component Dev Kit): Learn how to build your own custom UI components with the same primitives used by Angular Material.
- Custom Schematics: Automate common development tasks and scaffolding for your own libraries or project structures.
- Accessibility (A11y) in Depth: Dive deeper into creating truly inclusive web applications.
- Performance Optimization in Angular: Understand change detection strategies, lazy loading, and build optimizations.
- State Management (NgRx, Signals-based solutions like NgRx SignalStore): For complex applications, robust state management is crucial.
- Server-Side Rendering (SSR) with Angular Universal: Improve initial load times and SEO for your Angular applications.
- Progressive Web Apps (PWAs): Make your Angular application installable and capable of offline use.
Keep building, keep learning, and enjoy creating amazing user experiences with Angular Material!