From 01a0d53d692d8f1cb646335236fafe29c38f7a95 Mon Sep 17 00:00:00 2001 From: Jeremy Mowery Date: Sun, 14 Dec 2025 16:43:11 -0800 Subject: [PATCH 01/13] Initial progress button implementation --- src/dev-app/button/BUILD.bazel | 1 + src/dev-app/button/button-demo.html | 262 +++++++++++++++++++++++---- src/dev-app/button/button-demo.ts | 19 +- src/material/button/button-base.ts | 7 + src/material/button/button.html | 24 ++- src/material/button/button.scss | 175 +++++++++++++----- src/material/button/fab.scss | 84 +++++++-- src/material/button/icon-button.html | 9 + src/material/button/icon-button.scss | 41 ++++- 9 files changed, 504 insertions(+), 118 deletions(-) diff --git a/src/dev-app/button/BUILD.bazel b/src/dev-app/button/BUILD.bazel index c94c68c9b705..fe4c5d24315a 100644 --- a/src/dev-app/button/BUILD.bazel +++ b/src/dev-app/button/BUILD.bazel @@ -15,6 +15,7 @@ ng_project( "//src/material/button", "//src/material/checkbox", "//src/material/icon", + "//src/material/progress-spinner", "//src/material/tooltip", ], ) diff --git a/src/dev-app/button/button-demo.html b/src/dev-app/button/button-demo.html index 2d96f2740820..7e70494a204c 100644 --- a/src/dev-app/button/button-demo.html +++ b/src/dev-app/button/button-demo.html @@ -23,20 +23,20 @@

Buttons

[matButton]="appearance" disabled [disabledInteractive]="disabledInteractive" - [matTooltip]="tooltipText">{{appearance}} + [matTooltip]="tooltipText" + > + {{appearance}} + } - + [matTooltip]="tooltipText" + > + Search + + +
+ @for (appearance of appearances; track $index) { + + } + + + +
@for (appearance of appearances; track $index) { -

{{appearance}} Appearance

+

+ {{appearance}} Appearance +

@@ -149,7 +225,10 @@

{{appearance}} Appearance

[matButton]="appearance" disabled [disabledInteractive]="disabledInteractive" - [matTooltip]="tooltipText">disabled + [matTooltip]="tooltipText" + > + disabled + +
} @@ -180,9 +270,20 @@

Icon Buttons

matIconButton disabled [disabledInteractive]="disabledInteractive" - [matTooltip]="tooltipText"> + [matTooltip]="tooltipText" + > visibility +

Icon Button Anchors

@@ -204,7 +305,8 @@

Icon Button Anchors

matIconButton disabled [disabledInteractive]="disabledInteractive" - [matTooltip]="tooltipText"> + [matTooltip]="tooltipText" + > visibility
@@ -223,13 +325,19 @@

FABs

- +

Mini FABs

@@ -246,11 +354,22 @@

Mini FABs

+ @@ -261,37 +380,106 @@

Interaction/State

isDisabled: {{isDisabled}}

Button 1 as been clicked {{clickCounter}} times

- Allow disabled button interactivity + Allow disabled button interactivity

All disabled

+

+ All showProgress +

