Angular Material Installation: A Practical, Modern Setup Guide

I see the same pattern every time a team starts a new Angular UI: we burn days debating component libraries, then we hand‑roll buttons, inputs, and dialogs anyway. The result is inconsistent spacing, accessibility gaps, and subtle theming drift across features. When I want a reliable baseline that feels familiar to users and is maintainable for engineers, I reach for Angular Material. It gives you a curated set of components, built‑in accessibility, and a theming system that lets you keep product identity without inventing everything from scratch. In this guide I’ll show you exactly how I install Angular Material and wire it into a new project, then validate the setup with a real component. I’ll also point out the mistakes I see most often, how to avoid them, and where Material fits (and where it does not). By the end, you’ll have a clean, working setup plus the mental model to make smart choices as your UI grows.

Why I start with Angular Material

When I evaluate a UI library, I care about three things: how quickly a teammate can build a consistent UI, whether the components are accessible by default, and how painful it is to maintain a theme over time. Material checks those boxes. It’s opinionated enough to keep a product cohesive, but flexible enough that you can brand it without fighting the library every time marketing updates colors.

Material also integrates tightly with Angular’s patterns. That matters more than most teams realize: it means the components behave predictably with Angular forms, change detection, and the CLI tooling. In practice, that reduces “mystery bugs” where a component looks fine but doesn’t emit a value in a form or doesn’t play well with async data. If you’ve lived through those issues, you know how expensive they are.

I install Material early in a project because it shapes how the rest of the UI is built. It becomes the standard for spacing, typography, and UI states. That consistency has a compounding effect: onboarding new engineers is easier, QA becomes more predictable, and design reviews go faster because you’re comparing a known baseline instead of arguing about every pixel.

Prerequisites I verify before I install

I keep the prerequisites simple. You need Node.js and npm, and you need the Angular CLI installed globally if you’re creating a new project from scratch. The CLI is your entry point for generating a project and for adding Material in a single command that wires everything up.

Here’s the CLI install command I use:

npm install -g @angular/cli

If you already have the CLI, I still run ng version to make sure it’s functional on your machine and to check for obvious environment issues. If your team uses a version manager like Volta or nvm, use that, but I avoid getting version‑specific here because teams differ.

One practical tip: if you’re on Windows or you work across machines, keep a small note in your repo’s README with the Node range you expect. It saves a surprising amount of time when new developers pull the project and the install fails with cryptic errors.

Creating a project the way I prefer to start

If you’re starting a new app, create it with the CLI. You can name it anything, but choose a real project name instead of a placeholder so you don’t forget to update it later.

ng new store-dashboard

The CLI will ask about routing and stylesheet format. If you’re planning to use Angular Material, I usually pick SCSS because it makes theming and custom styles easier to manage. That’s not mandatory, but it’s my default.

Once the project is generated, I go into the directory:

cd store-dashboard

At this point you have a clean Angular project that compiles. I run it once to verify I’m not chasing unrelated environment problems while I install Material:

ng serve

If the app runs, I stop the server and move on to the Material setup. This may seem redundant, but it gives you a clean baseline if anything goes wrong later.

Installing Angular Material with ng add

I always use the CLI integration because it installs the packages and configures the app for you. That means fewer manual steps and fewer missed changes.

ng add @angular/material

During the install, you’ll be prompted for:

  • A prebuilt theme
  • Whether to set up Angular animations
  • Whether to add Material typography styles

I usually say yes to animations and typography, and I pick a theme that’s “close enough” to what the product needs. You can override it later. The key is to get a working baseline quickly.

After that, the CLI updates your project with new dependencies and tweaks the configuration. Under the hood it adds the Material package, brings in the CDK, and adjusts your styles to include the theme.

If you want to see what changed, open angular.json and look for the updated styles entry. A typical setup looks like this (your project name will differ):

