Angular Material Buttons: Stop Fighting the Framework
7 mins read

Angular Material Buttons: Stop Fighting the Framework

I have a love-hate relationship with Angular Material. Mostly love, but on days when I just want a button to be purple and the framework decides it wants to be strictly adhering to a theme I defined six months ago and forgot about? Yeah, that’s when the hate creeps in.

If you’ve been working with Angular for more than a week, you’ve probably pasted a <button mat-button> into your template, refreshed the browser, and stared blankly at a text-only element that looks nothing like the clickable call-to-action you promised your designer.

It happens. It’s 2025, Angular is faster than ever with Signals, standalone components are the default, yet we still trip over basic button styling. I spent about forty minutes yesterday trying to align an icon inside a stroked button before realizing I was using the wrong directive.

So, I’m writing this not as a “documentation replacement”—the official docs are fine for API references—but as a practical guide to actually using these things without pulling your hair out. Let’s talk about mat-button, its weird cousins, and how to make them look decent.

The Setup (Because You Probably Forgot an Import)

First things first. If your button looks like standard HTML Times New Roman garbage, you missed the import. Since we are all (hopefully) using standalone components now, you don’t need to bloat a shared module. Just toss it into the component imports array.

I usually grab the whole button module because I’m lazy, but you can be specific if you want to save three bytes of bundle size.

import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon'; // You'll want this too, trust me

@Component({
  selector: 'app-user-actions',
  standalone: true,
  imports: [MatButtonModule, MatIconModule],
  templateUrl: './user-actions.component.html',
  styleUrls: ['./user-actions.component.scss']
})
export class UserActionsComponent {
  // Logic goes here
}

The Button Zoo: Choosing Your Fighter

Angular Material doesn’t just give you “a button.” It gives you five different directives that fundamentally change how the element renders. Choosing the wrong one is usually why your UI looks “off.”

Here is my mental shorthand for these, because the official names are sometimes misleading:

Angular code on screen - Getting black screen with no errors on integrating with angular ...
Angular code on screen – Getting black screen with no errors on integrating with angular …
  • mat-button: The “Ghost” button. No background, no border. Use this for secondary actions like “Cancel” in a modal. If you use this for a “Submit” button, users will miss it.
  • mat-raised-button: The “Classic.” Has a shadow. It pops. It looks a bit dated in 2025 if I’m being honest, but it works for primary actions if your design language is heavy.
  • mat-flat-button: The “Modern” choice. Solid background, no shadow (until you hover/press). This is what 90% of my primary buttons look like these days.
  • mat-stroked-button: The “Outlined” one. Transparent background, visible border. Great for secondary actions that need more weight than a ghost button but less than a primary one.
  • mat-icon-button: The “Circle.” It’s literally just for icons. Do not try to put text in here. It will break. I tried it once at 2 AM. It was ugly.

Here is what they look like in code:

<!-- The "I'm just text" button -->
<button mat-button>Basic</button>

<!-- The "Click me please" button -->
<button mat-flat-button color="primary">Primary Action</button>

<!-- The "I'm an option but not the main one" button -->
<button mat-stroked-button color="accent">Secondary</button>

<!-- The "Old School" button -->
<button mat-raised-button color="warn">Delete Everything</button>

The Color Trap

You noticed I added color="primary" above. Here is the thing that trips up every junior dev I mentor: The color attribute only works if you have a theme configured.

If you haven’t set up your styles.scss with a proper Angular Material theme palette, putting color="primary" does absolutely nothing. It just defaults to black or transparent. I spent an embarrassing amount of time debugging CSS specificity once, only to realize I hadn’t actually included the theme mixin in my global styles.

Also, mat-button (the basic one) colors the text, while mat-flat-button colors the background. It sounds obvious, but when you switch from one to the other and your text suddenly disappears because it’s white-on-white, don’t panic. You just need to check your contrast ratios.

Icons Inside Buttons (Without It Looking Weird)

This is where things get messy. You want a button with a little trash can icon next to the word “Delete.”

Do not use mat-icon-button for this. That directive forces the button into a circle. Instead, you nest the icon inside a standard button. But wait—if you just drop it in, the alignment is usually off by like 2 pixels vertically. It drives me insane.

The fix? Angular Material 15+ (and definitely now in 2025) handles this much better natively, but sometimes you still need to force the icon to the right or left.

<button mat-flat-button color="warn">
  <mat-icon>delete</mat-icon>
  Delete Account
</button>

<!-- If you want the icon on the right, order matters -->
<button mat-stroked-button>
  Next Step
  <mat-icon iconPositionEnd>arrow_forward</mat-icon>
</button>

See that iconPositionEnd attribute on the second example? Use it. It handles the margin for you so you don’t have to write style="margin-left: 8px" like a caveman.

frustrated programmer at computer - Free Frustrated programmer working Image - Technology, Frustration ...
frustrated programmer at computer – Free Frustrated programmer working Image – Technology, Frustration …

Customizing (When the Boss Wants “Rounded Corners”)

Material Design 3 (M3) is pretty round by default, but sometimes you get a design mockup that demands a pill-shaped button, or maybe sharp corners, or a gradient background (god forbid).

The temptation is to use ::ng-deep to force the styles. Look, I’ve done it. You’ve done it. We all do it when a deadline is looming. But it’s bad practice because it bleeds styles globally if you aren’t careful.

A better way? Use the CSS variables (custom properties) that Angular Material exposes now. It’s cleaner and actually respects the shadow DOM boundaries.

/* In your component.scss */
.my-custom-button {
  /* Override the radius */
  --mdc-filled-button-container-shape: 24px;
  
  /* Override the height if you need a chunky button */
  --mdc-filled-button-container-height: 48px;
  
  /* Custom font weight */
  --mdc-filled-button-label-text-weight: 700;
}

Then just slap that class on your button: <button mat-flat-button class="my-custom-button">.

If you find yourself overriding every single property, you might want to ask yourself why you’re using Material in the first place. The whole point is to get a decent design system out of the box. If you fight it too hard, it fights back.

The Loading State Nightmare

One last thing that always trips people up: loading states. Angular Material buttons do not have a built-in [loading] property. I know, right? It seems like such a basic requirement.

You have to roll your own. My preferred hack—and it is a hack—is to disable the button and show a spinner either inside it or next to it.

<button mat-flat-button color="primary" [disabled]="isLoading" (click)="save()">
  @if (isLoading) {
    <mat-spinner diameter="20"></mat-spinner>
  } @else {
    <span>Save Changes</span>
  }
</button>

You’ll need some CSS to make that spinner sit nicely in the center, but it beats having the user click “Submit” fifty times because they didn’t think anything was happening.

Angular Material buttons are powerful, but they require you to play by their rules. Once you memorize the five variants and stop trying to force square pegs into round holes (or square icons into round buttons), your life gets a lot easier. Now, if I could just figure out why my datepicker is misaligned, I’d be all set.

Leave a Reply

Your email address will not be published. Required fields are marked *