Angular Material
Angular Material is the official component library for Angular, built and maintained by Google. It implements the Material Design specification as a suite of high-quality, accessible, and themeable UI components — buttons, form fields, dialogs, tables, date pickers, and dozens more. Because it is tightly integrated with the framework and ships with built-in keyboard navigation, ARIA support, and a flexible theming system, it lets you ship a consistent, professional-looking UI without reinventing common widgets.
Installing Angular Material
The Angular CLI provides a schematic that wires everything up for you: it adds the package, configures a theme, sets up typography, and imports the animations provider. Run it from the root of an existing Angular project.
ng add @angular/material
The schematic prompts you to pick a prebuilt theme (or a custom one), to set up global typography, and to include browser animations. It then updates angular.json, index.html, and your application config automatically.
Output:
? Choose a prebuilt theme name, or "custom" for a custom theme: Azure/Blue
? Set up global Angular Material typography styles? Yes
? Include the Angular animations module? Include and enable animations
UPDATE src/index.html
UPDATE src/styles.scss
UPDATE angular.json
UPDATE src/app/app.config.ts
Angular Material 18+ uses Material Design 3 (M3) tokens by default. The
ng addschematic generates an M3 theme, so colors are driven by design tokens rather than the older M2 palettes.
Importing components
Angular Material components are standalone, so you import only the modules you use directly into a component’s imports array — no shared NgModule required. This keeps bundles lean because unused components are tree-shaken away.
import { Component, signal } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatCardModule } from '@angular/material/card';
@Component({
selector: 'app-welcome',
standalone: true,
imports: [MatButtonModule, MatIconModule, MatCardModule],
template: `
<mat-card>
<mat-card-header>
<mat-card-title>Welcome</mat-card-title>
</mat-card-header>
<mat-card-content>
<p>You clicked {{ count() }} times.</p>
</mat-card-content>
<mat-card-actions>
<button mat-raised-button color="primary" (click)="increment()">
<mat-icon>add</mat-icon>
Click me
</button>
</mat-card-actions>
</mat-card>
`,
})
export class WelcomeComponent {
count = signal(0);
increment() {
this.count.update((n) => n + 1);
}
}
The mat-icon element above relies on the Material Symbols/Icons font. Add it to index.html (the ng add schematic does this for you):
<link
href="https://fonts.googleapis.com/icon?family=Material+Icons"
rel="stylesheet"
/>
Form fields and reactive forms
Form controls are where Angular Material shines. mat-form-field wraps inputs, selects, and date pickers, giving you labels, hints, and error states out of the box. It integrates cleanly with reactive forms and the new control flow syntax.
import { Component, inject } from '@angular/core';
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatButtonModule } from '@angular/material/button';
@Component({
selector: 'app-signup',
standalone: true,
imports: [
ReactiveFormsModule,
MatFormFieldModule,
MatInputModule,
MatButtonModule,
],
template: `
<form [formGroup]="form" (ngSubmit)="submit()">
<mat-form-field appearance="outline">
<mat-label>Email</mat-label>
<input matInput formControlName="email" type="email" />
@if (form.controls.email.hasError('required')) {
<mat-error>Email is required</mat-error>
} @else if (form.controls.email.hasError('email')) {
<mat-error>Enter a valid email</mat-error>
}
</mat-form-field>
<button mat-raised-button color="primary" [disabled]="form.invalid">
Sign up
</button>
</form>
`,
})
export class SignupComponent {
private fb = inject(FormBuilder);
form = this.fb.nonNullable.group({
email: ['', [Validators.required, Validators.email]],
});
submit() {
if (this.form.valid) {
console.log(this.form.getRawValue());
}
}
}
Theming with Material Design 3
Theming is handled through SCSS. You define a theme with mat.define-theme, choosing color, typography, and density, then apply it globally. Because M3 themes are token-based, switching to dark mode is a matter of overriding the color scheme.
@use '@angular/material' as mat;
$light-theme: mat.define-theme((
color: (
theme-type: light,
primary: mat.$azure-palette,
),
density: (scale: 0),
));
html {
@include mat.all-component-themes($light-theme);
}
.dark-mode {
$dark-theme: mat.define-theme((
color: (theme-type: dark, primary: mat.$azure-palette),
));
@include mat.all-component-colors($dark-theme);
}
Toggling a .dark-mode class on the document root swaps the entire palette instantly.
Common components reference
| Module | Selector | Use case |
|---|---|---|
MatButtonModule | button mat-button | Buttons (basic, raised, FAB) |
MatFormFieldModule | mat-form-field | Wrapper for inputs and selects |
MatTableModule | mat-table | Data tables with sorting/paging |
MatDialogModule | MatDialog service | Modal dialogs |
MatSnackBarModule | MatSnackBar service | Toast notifications |
MatDatepickerModule | mat-datepicker | Date selection |
MatMenuModule | mat-menu | Dropdown menus |
Opening a dialog
Services like MatDialog and MatSnackBar are injected, not declared in templates. Inject them with inject() and call them imperatively.
import { Component, inject } from '@angular/core';
import { MatDialog, MatDialogModule } from '@angular/material/dialog';
import { MatButtonModule } from '@angular/material/button';
import { ConfirmDialogComponent } from './confirm-dialog.component';
@Component({
selector: 'app-toolbar',
standalone: true,
imports: [MatButtonModule, MatDialogModule],
template: `<button mat-button (click)="confirm()">Delete</button>`,
})
export class ToolbarComponent {
private dialog = inject(MatDialog);
confirm() {
const ref = this.dialog.open(ConfirmDialogComponent, {
data: { message: 'Delete this item?' },
});
ref.afterClosed().subscribe((result) => {
if (result) console.log('confirmed');
});
}
}
Best practices
- Import only the specific component modules a component uses; relying on standalone imports keeps bundles small and tree-shakeable.
- Prefer the
outlineorfillappearance onmat-form-fieldconsistently across the app for a coherent look — mixing them looks unpolished. - Drive all theming through SCSS tokens and
mat.define-themerather than hardcoding hex colors in component styles, so dark mode and rebrands stay trivial. - Lean on the built-in accessibility: pair every
mat-form-fieldwith amat-label, and give icon-only buttons anaria-label. - Use the
MatDialogandMatSnackBarservices viainject()instead of declaring custom overlays — they handle focus trapping and ARIA for you. - Keep the Material version aligned with your Angular version; upgrade both together with
ng updateto pick up migration schematics.