{

"$schema": "./node_modules/@angular/cli/lib/config/schema.json",

"version": 1,

"newProjectRoot": "projects",

"projects": {

"store-dashboard": {

"projectType": "application",

"schematics": {},

"root": "",

"sourceRoot": "src",

"prefix": "app",

"architect": {

"build": {

"builder": "@angular-devkit/build-angular:browser",

"options": {

"outputPath": "dist/store-dashboard",

"index": "src/index.html",

"main": "src/main.ts",

"polyfills": [

"zone.js"

],

"tsConfig": "tsconfig.app.json",

"assets": [

"src/favicon.ico",

"src/assets"

],

"styles": [

"src/styles.css"

],

"scripts": []

},

"configurations": {

"production": {

"budgets": [

{

"type": "initial",

"maximumWarning": "500kb",

"maximumError": "1mb"

},

{

"type": "anyComponentStyle",

"maximumWarning": "2kb",

"maximumError": "4kb"

}

],

"outputHashing": "all"

},

"development": {

"buildOptimizer": false,

"optimization": false,

"vendorChunk": true,

"extractLicenses": false,

"sourceMap": true,

"namedChunks": true

}

},

"defaultConfiguration": "production"

},

"serve": {

"builder": "@angular-devkit/build-angular:dev-server",

"configurations": {

"production": {

"browserTarget": "store-dashboard:build:production"

},

"development": {

"browserTarget": "store-dashboard:build:development"

}

},

"defaultConfiguration": "development"

},

"extract-i18n": {

"builder": "@angular-devkit/build-angular:extract-i18n",

"options": {

"browserTarget": "store-dashboard:build"

}

},

"test": {

"builder": "@angular-devkit/build-angular:karma",

"options": {

"polyfills": [

"zone.js",

"zone.js/testing"

],

"tsConfig": "tsconfig.spec.json",

"assets": [

"src/favicon.ico",

"src/assets"

],

"styles": [

"src/styles.css"

],

"scripts": []

}

}

}

}

}

}

Don’t copy this verbatim unless you’re creating a new project. The point is that the CLI handles these changes, and I trust it. You can inspect the configuration if you want to understand how everything is wired, but I don’t edit this file unless I have a specific reason.

Adding your first Material component (proof it works)

I like to validate the install with a small UI that exercises a few common pieces: a toolbar, a card, and a button. This confirms that styles, icons, and animations are all loading.

If your project uses standalone components (a common setup in recent Angular releases), I create a simple component and import Material modules directly. Here’s an example app.component.ts you can use as a starting point:

import { Component } from ‘@angular/core‘;

import { MatButtonModule } from ‘@angular/material/button‘;

import { MatCardModule } from ‘@angular/material/card‘;

import { MatToolbarModule } from ‘@angular/material/toolbar‘;

@Component({

selector: ‘app-root‘,

standalone: true,

imports: [MatToolbarModule, MatCardModule, MatButtonModule],

templateUrl: ‘./app.component.html‘,

styleUrls: [‘./app.component.scss‘]

})

export class AppComponent {

title = ‘Store Dashboard‘;

}

And a simple app.component.html:


{{ title }}

Welcome back

Your metrics are ready. Review the latest orders and inventory changes.

And a small app.component.scss so the layout doesn’t look cramped:

.page {

padding: 24px;

}

.hero {

max-width: 520px;

}

Run ng serve again and you should see a toolbar, a card, and a styled button. If the styles look like plain HTML, you likely missed the theme stylesheet or the Material modules aren’t imported in the component.

Theme setup choices I make early

Material supports theming, and the CLI gives you a prebuilt theme during installation. That’s great, but I usually plan for customization. Here’s the approach I use:

  • Keep the prebuilt theme at first for speed.
  • Move to a custom theme only when design settles.
  • Centralize typography and spacing rules so teams don’t invent new styles for each feature.

If you use SCSS, you can define a custom theme in styles.scss later and remove the prebuilt theme import. This is a natural evolution step. I avoid doing it on day one unless design is already locked, because it’s too easy to spend hours tweaking a palette when you should be building features.

I also keep typography on by default. It ensures headings, paragraphs, and UI labels use a consistent set of styles. Otherwise, you end up with accidental font sizes and inconsistent line heights.

Common mistakes I see and how I avoid them

The most frequent issues I debug fall into a few buckets. Here’s what I watch for:

  • Forgetting to import Material modules in a standalone component. The template uses , but nothing renders with Material styles. Fix: import the needed modules in the component’s imports array.
  • Installing Material without enabling animations. Some components rely on the animation module. Fix: accept the animations prompt, or add it in your app configuration.
  • Missing the theme stylesheet. Without it, everything looks like plain HTML. Fix: ensure the theme CSS is in your global styles or in angular.json.
  • Mixing CSS and SCSS without a plan. If you choose SCSS, stick with it so theme overrides are consistent. If you choose CSS, keep overrides minimal.
  • Assuming icons are automatic. Material icons are a separate resource. If you use , make sure you load the font or configure SVG icons.

When I see a UI bug, I ask myself one simple question: “Is this a component problem or a configuration problem?” Most of the time it’s configuration. That mental check saves me from digging into component code unnecessarily.

When I use Material and when I avoid it