- - - + Button 3 + - - -
diff --git a/src/dev-app/button/button-demo.ts b/src/dev-app/button/button-demo.ts index b8b8fa4b42f3..79725c59bbb7 100644 --- a/src/dev-app/button/button-demo.ts +++ b/src/dev-app/button/button-demo.ts @@ -21,6 +21,7 @@ import { } from '@angular/material/button'; import {MatCheckbox} from '@angular/material/checkbox'; import {MatIcon} from '@angular/material/icon'; +import {MatProgressSpinner} from '@angular/material/progress-spinner'; import {MatTooltip} from '@angular/material/tooltip'; @Component({ @@ -28,23 +29,25 @@ import {MatTooltip} from '@angular/material/tooltip'; templateUrl: 'button-demo.html', styleUrl: 'button-demo.css', imports: [ - MatButton, + FormsModule, MatAnchor, - MatFabButton, + MatButton, + MatCheckbox, MatFabAnchor, - MatMiniFabButton, - MatMiniFabAnchor, - MatIconButton, - MatIconAnchor, + MatFabButton, MatIcon, + MatIconAnchor, + MatIconButton, + MatMiniFabAnchor, + MatMiniFabButton, + MatProgressSpinner, MatTooltip, - MatCheckbox, - FormsModule, ], changeDetection: ChangeDetectionStrategy.OnPush, }) export class ButtonDemo { isDisabled = false; + showProgress = false; clickCounter = 0; toggleDisable = false; tooltipText = 'This is a button tooltip!'; diff --git a/src/material/button/button-base.ts b/src/material/button/button-base.ts index 1c048f6bb456..2854c6051f55 100644 --- a/src/material/button/button-base.ts +++ b/src/material/button/button-base.ts @@ -14,6 +14,7 @@ import { ElementRef, inject, InjectionToken, + input, Input, NgZone, numberAttribute, @@ -55,6 +56,7 @@ function transformTabIndex(value: unknown): number | undefined { // wants to target all Material buttons. 'class': 'mat-mdc-button-base', '[class]': 'color ? "mat-" + color : ""', + '[class.mat-mdc-button-progress-indicator-shown]': 'showProgress()', '[attr.disabled]': '_getDisabledAttribute()', '[attr.aria-disabled]': '_getAriaDisabled()', '[attr.tabindex]': '_getTabIndex()', @@ -150,6 +152,11 @@ export class MatButtonBase implements AfterViewInit, OnDestroy { this.tabIndex = value; } + // Open question: should this be in ButtonBase or in the individual components? button.html is + // used by several things + /** Whether the button is showing a progress indicator. */ + readonly showProgress = input(false, {transform: booleanAttribute}); + constructor(...args: unknown[]); constructor() { diff --git a/src/material/button/button.html b/src/material/button/button.html index 0ddd2794ef91..d001079cb90a 100644 --- a/src/material/button/button.html +++ b/src/material/button/button.html @@ -1,16 +1,30 @@ + class="mat-mdc-button-persistent-ripple" + [class.mdc-button__ripple]="!_isFab" + [class.mdc-fab__ripple]="_isFab" +> - + - + +@if (showProgress()) { +
+ + + ... + +
+} + + ... +
+ +} + - ... -
+ } diff --git a/src/material/button/button.spec.ts b/src/material/button/button.spec.ts index 61d7addb5e1d..e6d91eaa6d89 100644 --- a/src/material/button/button.spec.ts +++ b/src/material/button/button.spec.ts @@ -130,6 +130,18 @@ describe('MatButton', () => { .withContext('Expected fab buttons to use accent palette by default') .toContain('mat-accent'); }); + + it('should show progress indicator when showProgress is true', () => { + const fixture = TestBed.createComponent(TestApp); + const fabButtonDebugEl = fixture.debugElement.query(By.css('button[mat-fab]'))!; + + expect(fabButtonDebugEl.queryAll(By.css('[progressIndicator]')).length).toBe(0); + fixture.componentInstance.showProgress = true; + fixture.changeDetectorRef.markForCheck(); + fixture.detectChanges(); + + expect(fabButtonDebugEl.queryAll(By.css('[progressIndicator]')).length).toBe(1); + }); }); describe('button[mat-mini-fab]', () => { @@ -143,6 +155,18 @@ describe('MatButton', () => { .withContext('Expected mini-fab buttons to use accent palette by default') .toContain('mat-accent'); }); + + it('should show progress indicator when showProgress is true', () => { + const fixture = TestBed.createComponent(TestApp); + const miniFabButtonDebugEl = fixture.debugElement.query(By.css('button[mat-mini-fab]'))!; + + expect(miniFabButtonDebugEl.queryAll(By.css('[progressIndicator]')).length).toBe(0); + fixture.componentInstance.showProgress = true; + fixture.changeDetectorRef.markForCheck(); + fixture.detectChanges(); + + expect(miniFabButtonDebugEl.queryAll(By.css('[progressIndicator]')).length).toBe(1); + }); }); describe('button[mat-fab] extended', () => { @@ -202,6 +226,18 @@ describe('MatButton', () => { .withContext('Expected button to be disabled') .toBeTruthy(); }); + + it('should show progress indicator when showProgress is true', () => { + const fixture = TestBed.createComponent(TestApp); + const buttonDebugEl = fixture.debugElement.query(By.css('button'))!; + + expect(buttonDebugEl.queryAll(By.css('[progressIndicator]')).length).toBe(0); + fixture.componentInstance.showProgress = true; + fixture.changeDetectorRef.markForCheck(); + fixture.detectChanges(); + + expect(buttonDebugEl.queryAll(By.css('[progressIndicator]')).length).toBe(1); + }); }); // Anchor button tests @@ -289,6 +325,18 @@ describe('MatButton', () => { expect(buttonElement.hasAttribute('tabindex')).toBe(false); }); + it('should show progress indicator when showProgress is true', () => { + const fixture = TestBed.createComponent(TestApp); + const anchorDebugEl = fixture.debugElement.query(By.css('a'))!; + + expect(anchorDebugEl.queryAll(By.css('[progressIndicator]')).length).toBe(0); + fixture.componentInstance.showProgress = true; + fixture.changeDetectorRef.markForCheck(); + fixture.detectChanges(); + + expect(anchorDebugEl.queryAll(By.css('[progressIndicator]')).length).toBe(1); + }); + describe('change detection behavior', () => { it('should not run change detection for disabled anchor but should prevent the default behavior and stop event propagation', () => { const appRef = TestBed.inject(ApplicationRef); @@ -445,16 +493,26 @@ describe('MatFabDefaultOptions', () => { template: ` + [color]="buttonColor" [disabledInteractive]="disabledInteractive" + [showProgress]="showProgress"> Link + Progress... - + - + `, @@ -469,6 +527,7 @@ class TestApp { extended = false; disabledInteractive = false; appearance: MatButtonAppearance = 'text'; + showProgress = false; increment() { this.clickCount++; From 9c24b8ed5702b97c91c2e029b2cf95c4df623815 Mon Sep 17 00:00:00 2001 From: Jeremy Mowery Date: Mon, 19 Jan 2026 12:25:18 -0800 Subject: [PATCH 06/13] Remove tab index handling in favor of documentation --- src/material/button/button-base.ts | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/material/button/button-base.ts b/src/material/button/button-base.ts index 1729f6a5fd5a..dffa0b8d1a25 100644 --- a/src/material/button/button-base.ts +++ b/src/material/button/button-base.ts @@ -20,8 +20,6 @@ import { numberAttribute, OnDestroy, Renderer2, - contentChild, - afterRenderEffect, } from '@angular/core'; import {_animationsDisabled, _StructuralStylesLoader, MatRippleLoader, ThemePalette} from '../core'; import {_CdkPrivateStyleLoader} from '@angular/cdk/private'; @@ -159,9 +157,6 @@ export class MatButtonBase implements AfterViewInit, OnDestroy { /** Whether the button is showing a progress indicator. */ readonly showProgress = input(false, {transform: booleanAttribute}); - private readonly progressIndicatorElementRef = - contentChild>('[progressIndicator]'); - constructor(...args: unknown[]); constructor() { @@ -172,16 +167,6 @@ export class MatButtonBase implements AfterViewInit, OnDestroy { this.disabledInteractive = this._config?.disabledInteractive ?? false; this.color = this._config?.color ?? null; this._rippleLoader?.configureRipple(element, {className: 'mat-mdc-button-ripple'}); - - // Ensure that the loading indicator is not in a focus order, otherwise we end up with - // an interactable element inside the interactable button. - afterRenderEffect(() => { - const element = this.progressIndicatorElementRef()?.nativeElement; - if (!element) { - return; - } - this._renderer.setAttribute(element, 'tabindex', ''); - }); } ngAfterViewInit() { From ec94163a708039555dba5b0cae215e15ef00ff3b Mon Sep 17 00:00:00 2001 From: Jeremy Mowery Date: Mon, 19 Jan 2026 12:27:57 -0800 Subject: [PATCH 07/13] Check for the class in the tests --- src/material/button/button.spec.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/material/button/button.spec.ts b/src/material/button/button.spec.ts index e6d91eaa6d89..b3c1a15b2e18 100644 --- a/src/material/button/button.spec.ts +++ b/src/material/button/button.spec.ts @@ -136,11 +136,17 @@ describe('MatButton', () => { const fabButtonDebugEl = fixture.debugElement.query(By.css('button[mat-fab]'))!; expect(fabButtonDebugEl.queryAll(By.css('[progressIndicator]')).length).toBe(0); + expect(fabButtonDebugEl.nativeElement.classList).not.toContain( + 'mat-mdc-button-progress-indicator-shown', + ); fixture.componentInstance.showProgress = true; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); expect(fabButtonDebugEl.queryAll(By.css('[progressIndicator]')).length).toBe(1); + expect(fabButtonDebugEl.nativeElement.classList).toContain( + 'mat-mdc-button-progress-indicator-shown', + ); }); }); @@ -161,11 +167,17 @@ describe('MatButton', () => { const miniFabButtonDebugEl = fixture.debugElement.query(By.css('button[mat-mini-fab]'))!; expect(miniFabButtonDebugEl.queryAll(By.css('[progressIndicator]')).length).toBe(0); + expect(miniFabButtonDebugEl.nativeElement.classList).not.toContain( + 'mat-mdc-button-progress-indicator-shown', + ); fixture.componentInstance.showProgress = true; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); expect(miniFabButtonDebugEl.queryAll(By.css('[progressIndicator]')).length).toBe(1); + expect(miniFabButtonDebugEl.nativeElement.classList).toContain( + 'mat-mdc-button-progress-indicator-shown', + ); }); }); @@ -232,11 +244,17 @@ describe('MatButton', () => { const buttonDebugEl = fixture.debugElement.query(By.css('button'))!; expect(buttonDebugEl.queryAll(By.css('[progressIndicator]')).length).toBe(0); + expect(buttonDebugEl.nativeElement.classList).not.toContain( + 'mat-mdc-button-progress-indicator-shown', + ); fixture.componentInstance.showProgress = true; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); expect(buttonDebugEl.queryAll(By.css('[progressIndicator]')).length).toBe(1); + expect(buttonDebugEl.nativeElement.classList).toContain( + 'mat-mdc-button-progress-indicator-shown', + ); }); }); @@ -330,11 +348,17 @@ describe('MatButton', () => { const anchorDebugEl = fixture.debugElement.query(By.css('a'))!; expect(anchorDebugEl.queryAll(By.css('[progressIndicator]')).length).toBe(0); + expect(anchorDebugEl.nativeElement.classList).not.toContain( + 'mat-mdc-button-progress-indicator-shown', + ); fixture.componentInstance.showProgress = true; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); expect(anchorDebugEl.queryAll(By.css('[progressIndicator]')).length).toBe(1); + expect(anchorDebugEl.nativeElement.classList).toContain( + 'mat-mdc-button-progress-indicator-shown', + ); }); describe('change detection behavior', () => { From e9a764d0680d1188ba089104e4651d35295a37ad Mon Sep 17 00:00:00 2001 From: Jeremy Mowery Date: Mon, 19 Jan 2026 12:31:39 -0800 Subject: [PATCH 08/13] Cleanup comments --- src/material/button/button-base.ts | 2 -- src/material/button/icon-button.html | 5 +---- src/material/button/icon-button.scss | 1 - 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/material/button/button-base.ts b/src/material/button/button-base.ts index dffa0b8d1a25..2c48ba787740 100644 --- a/src/material/button/button-base.ts +++ b/src/material/button/button-base.ts @@ -152,8 +152,6 @@ export class MatButtonBase implements AfterViewInit, OnDestroy { this.tabIndex = value; } - // Open question: should this be in ButtonBase or in the individual components? button.html is - // used by several things /** Whether the button is showing a progress indicator. */ readonly showProgress = input(false, {transform: booleanAttribute}); diff --git a/src/material/button/icon-button.html b/src/material/button/icon-button.html index 357077529a40..762a605197a0 100644 --- a/src/material/button/icon-button.html +++ b/src/material/button/icon-button.html @@ -4,10 +4,7 @@ @if (showProgress()) {
- - - ... - +
} diff --git a/src/material/button/icon-button.scss b/src/material/button/icon-button.scss index 1c9289321dbf..07b5fc2af148 100644 --- a/src/material/button/icon-button.scss +++ b/src/material/button/icon-button.scss @@ -109,7 +109,6 @@ $fallbacks: m3-icon-button.get-tokens(); } .mat-mdc-button-progress-indicator-shown { - // Open question: is this sufficient for icon button? .material-icons, mat-icon { visibility: hidden; From e266bed460ed1f5ab0daff2f6104d5d39a91f9a5 Mon Sep 17 00:00:00 2001 From: Jeremy Mowery Date: Sat, 24 Jan 2026 16:55:52 -0800 Subject: [PATCH 09/13] Revert --- src/material/button/button.scss | 183 ++++++++------------------------ 1 file changed, 42 insertions(+), 141 deletions(-) diff --git a/src/material/button/button.scss b/src/material/button/button.scss index d6a719922d45..aca34d9ab351 100644 --- a/src/material/button/button.scss +++ b/src/material/button/button.scss @@ -4,7 +4,6 @@ @use '../core/tokens/token-utils'; @use '../core/focus-indicators/private' as focus-indicators-private; @use './m3-button'; -@use '../progress-spinner/progress-spinner-theme'; $fallbacks: m3-button.get-tokens(); @@ -79,8 +78,7 @@ $fallbacks: m3-button.get-tokens(); text-transform: token-utils.slot(button-text-label-text-transform, $fallbacks); font-weight: token-utils.slot(button-text-label-text-weight, $fallbacks); - &, - .mdc-button__ripple { + &, .mdc-button__ripple { border-radius: token-utils.slot(button-text-container-shape, $fallbacks); } @@ -95,26 +93,15 @@ $fallbacks: m3-button.get-tokens(); } @include button-base.mat-private-button-horizontal-layout( - button-text-icon-spacing, - button-text-icon-offset, - button-text-with-icon-horizontal-padding, - $fallbacks - ); + button-text-icon-spacing, button-text-icon-offset, + button-text-with-icon-horizontal-padding, $fallbacks); @include button-base.mat-private-button-ripple( - button-text-ripple-color, - button-text-state-layer-color, - button-text-disabled-state-layer-color, - button-text-hover-state-layer-opacity, - button-text-focus-state-layer-opacity, - button-text-pressed-state-layer-opacity, - $fallbacks - ); - @include button-base.mat-private-button-touch-target( - false, - button-text-touch-target-size, - button-text-touch-target-display, - $fallbacks - ); + button-text-ripple-color, button-text-state-layer-color, + button-text-disabled-state-layer-color, + button-text-hover-state-layer-opacity, button-text-focus-state-layer-opacity, + button-text-pressed-state-layer-opacity, $fallbacks); + @include button-base.mat-private-button-touch-target(false, + button-text-touch-target-size, button-text-touch-target-display, $fallbacks); } .mat-mdc-unelevated-button { @@ -128,48 +115,24 @@ $fallbacks: m3-button.get-tokens(); padding: 0 #{token-utils.slot(button-filled-horizontal-padding, $fallbacks, true)}; @include button-base.mat-private-button-horizontal-layout( - button-filled-icon-spacing, - button-filled-icon-offset, - null, - $fallbacks - ); + button-filled-icon-spacing, button-filled-icon-offset, null, $fallbacks); @include button-base.mat-private-button-ripple( - button-filled-ripple-color, - button-filled-state-layer-color, - button-filled-disabled-state-layer-color, - button-filled-hover-state-layer-opacity, - button-filled-focus-state-layer-opacity, - button-filled-pressed-state-layer-opacity, - $fallbacks - ); - @include button-base.mat-private-button-touch-target( - false, - button-filled-touch-target-size, - button-filled-touch-target-display, - $fallbacks - ); + button-filled-ripple-color, button-filled-state-layer-color, + button-filled-disabled-state-layer-color, + button-filled-hover-state-layer-opacity, button-filled-focus-state-layer-opacity, + button-filled-pressed-state-layer-opacity, $fallbacks); + @include button-base.mat-private-button-touch-target(false, + button-filled-touch-target-size, button-filled-touch-target-display, $fallbacks); &:not(:disabled) { color: token-utils.slot(button-filled-label-text-color, $fallbacks); background-color: token-utils.slot(button-filled-container-color, $fallbacks); } - &, - .mdc-button__ripple { + &, .mdc-button__ripple { border-radius: token-utils.slot(button-filled-container-shape, $fallbacks); } - .mat-mdc-button-progress-indicator-container { - @include progress-spinner-theme.overrides( - ( - active-indicator-color: token-utils.slot( - button-filled-progress-active-indicator-color, - $fallbacks - ), - ) - ); - } - // We need to re-apply the disabled tokens since MDC uses // `:disabled` which doesn't apply to anchors. @include button-base.mat-private-button-disabled { @@ -190,34 +153,21 @@ $fallbacks: m3-button.get-tokens(); padding: 0 #{token-utils.slot(button-protected-horizontal-padding, $fallbacks, true)}; @include button-base.mat-private-button-horizontal-layout( - button-protected-icon-spacing, - button-protected-icon-offset, - null, - $fallbacks - ); + button-protected-icon-spacing, button-protected-icon-offset, null, $fallbacks); @include button-base.mat-private-button-ripple( - button-protected-ripple-color, - button-protected-state-layer-color, - button-protected-disabled-state-layer-color, - button-protected-hover-state-layer-opacity, - button-protected-focus-state-layer-opacity, - button-protected-pressed-state-layer-opacity, - $fallbacks - ); - @include button-base.mat-private-button-touch-target( - false, - button-protected-touch-target-size, - button-protected-touch-target-display, - $fallbacks - ); + button-protected-ripple-color, button-protected-state-layer-color, + button-protected-disabled-state-layer-color, + button-protected-hover-state-layer-opacity, button-protected-focus-state-layer-opacity, + button-protected-pressed-state-layer-opacity, $fallbacks); + @include button-base.mat-private-button-touch-target(false, + button-protected-touch-target-size, button-protected-touch-target-display, $fallbacks); &:not(:disabled) { color: token-utils.slot(button-protected-label-text-color, $fallbacks); background-color: token-utils.slot(button-protected-container-color, $fallbacks); } - &, - .mdc-button__ripple { + &, .mdc-button__ripple { border-radius: token-utils.slot(button-protected-container-shape, $fallbacks); } @@ -231,8 +181,7 @@ $fallbacks: m3-button.get-tokens(); box-shadow: token-utils.slot(button-protected-focus-container-elevation-shadow, $fallbacks); } - &:active, - &:focus:active { + &:active, &:focus:active { box-shadow: token-utils.slot(button-protected-pressed-container-elevation-shadow, $fallbacks); } @@ -244,9 +193,7 @@ $fallbacks: m3-button.get-tokens(); &.mat-mdc-button-disabled { box-shadow: token-utils.slot( - button-protected-disabled-container-elevation-shadow, - $fallbacks - ); + button-protected-disabled-container-elevation-shadow, $fallbacks); } } } @@ -265,26 +212,14 @@ $fallbacks: m3-button.get-tokens(); padding: 0 #{token-utils.slot(button-outlined-horizontal-padding, $fallbacks, true)}; @include button-base.mat-private-button-horizontal-layout( - button-outlined-icon-spacing, - button-outlined-icon-offset, - null, - $fallbacks - ); + button-outlined-icon-spacing, button-outlined-icon-offset, null, $fallbacks); @include button-base.mat-private-button-ripple( - button-outlined-ripple-color, - button-outlined-state-layer-color, - button-outlined-disabled-state-layer-color, - button-outlined-hover-state-layer-opacity, - button-outlined-focus-state-layer-opacity, - button-outlined-pressed-state-layer-opacity, - $fallbacks - ); - @include button-base.mat-private-button-touch-target( - false, - button-outlined-touch-target-size, - button-outlined-touch-target-display, - $fallbacks - ); + button-outlined-ripple-color, button-outlined-state-layer-color, + button-outlined-disabled-state-layer-color, + button-outlined-hover-state-layer-opacity, button-outlined-focus-state-layer-opacity, + button-outlined-pressed-state-layer-opacity, $fallbacks); + @include button-base.mat-private-button-touch-target(false, + button-outlined-touch-target-size, button-outlined-touch-target-display, $fallbacks); &:not(:disabled) { color: token-utils.slot(button-outlined-label-text-color, $fallbacks); @@ -314,8 +249,7 @@ $fallbacks: m3-button.get-tokens(); background-color: token-utils.slot(button-tonal-container-color, $fallbacks); } - &, - .mdc-button__ripple { + &, .mdc-button__ripple { border-radius: token-utils.slot(button-tonal-container-shape, $fallbacks); } @@ -327,26 +261,14 @@ $fallbacks: m3-button.get-tokens(); } @include button-base.mat-private-button-horizontal-layout( - button-tonal-icon-spacing, - button-tonal-icon-offset, - null, - $fallbacks - ); + button-tonal-icon-spacing, button-tonal-icon-offset, null, $fallbacks); @include button-base.mat-private-button-ripple( - button-tonal-ripple-color, - button-tonal-state-layer-color, - button-tonal-disabled-state-layer-color, - button-tonal-hover-state-layer-opacity, - button-tonal-focus-state-layer-opacity, - button-tonal-pressed-state-layer-opacity, - $fallbacks - ); - @include button-base.mat-private-button-touch-target( - false, - button-tonal-touch-target-size, - button-tonal-touch-target-display, - $fallbacks - ); + button-tonal-ripple-color, button-tonal-state-layer-color, + button-tonal-disabled-state-layer-color, + button-tonal-hover-state-layer-opacity, button-tonal-focus-state-layer-opacity, + button-tonal-pressed-state-layer-opacity, $fallbacks); + @include button-base.mat-private-button-touch-target(false, + button-tonal-touch-target-size, button-tonal-touch-target-display, $fallbacks); } .mat-mdc-button, @@ -405,24 +327,3 @@ $fallbacks: m3-button.get-tokens(); $offset: calc(#{$border-width} + 3px); margin: calc(#{$offset} * -1); } - -.mat-mdc-button-progress-indicator-container { - position: absolute; - inset-inline-start: 0; - inset-block-start: 0; - display: flex; - align-items: center; - justify-content: center; - width: 100%; - height: 100%; - box-sizing: border-box; -} - -.mat-mdc-button-progress-indicator-shown { - .material-icons, - mat-icon, - [matButtonIcon], - .mdc-button__label { - visibility: hidden; - } -} From bf9af5950cd9af550dc13549e7ee7dbe51e9213f Mon Sep 17 00:00:00 2001 From: Jeremy Mowery Date: Sat, 24 Jan 2026 17:01:00 -0800 Subject: [PATCH 10/13] Add back the changed styles --- src/material/button/button.scss | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/material/button/button.scss b/src/material/button/button.scss index aca34d9ab351..8513604ed70e 100644 --- a/src/material/button/button.scss +++ b/src/material/button/button.scss @@ -133,6 +133,12 @@ $fallbacks: m3-button.get-tokens(); border-radius: token-utils.slot(button-filled-container-shape, $fallbacks); } + .mat-mdc-button-progress-indicator-container { + @include progress-spinner-theme.overrides((active-indicator-color: token-utils.slot(button-filled-progress-active-indicator-color, + $fallbacks ), + )); + } + // We need to re-apply the disabled tokens since MDC uses // `:disabled` which doesn't apply to anchors. @include button-base.mat-private-button-disabled { @@ -327,3 +333,24 @@ $fallbacks: m3-button.get-tokens(); $offset: calc(#{$border-width} + 3px); margin: calc(#{$offset} * -1); } + +.mat-mdc-button-progress-indicator-container { + position: absolute; + inset-inline-start: 0; + inset-block-start: 0; + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; + box-sizing: border-box; +} + +.mat-mdc-button-progress-indicator-shown { + .material-icons, + mat-icon, + [matButtonIcon], + .mdc-button__label { + visibility: hidden; + } +} \ No newline at end of file From 8e81fa1ab0918e7592a1e48b0f356b8839a9fe1c Mon Sep 17 00:00:00 2001 From: Jeremy Mowery Date: Sat, 24 Jan 2026 17:10:57 -0800 Subject: [PATCH 11/13] Revert the changes we don't want --- src/material/button/_m3-button.scss | 110 ++++++++++------------------ 1 file changed, 39 insertions(+), 71 deletions(-) diff --git a/src/material/button/_m3-button.scss b/src/material/button/_m3-button.scss index 1a2996d62394..a388b17234cc 100644 --- a/src/material/button/_m3-button.scss +++ b/src/material/button/_m3-button.scss @@ -5,6 +5,7 @@ @use '../core/style/elevation'; @use '../core/theming/theming'; + /// Generates custom tokens for the button. @function get-tokens($theme: m3.$sys-theme, $color-variant: null) { $system: m3-utils.get-system($theme); @@ -51,110 +52,77 @@ ), color: ( button-filled-container-color: map.get($system, primary), - button-filled-disabled-container-color: m3-utils.color-with-opacity( - map.get($system, on-surface), - 12% - ), - button-filled-disabled-label-text-color: m3-utils.color-with-opacity( - map.get($system, on-surface), - 38% - ), + button-filled-disabled-container-color: + m3-utils.color-with-opacity(map.get($system, on-surface), 12%), + button-filled-disabled-label-text-color: + m3-utils.color-with-opacity(map.get($system, on-surface), 38%), button-filled-disabled-state-layer-color: map.get($system, on-surface-variant), button-filled-focus-state-layer-opacity: map.get($system, focus-state-layer-opacity), button-filled-hover-state-layer-opacity: map.get($system, hover-state-layer-opacity), button-filled-label-text-color: map.get($system, on-primary), - button-filled-pressed-state-layer-opacity: map.get($system, pressed-state-layer-opacity), + button-filled-pressed-state-layer-opacity:map.get($system, pressed-state-layer-opacity), button-filled-ripple-color: m3-utils.color-with-opacity( - map.get($system, on-primary), - map.get($system, pressed-state-layer-opacity) - ), + map.get($system, on-primary), map.get($system, pressed-state-layer-opacity)), button-filled-progress-active-indicator-color: map.get($system, on-primary), button-filled-state-layer-color: map.get($system, on-primary), - button-outlined-disabled-label-text-color: m3-utils.color-with-opacity( - map.get($system, on-surface), - 38% - ), - button-outlined-disabled-outline-color: m3-utils.color-with-opacity( - map.get($system, on-surface), - 12% - ), + button-outlined-disabled-label-text-color: + m3-utils.color-with-opacity(map.get($system, on-surface), 38%), + button-outlined-disabled-outline-color: + m3-utils.color-with-opacity(map.get($system, on-surface), 12%), button-outlined-disabled-state-layer-color: map.get($system, on-surface-variant), button-outlined-focus-state-layer-opacity: map.get($system, focus-state-layer-opacity), button-outlined-hover-state-layer-opacity: map.get($system, hover-state-layer-opacity), button-outlined-label-text-color: map.get($system, primary), button-outlined-outline-color: map.get($system, outline), - button-outlined-pressed-state-layer-opacity: map.get($system, pressed-state-layer-opacity), + button-outlined-pressed-state-layer-opacity:map.get($system, pressed-state-layer-opacity), button-outlined-ripple-color: m3-utils.color-with-opacity( - map.get($system, primary), - map.get($system, pressed-state-layer-opacity) - ), + map.get($system, primary), map.get($system, pressed-state-layer-opacity)), button-outlined-state-layer-color: map.get($system, primary), button-protected-container-color: map.get($system, surface), - button-protected-container-elevation-shadow: elevation.get-box-shadow( - map.get($system, level1) - ), - button-protected-disabled-container-color: m3-utils.color-with-opacity( - map.get($system, on-surface), - 12% - ), - button-protected-disabled-container-elevation-shadow: elevation.get-box-shadow( - map.get($system, level0) - ), - button-protected-disabled-label-text-color: m3-utils.color-with-opacity( - map.get($system, on-surface), - 38% - ), + button-protected-container-elevation-shadow: + elevation.get-box-shadow(map.get($system, level1)), + button-protected-disabled-container-color: + m3-utils.color-with-opacity(map.get($system, on-surface), 12%), + button-protected-disabled-container-elevation-shadow: + elevation.get-box-shadow(map.get($system, level0)), + button-protected-disabled-label-text-color: + m3-utils.color-with-opacity(map.get($system, on-surface), 38%), button-protected-disabled-state-layer-color: map.get($system, on-surface-variant), - button-protected-focus-container-elevation-shadow: elevation.get-box-shadow( - map.get($system, level1) - ), + button-protected-focus-container-elevation-shadow: + elevation.get-box-shadow(map.get($system, level1)), button-protected-focus-state-layer-opacity: map.get($system, focus-state-layer-opacity), - button-protected-hover-container-elevation-shadow: elevation.get-box-shadow( - map.get($system, level2) - ), + button-protected-hover-container-elevation-shadow: + elevation.get-box-shadow(map.get($system, level2)), button-protected-hover-state-layer-opacity: map.get($system, hover-state-layer-opacity), button-protected-label-text-color: map.get($system, primary), - button-protected-pressed-container-elevation-shadow: elevation.get-box-shadow( - map.get($system, level1) - ), - button-protected-pressed-state-layer-opacity: map.get($system, pressed-state-layer-opacity), + button-protected-pressed-container-elevation-shadow: + elevation.get-box-shadow(map.get($system, level1)), + button-protected-pressed-state-layer-opacity:map.get($system, pressed-state-layer-opacity), button-protected-ripple-color: m3-utils.color-with-opacity( - map.get($system, primary), - map.get($system, pressed-state-layer-opacity) - ), + map.get($system, primary), map.get($system, pressed-state-layer-opacity)), button-protected-state-layer-color: map.get($system, primary), - button-text-disabled-label-text-color: m3-utils.color-with-opacity( - map.get($system, on-surface), - 38% - ), + button-text-disabled-label-text-color: + m3-utils.color-with-opacity(map.get($system, on-surface), 38%), button-text-disabled-state-layer-color: map.get($system, on-surface-variant), button-text-focus-state-layer-opacity: map.get($system, focus-state-layer-opacity), button-text-hover-state-layer-opacity: map.get($system, hover-state-layer-opacity), button-text-label-text-color: map.get($system, primary), button-text-pressed-state-layer-opacity: map.get($system, pressed-state-layer-opacity), button-text-ripple-color: m3-utils.color-with-opacity( - map.get($system, primary), - map.get($system, pressed-state-layer-opacity) - ), + map.get($system, primary), map.get($system, pressed-state-layer-opacity)), button-text-state-layer-color: map.get($system, primary), button-tonal-container-color: map.get($system, secondary-container), - button-tonal-disabled-container-color: m3-utils.color-with-opacity( - map.get($system, on-surface), - 12% - ), - button-tonal-disabled-label-text-color: m3-utils.color-with-opacity( - map.get($system, on-surface), - 38% - ), + button-tonal-disabled-container-color: + m3-utils.color-with-opacity(map.get($system, on-surface), 12%), + button-tonal-disabled-label-text-color: + m3-utils.color-with-opacity(map.get($system, on-surface), 38%), button-tonal-disabled-state-layer-color: map.get($system, on-surface-variant), button-tonal-focus-state-layer-opacity: map.get($system, focus-state-layer-opacity), button-tonal-hover-state-layer-opacity: map.get($system, hover-state-layer-opacity), button-tonal-label-text-color: map.get($system, on-secondary-container), button-tonal-pressed-state-layer-opacity: map.get($system, pressed-state-layer-opacity), button-tonal-ripple-color: m3-utils.color-with-opacity( - map.get($system, on-secondary-container), - map.get($system, pressed-state-layer-opacity) - ), + map.get($system, on-secondary-container), map.get($system, pressed-state-layer-opacity)), button-tonal-state-layer-color: map.get($system, on-secondary-container), ), typography: ( @@ -179,7 +147,7 @@ button-tonal-label-text-tracking: map.get($system, label-large-tracking), button-tonal-label-text-weight: map.get($system, label-large-weight), ), - density: get-density-tokens(map.get($system, density-scale)) + density: get-density-tokens(map.get($system, density-scale)), ); } @@ -198,6 +166,6 @@ button-text-touch-target-display: list.nth((block, block, none, none), $index), button-text-container-height: list.nth((40px, 36px, 32px, 28px), $index), button-tonal-container-height: list.nth((40px, 36px, 32px, 28px), $index), - button-tonal-touch-target-display: list.nth((block, block, none, none), $index) + button-tonal-touch-target-display: list.nth((block, block, none, none), $index), ); } From 38b43977cc675c6022cb9d92be08c1acc405e171 Mon Sep 17 00:00:00 2001 From: Jeremy Mowery Date: Sat, 24 Jan 2026 17:13:58 -0800 Subject: [PATCH 12/13] revert the fab stuff --- src/material/button/fab.scss | 64 ++++++++++-------------------------- 1 file changed, 18 insertions(+), 46 deletions(-) diff --git a/src/material/button/fab.scss b/src/material/button/fab.scss index 1dc54a2fd7a9..81d5aa5caed9 100644 --- a/src/material/button/fab.scss +++ b/src/material/button/fab.scss @@ -24,9 +24,7 @@ $fallbacks: m3-fab.get-tokens(); -moz-appearance: none; -webkit-appearance: none; overflow: visible; - transition: - box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1), - opacity 15ms linear 30ms, + transition: box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1), opacity 15ms linear 30ms, transform 270ms 0ms cubic-bezier(0, 0, 0.2, 1); flex-shrink: 0; // Prevent the button from shrinking since it's always supposed to be a circle. @@ -57,8 +55,7 @@ $fallbacks: m3-fab.get-tokens(); border: 0; } - &:active, - &:focus { + &:active, &:focus { outline: none; } @@ -77,8 +74,7 @@ $fallbacks: m3-fab.get-tokens(); // However, Angular Material expects a `mat-icon` instead. The following // mixin will style the icons appropriately. // stylelint-disable-next-line selector-class-pattern - .mat-icon, - .material-icons { + .mat-icon, .material-icons { transition: transform 180ms 90ms cubic-bezier(0, 0, 0.2, 1); fill: currentColor; will-change: transform; @@ -93,8 +89,7 @@ $fallbacks: m3-fab.get-tokens(); @include button-base.mat-private-button-disabled { // Necessary for interactive disabled buttons. - &, - &:focus { + &, &:focus { box-shadow: none; } } @@ -116,8 +111,7 @@ $fallbacks: m3-fab.get-tokens(); box-shadow: token-utils.slot(fab-focus-container-elevation-shadow, $fallbacks); } - &:active, - &:focus:active { + &:active, &:focus:active { box-shadow: token-utils.slot(fab-pressed-container-elevation-shadow, $fallbacks); } @@ -126,21 +120,11 @@ $fallbacks: m3-fab.get-tokens(); background-color: token-utils.slot(fab-disabled-state-container-color, $fallbacks); } - @include button-base.mat-private-button-touch-target( - true, - fab-touch-target-size, - fab-touch-target-display, - $fallbacks - ); - @include button-base.mat-private-button-ripple( - fab-ripple-color, - fab-state-layer-color, - fab-disabled-state-layer-color, - fab-hover-state-layer-opacity, - fab-focus-state-layer-opacity, - fab-pressed-state-layer-opacity, - $fallbacks - ); + @include button-base.mat-private-button-touch-target(true, fab-touch-target-size, + fab-touch-target-display, $fallbacks); + @include button-base.mat-private-button-ripple(fab-ripple-color, fab-state-layer-color, + fab-disabled-state-layer-color, fab-hover-state-layer-opacity, fab-focus-state-layer-opacity, + fab-pressed-state-layer-opacity, $fallbacks); } .mat-mdc-mini-fab { @@ -161,8 +145,7 @@ $fallbacks: m3-fab.get-tokens(); box-shadow: token-utils.slot(fab-small-focus-container-elevation-shadow, $fallbacks); } - &:active, - &:focus:active { + &:active, &:focus:active { box-shadow: token-utils.slot(fab-small-pressed-container-elevation-shadow, $fallbacks); } @@ -171,21 +154,12 @@ $fallbacks: m3-fab.get-tokens(); background-color: token-utils.slot(fab-small-disabled-state-container-color, $fallbacks); } - @include button-base.mat-private-button-touch-target( - true, - fab-small-touch-target-size, - fab-small-touch-target-display, - $fallbacks - ); - @include button-base.mat-private-button-ripple( - fab-small-ripple-color, + @include button-base.mat-private-button-touch-target(true, + fab-small-touch-target-size, fab-small-touch-target-display, $fallbacks); + @include button-base.mat-private-button-ripple(fab-small-ripple-color, fab-small-state-layer-color, - fab-small-disabled-state-layer-color, - fab-small-hover-state-layer-opacity, - fab-small-focus-state-layer-opacity, - fab-small-pressed-state-layer-opacity, - $fallbacks - ); + fab-small-disabled-state-layer-color, fab-small-hover-state-layer-opacity, + fab-small-focus-state-layer-opacity, fab-small-pressed-state-layer-opacity, $fallbacks); } .mat-mdc-extended-fab { @@ -216,15 +190,13 @@ $fallbacks: m3-fab.get-tokens(); box-shadow: token-utils.slot(fab-extended-focus-container-elevation-shadow, $fallbacks); } - &:active, - &:focus:active { + &:active, &:focus:active { box-shadow: token-utils.slot(fab-extended-pressed-container-elevation-shadow, $fallbacks); } @include button-base.mat-private-button-disabled { // Necessary for interactive disabled buttons. - &, - &:focus { + &, &:focus { box-shadow: none; } } From a1f74f49e6729c0f2f1a53b4cdee9c8559af3082 Mon Sep 17 00:00:00 2001 From: Jeremy Mowery Date: Sat, 24 Jan 2026 17:16:07 -0800 Subject: [PATCH 13/13] Revert icon button stuff --- src/material/button/icon-button.scss | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/src/material/button/icon-button.scss b/src/material/button/icon-button.scss index 07b5fc2af148..4f7d4b88c56c 100644 --- a/src/material/button/icon-button.scss +++ b/src/material/button/icon-button.scss @@ -48,25 +48,19 @@ $fallbacks: m3-icon-button.get-tokens(); @include button-base.mat-private-button-interactive(); @include button-base.mat-private-button-ripple( - icon-button-ripple-color, - icon-button-state-layer-color, - icon-button-disabled-state-layer-color, - icon-button-hover-state-layer-opacity, - icon-button-focus-state-layer-opacity, - icon-button-pressed-state-layer-opacity, - $fallbacks - ); - @include button-base.mat-private-button-touch-target( - true, - icon-button-touch-target-size, - icon-button-touch-target-display, - $fallbacks - ); + icon-button-ripple-color, icon-button-state-layer-color, + icon-button-disabled-state-layer-color, + icon-button-hover-state-layer-opacity, icon-button-focus-state-layer-opacity, + icon-button-pressed-state-layer-opacity, $fallbacks); + @include button-base.mat-private-button-touch-target(true, + icon-button-touch-target-size, icon-button-touch-target-display, $fallbacks); @include private.private-animation-noop(); @include button-base.mat-private-button-disabled { color: token-utils.slot(icon-button-disabled-icon-color, $fallbacks); } +; + img, svg:not(.mat-mdc-button-progress-indicator-container *) { width: token-utils.slot(icon-button-icon-size, $fallbacks);