Skip to content
Angular ng libraries 4 min read

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 add schematic 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

ModuleSelectorUse case
MatButtonModulebutton mat-buttonButtons (basic, raised, FAB)
MatFormFieldModulemat-form-fieldWrapper for inputs and selects
MatTableModulemat-tableData tables with sorting/paging
MatDialogModuleMatDialog serviceModal dialogs
MatSnackBarModuleMatSnackBar serviceToast notifications
MatDatepickerModulemat-datepickerDate selection
MatMenuModulemat-menuDropdown 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 outline or fill appearance on mat-form-field consistently across the app for a coherent look — mixing them looks unpolished.
  • Drive all theming through SCSS tokens and mat.define-theme rather than hardcoding hex colors in component styles, so dark mode and rebrands stay trivial.
  • Lean on the built-in accessibility: pair every mat-form-field with a mat-label, and give icon-only buttons an aria-label.
  • Use the MatDialog and MatSnackBar services via inject() 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 update to pick up migration schematics.
Last updated June 14, 2026
Was this helpful?