I like Material for admin panels, internal tools, and dashboards where consistency and speed beat bespoke UI. It’s also great for early product stages: you can ship faster and validate your product without spending weeks on custom components.

I avoid Material when a product’s UI must be highly unique or brand‑centric. If the design language is far from Material, you can fight the defaults more than you build. In those cases, a lighter library or a custom component system may be better, especially if design already has a strong component specification.

Here’s a quick comparison of the two approaches:

Approach

Traditional custom UI

Modern Material‑based UI —

— Setup time

Days to weeks for base components

Hours to a day Accessibility

Must be built and tested manually

Defaults are strong, still validate Consistency

Depends on discipline

Enforced by components Theming

Full control, heavy effort

Good control, balanced effort Risk

Higher risk of UI regressions

Lower risk, predictable behavior

If your team can accept Material’s aesthetic as a baseline, I recommend it. You’ll move faster and you’ll spend less time fixing UI edge cases.

Performance considerations I keep in mind

Material is not a “free” library. It adds components and styles, so you should be deliberate about how you import it. I only import the modules I need rather than pulling in everything. That keeps the bundle slimmer.

In real projects I typically see a few tens of milliseconds difference in initial render between a lean UI and a full suite of components. This isn’t a crisis, but it’s worth measuring. If the app is large or the target devices are low‑end, be conservative about how many components you include and avoid heavy visual effects until you need them.

I also watch for slow form rendering when a page has many complex inputs. Material components are richer than plain HTML inputs, so a page with hundreds of them can take longer to paint. When that happens, I split the view into tabs or progressive sections so the browser isn’t asked to render everything at once.

Modern workflow notes for 2026

Today’s Angular workflow is more ergonomic than it used to be, and I take advantage of that. I use standalone components by default because they reduce module boilerplate. I keep the CLI in my loop for tasks like generating components and wiring up libraries. And I use AI‑assisted tools to draft component markup, but I always review the output for accessibility and design consistency.

Here’s how I like to collaborate with AI tools without losing control:

  • Ask for a basic template and then tighten it to match Material patterns
  • Validate that inputs and buttons use the correct directives (matInput, mat-raised-button)
  • Ensure labels, error states, and helper text are in place
  • Run the app and visually inspect the layout before shipping

The result is faster UI assembly without letting the UI quality slip.

Troubleshooting checklist I share with teams

When someone on my team says “Material isn’t working,” I have them go through this

Compatibility and version alignment I check first

Angular Material is tightly coupled to Angular’s major version. If your Angular version and Material version drift, you’ll see weird errors, missing styles, or runtime exceptions that make no sense. My first move is to check ng version and compare Angular with Material and the CDK package versions in package.json.

Here’s the mental model I keep: if Angular is on major version X, Angular Material and CDK should also be on major version X. Patch and minor mismatches are usually fine. Major mismatches are not.

When I inherit a repo, I also check whether @angular/material, @angular/cdk, and @angular/animations are all present and aligned. It’s easy to end up with Material installed but animations missing if a previous setup step was skipped or a package was removed by accident.

Installing Material in an existing repo (not just new apps)

In mature codebases, I’m careful about what the CLI changes. ng add is still the best option, but I watch the diffs closely. Here’s a minimal install sequence I use when I want to be explicit:

ng add @angular/material

If I need full control, I do a manual install:

npm install @angular/material @angular/cdk @angular/animations

Then I ensure the animations module is included in app configuration. With standalone bootstrapping, I add provideAnimations() to the providers. With NgModules, I import BrowserAnimationsModule into the root module. I keep this checklist short and deterministic so I can repeat it across projects.

Manual setup is also useful when a team prefers to skip the CLI prompts and handle theming explicitly. That said, I still recommend using the CLI unless you have a clear reason not to.

Standalone vs NgModule setup (the two patterns I see most)

Teams run into confusion because Angular now supports standalone components and the older NgModule pattern. Material works with both, but the wiring is different.

For standalone apps, I import Material modules directly into each component or into a shared component that I re‑export. Example approach:

  • AppComponent imports only what it uses.
  • Feature components import their own Material modules.
  • A ui-material.ts file exports a common set if multiple components share the same base modules.

For NgModule apps, I use a MaterialModule pattern only when it helps reduce repetition. Otherwise I import modules directly into the feature module so the dependencies are easy to read.

A quick comparison:

Pattern

Standalone

NgModule —

— Where imports live

Component imports

Module imports Reuse approach

Shared export file

Shared MaterialModule Common pitfall

Forgetting module in a component

Forgetting module in a feature module

If your app is mixed, I keep it consistent per feature. Mixing patterns inside a single feature tends to confuse new teammates.

Icons: the missing piece many teams forget

Material icons are not bundled automatically. If I see empty squares where icons should be, I check this first. I typically load the font in index.html:


Then in templates I use:

search

If the project is strict about external resources or needs offline support, I switch to SVG icons and register them with MatIconRegistry. The key is to choose one strategy and document it. I’ve seen teams mix font and SVG approaches and end up with inconsistent icon weights and sizes.

Animations: why I rarely disable them entirely

Material animations help with perceived quality, but they also have functional value. Dialogs, snackbars, and menus use animations for focus management and visual cues. If animations are disabled, these components can feel abrupt or even visually broken.

I only disable animations in two cases:

  • Automated tests where animations cause flaky timing.
  • Very constrained environments where performance is critical.

When I do disable them, I use NoopAnimationsModule or provideNoopAnimations() and I document the decision. It’s easy to forget that a disabled animations module is causing a layout to “feel wrong.”

Forms: the first real production use case

Most teams start with forms. That’s where Material can either feel magical or frustrating depending on setup. I typically introduce MatFormFieldModule and MatInputModule early and then build a small, real form to validate behavior.

A minimal example in a standalone component:

import { Component } from ‘@angular/core‘;

import { ReactiveFormsModule, FormControl, 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-login‘,

standalone: true,

imports: [ReactiveFormsModule, MatFormFieldModule, MatInputModule, MatButtonModule],

templateUrl: ‘./login.component.html‘

})

export class LoginComponent {

email = new FormControl(‘‘, { nonNullable: true, validators: [Validators.required, Validators.email] });

password = new FormControl(‘‘, { nonNullable: true, validators: [Validators.required] });

}

And a template:



Email

Enter a valid email
Email is required


Password

Password is required



This little form validates several things at once: form field styling, label floating, validation messaging, disabled state, and button theming. If something is broken here, it almost always traces back to missing imports or a theme issue.

Density and spacing: the subtle consistency lever

Material supports density configurations that control spacing and component height. Most teams ignore this, but I find it useful when you’re building dashboard‑style UIs with data tables and you want a tighter layout.

My strategy:

  • Start with the default density.
  • Switch to a compact density only if the UI truly needs it.
  • Document the density choice so designers know what to expect.

If you compress density too early, you’ll fight label collisions and error message overlap. I prefer to let the layout breathe until the product demands otherwise.

Theming deep dive: how I transition from prebuilt to custom

When the product’s branding is stable, I move from a prebuilt theme to a custom SCSS theme. I do this in three steps:

  • Define brand colors and typography tokens.
  • Create a theme file with Material theming functions.
  • Replace the prebuilt theme import in global styles.

Here’s a simplified example of a custom theme setup:

@use ‘@angular/material‘ as mat;

$primary: mat.define-palette(mat.$indigo-palette, 600);

$accent: mat.define-palette(mat.$orange-palette, 500);

$warn: mat.define-palette(mat.$red-palette);

$theme: mat.define-light-theme((

color: (

primary: $primary,

accent: $accent,

warn: $warn,

),

typography: mat.define-typography-config(),

density: 0,

));

@include mat.core();

@include mat.all-component-themes($theme);

I keep this in styles.scss and delete the prebuilt theme import. The main advantage is explicit control. I can audit the exact colors and typography used across the app, and designers can align their specs to these tokens.

Dark mode: a practical, controlled rollout

Dark mode is expected now, but I keep it simple. I define two themes and toggle them with a class on body or the app root. I avoid toggling themes at the component level because it fragments the visual system.

A common pattern is:

  • .theme-light uses a light theme definition.
  • .theme-dark uses a dark theme definition.
  • The app shell toggles the class based on user preference.

I also make sure to test contrast in dark mode. Material’s defaults are solid, but custom brand palettes can fail contrast checks if I’m not careful.

Alternative installation paths (when the CLI can’t be used)

Sometimes the CLI is blocked in enterprise environments, or a repo is locked down. In those cases, I do a manual install, and I follow a short checklist:

  • Install @angular/material, @angular/cdk, and @angular/animations.
  • Add BrowserAnimationsModule or provideAnimations().
  • Add a theme stylesheet to global styles.
  • Import required Material modules where they’re used.

This manual flow is slower, but it’s reliable when the CLI isn’t available. I keep it documented in the repo so new teammates can follow the same steps.

Component import strategy: avoiding bundle bloat

I avoid MatNativeDateModule unless I actually use the datepicker. I import MatDialogModule only when I need dialogs, not just because “we might.” The pattern is simple: each feature imports the specific modules it needs and no more.

If I see performance regressions, I check for a shared Material module that imports everything. Those “kitchen sink” modules make it easy to balloon the bundle without noticing.

Practical scenario: a mini dashboard layout

Here’s a small setup I use to validate layout and component interplay. It includes toolbar, cards, list, and a data table skeleton. It’s not for production, but it surfaces most configuration issues:


Store Dashboard



Revenue $124,830 Orders 1,248

And basic styles:

.spacer { flex: 1 1 auto; }

.grid {

display: grid;

grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));

gap: 16px;

padding: 24px;

}

If these basic layouts look right, most configuration problems are already solved. I also check keyboard navigation on the toolbar button to confirm focus styles work.

Edge cases I’ve learned to handle early

Material is stable, but there are a few edge cases that surprise teams:

  • SSR and hydration: Material components rely on browser APIs. If your app uses SSR, test components like dialogs and menus early so you can handle any hydration quirks.
  • Theme duplication: Importing the theme in multiple SCSS files can lead to duplicated CSS and bloated bundles. I keep theme imports in one global place.
  • Global style overrides: Aggressive global CSS (like button { all: unset; }) can break Material. I keep global resets minimal or scoped.
  • Z‑index conflicts: Overlays use a standard z‑index. If you have custom modals or headers, ensure they don’t conflict with Material overlay containers.

These aren’t daily issues, but they’re worth knowing so you can debug faster.

Production readiness: the checklist I use before launch

I treat Material setup as part of production readiness. Here’s the list I run before shipping:

  • Confirm only needed Material modules are imported.
  • Verify theme CSS is included once and only once.
  • Test at least one dialog, one menu, and one form field with keyboard navigation.
  • Validate contrast and typography consistency in both light and dark modes.
  • Run a quick performance check on a low‑end device or throttled network.

This catches issues that rarely show up in local dev but become obvious after launch.

Upgrades and maintenance: how I keep it smooth

Material updates are generally safe, but I don’t treat them as trivial. My upgrade flow is:

  • Update Angular and Material together.
  • Run ng update if available.
  • Scan the changelog for breaking changes, especially in theming.
  • Check theme compilation and typography output.
  • Test a few key components: form fields, dialogs, and tables.

The biggest risks I see are in theming changes and deprecations. A quick smoke test usually surfaces them.

Performance considerations I add as a baseline policy

Beyond component imports, I also keep an eye on two patterns:

  • Dialog storms: Stacking multiple dialogs or opening dialogs frequently can feel heavy. I prefer a single dialog with stepper content where possible.
  • Large tables: MatTable is powerful but can be heavy with large datasets. I paginate by default and consider virtual scrolling for huge lists.

These aren’t installation issues, but they matter because they shape the UI architecture from the start.

Alternative approaches I still consider

Material is not the only answer. If I need extreme branding control, I consider a headless component approach or a design‑system‑first stack. If I need very lightweight UI, I might choose a minimal set of custom components and rely on native HTML for most interactions.

That said, I still default to Material for most internal and B2B applications because the speed and accessibility baseline are hard to beat.

Troubleshooting checklist I share with teams (expanded)

Here’s the full checklist I use when something looks wrong:

  • Confirm @angular/material, @angular/cdk, and @angular/animations versions match the Angular major version.
  • Confirm animations are enabled (BrowserAnimationsModule or provideAnimations()).
  • Confirm the theme stylesheet is loaded once in global styles.
  • Confirm each component imports the specific Material modules it uses.
  • Confirm Material icons are loaded if is used.
  • Check for global CSS that overrides Material styles.
  • Check for duplicated theme imports that inflate CSS.
  • Restart the dev server to ensure configuration changes are picked up.
  • Inspect the DOM for Material classes to verify the directive is active.
  • Reduce the UI to a minimal test component to isolate whether it’s configuration or markup.

This checklist is intentionally short and practical. It helps new teammates resolve 80% of issues without diving into framework internals.

Final take: why this approach scales

Angular Material isn’t perfect, but it’s the most reliable baseline I’ve found for Angular teams that want a cohesive UI without paying the full cost of a custom design system. The install flow is straightforward, the configuration is predictable, and the components are mature enough to handle real‑world needs.

If you follow the steps in this guide, you’ll end up with more than a working install. You’ll have a clear mental model for how Material integrates with Angular, how theming evolves over time, and how to troubleshoot the usual pitfalls quickly. That’s the difference between “it works on my machine” and a UI foundation your whole team can build on with confidence.

Scroll to Top