Skip to content

Commit

Permalink
feat(material/button): add the ability to interact with disabled butt…
Browse files Browse the repository at this point in the history
…ons (#28242)

Native disabled buttons don't allow focus and prevent the button from dispatching events. In some cases this can be problematic, because it prevents the app from showing to the user why the button is disabled.

These changes introduce a new opt-in input that will style buttons as disabled and set `aria-disabled="true"`, but not set the native `disabled` attribute, allowing them to be interactive.
  • Loading branch information
crisbeto committed Dec 11, 2023
1 parent 771f1df commit f23d8c1
Show file tree
Hide file tree
Showing 22 changed files with 399 additions and 70 deletions.
@@ -0,0 +1,3 @@
button {
margin-right: 8px;
}
@@ -0,0 +1,10 @@
<button
mat-raised-button
disabled
disabledInteractive
matTooltip="This is a tooltip!">Disabled button allowing interactivity</button>

<button
mat-raised-button
disabled
matTooltip="This is a tooltip!">Default disabled button</button>
@@ -0,0 +1,15 @@
import {Component} from '@angular/core';
import {MatButton} from '@angular/material/button';
import {MatTooltip} from '@angular/material/tooltip';

/**
* @title Interactive disabled buttons
*/
@Component({
selector: 'button-disabled-interactive-example',
templateUrl: 'button-disabled-interactive-example.html',
styleUrls: ['button-disabled-interactive-example.css'],
standalone: true,
imports: [MatButton, MatTooltip],
})
export class ButtonDisabledInteractiveExample {}
1 change: 1 addition & 0 deletions src/components-examples/material/button/index.ts
@@ -1,3 +1,4 @@
export {ButtonOverviewExample} from './button-overview/button-overview-example';
export {ButtonTypesExample} from './button-types/button-types-example';
export {ButtonDisabledInteractiveExample} from './button-disabled-interactive/button-disabled-interactive-example';
export {ButtonHarnessExample} from './button-harness/button-harness-example';
2 changes: 2 additions & 0 deletions src/dev-app/button/BUILD.bazel
Expand Up @@ -11,7 +11,9 @@ ng_module(
],
deps = [
"//src/material/button",
"//src/material/checkbox",
"//src/material/icon",
"//src/material/tooltip",
],
)

Expand Down
169 changes: 141 additions & 28 deletions src/dev-app/button/button-demo.html
Expand Up @@ -19,18 +19,52 @@ <h4 class="demo-section-header">Buttons</h4>
</button>
</section>
<section>
<button mat-button disabled>normal</button>
<button mat-raised-button disabled>raised</button>
<button mat-stroked-button disabled>stroked</button>
<button mat-flat-button disabled>flat</button>
<button mat-fab disabled>
<button
mat-button
disabled
[disabledInteractive]="disabledInteractive"
[matTooltip]="tooltipText">normal</button>
<button
mat-raised-button
disabled
[disabledInteractive]="disabledInteractive"
[matTooltip]="tooltipText">raised</button>
<button
mat-stroked-button
disabled
[disabledInteractive]="disabledInteractive"
[matTooltip]="tooltipText">stroked</button>
<button
mat-flat-button
disabled
[disabledInteractive]="disabledInteractive"
[matTooltip]="tooltipText">flat</button>
<button
mat-fab
disabled
[disabledInteractive]="disabledInteractive"
[matTooltip]="tooltipText">
<mat-icon>check</mat-icon>
</button>
<button mat-mini-fab disabled>
<button
mat-mini-fab
disabled
[disabledInteractive]="disabledInteractive"
[matTooltip]="tooltipText">
<mat-icon>check</mat-icon>
</button>
<button mat-fab extended disabled>Search</button>
<button mat-fab extended disabled>
<button
mat-fab
extended
disabled
[disabledInteractive]="disabledInteractive"
[matTooltip]="tooltipText">Search</button>
<button
mat-fab
extended
disabled
[disabledInteractive]="disabledInteractive"
[matTooltip]="tooltipText">
<mat-icon>check</mat-icon>
Search
<mat-icon iconPositionEnd>check</mat-icon>
Expand All @@ -57,18 +91,61 @@ <h4 class="demo-section-header">Anchors</h4>
</a>
</section>
<section>
<a href="//www.google.com" disabled mat-button color="primary">SEARCH</a>
<a href="//www.google.com" disabled mat-raised-button>SEARCH</a>
<a href="//www.google.com" disabled mat-stroked-button color="primary">SEARCH</a>
<a href="//www.google.com" disabled mat-flat-button>SEARCH</a>
<a href="//www.google.com" disabled mat-fab>
<a
href="//www.google.com"
disabled
[disabledInteractive]="disabledInteractive"
[matTooltip]="tooltipText"
mat-button
color="primary">SEARCH</a>
<a
href="//www.google.com"
disabled
[disabledInteractive]="disabledInteractive"
[matTooltip]="tooltipText"
mat-raised-button>SEARCH</a>
<a
href="//www.google.com"
disabled
[disabledInteractive]="disabledInteractive"
[matTooltip]="tooltipText"
mat-stroked-button color="primary">SEARCH</a>
<a
href="//www.google.com"
disabled
[disabledInteractive]="disabledInteractive"
[matTooltip]="tooltipText"
mat-flat-button>SEARCH</a>
<a
href="//www.google.com"
disabled
[disabledInteractive]="disabledInteractive"
[matTooltip]="tooltipText"
mat-fab>
<mat-icon>check</mat-icon>
</a>
<a href="//www.google.com" disabled mat-mini-fab>
<a
href="//www.google.com"
disabled
[disabledInteractive]="disabledInteractive"
[matTooltip]="tooltipText"
mat-mini-fab>
<mat-icon>check</mat-icon>
</a>
<a href="//www.google.com" disabled mat-fab extended>Search</a>
<a href="//www.google.com" disabled mat-fab extended>
<a
href="//www.google.com"
disabled
[disabledInteractive]="disabledInteractive"
[matTooltip]="tooltipText"
mat-fab
extended>Search</a>
<a
href="//www.google.com"
disabled
[disabledInteractive]="disabledInteractive"
[matTooltip]="tooltipText"
mat-fab
extended>
<mat-icon>check</mat-icon>
Search
<mat-icon iconPositionEnd>check</mat-icon>
Expand All @@ -81,7 +158,11 @@ <h4 class="demo-section-header">Text Buttons [mat-button]</h4>
<button mat-button color="primary">primary</button>
<button mat-button color="accent">accent</button>
<button mat-button color="warn">warn</button>
<button mat-button disabled>disabled</button>
<button
mat-button
disabled
[disabledInteractive]="disabledInteractive"
[matTooltip]="tooltipText">disabled</button>
<button mat-button>
<mat-icon>home</mat-icon>
with icons
Expand All @@ -95,7 +176,11 @@ <h4 class="demo-section-header">Raised Buttons [mat-raised-button]</h4>
<button mat-raised-button color="primary">primary</button>
<button mat-raised-button color="accent">accent</button>
<button mat-raised-button color="warn">warn</button>
<button mat-raised-button disabled>disabled</button>
<button
mat-raised-button
disabled
[disabledInteractive]="disabledInteractive"
[matTooltip]="tooltipText">disabled</button>
<button mat-raised-button>
<mat-icon>home</mat-icon>
with icons
Expand All @@ -109,7 +194,11 @@ <h4 class="demo-section-header">Stroked Buttons [mat-stroked-button]</h4>
<button mat-stroked-button color="primary">primary</button>
<button mat-stroked-button color="accent">accent</button>
<button mat-stroked-button color="warn">warn</button>
<button mat-stroked-button disabled>disabled</button>
<button
mat-stroked-button
disabled
[disabledInteractive]="disabledInteractive"
[matTooltip]="tooltipText">disabled</button>
<button mat-stroked-button>
<mat-icon>home</mat-icon>
with icons
Expand All @@ -123,7 +212,11 @@ <h4 class="demo-section-header">Flat Buttons [mat-flat-button]</h4>
<button mat-flat-button color="primary">primary</button>
<button mat-flat-button color="accent">accent</button>
<button mat-flat-button color="warn">warn</button>
<button mat-flat-button disabled>disabled</button>
<button
mat-flat-button
disabled
[disabledInteractive]="disabledInteractive"
[matTooltip]="tooltipText">disabled</button>
<button mat-flat-button>
<mat-icon>home</mat-icon>
with icons
Expand All @@ -145,12 +238,16 @@ <h4 class="demo-section-header"> Icon Buttons [mat-icon-button]</h4>
<button mat-icon-button color="warn">
<mat-icon>trending_up</mat-icon>
</button>
<button mat-icon-button disabled>
<button
mat-icon-button
disabled
[disabledInteractive]="disabledInteractive"
[matTooltip]="tooltipText">
<mat-icon>visibility</mat-icon>
</button>
</section>

<h4 class="demo-section-header"> Icon Button Anchors [mat-icon-button]</h4>
<h4 class="demo-section-header">Icon Button Anchors [mat-icon-button]</h4>
<section>
<a href="#" mat-icon-button>
<mat-icon>cached</mat-icon>
Expand All @@ -164,7 +261,12 @@ <h4 class="demo-section-header"> Icon Button Anchors [mat-icon-button]</h4>
<a href="#" mat-icon-button color="warn">
<mat-icon>trending_up</mat-icon>
</a>
<a href="#" mat-icon-button disabled>
<a
href="#"
mat-icon-button
disabled
[disabledInteractive]="disabledInteractive"
[matTooltip]="tooltipText">
<mat-icon>visibility</mat-icon>
</a>
</section>
Expand All @@ -183,7 +285,11 @@ <h4 class="demo-section-header">Fab Buttons [mat-fab]</h4>
<button mat-fab color="warn">
<mat-icon>home</mat-icon>
</button>
<button mat-fab disabled>
<button
mat-fab
disabled
[disabledInteractive]="disabledInteractive"
[matTooltip]="tooltipText">
<mat-icon>favorite</mat-icon>
</button>
</section>
Expand All @@ -202,7 +308,11 @@ <h4 class="demo-section-header"> Mini Fab Buttons [mat-mini-fab]</h4>
<button mat-mini-fab color="warn">
<mat-icon>filter_list</mat-icon>
</button>
<button mat-mini-fab disabled>
<button
mat-mini-fab
disabled
[disabledInteractive]="disabledInteractive"
[matTooltip]="tooltipText">
<mat-icon>home</mat-icon>
</button>
</section>
Expand All @@ -212,9 +322,12 @@ <h4 class="demo-section-header">Interaction/State</h4>
<div>
<p>isDisabled: {{isDisabled}}</p>
<p>Button 1 as been clicked {{clickCounter}} times</p>
<button mat-flat-button (click)="isDisabled=!isDisabled">
{{isDisabled ? 'Enable All' : 'Disable All'}}
</button>
<p>
<mat-checkbox [(ngModel)]="disabledInteractive">Allow disabled button interactivity</mat-checkbox>
</p>
<p>
<mat-checkbox [(ngModel)]="isDisabled">All disabled</mat-checkbox>
</p>
<button mat-flat-button (click)="button1.focus()">Focus 1</button>
<button mat-flat-button (click)="button2.focus()">Focus 2</button>
<button mat-flat-button (click)="button3.focus()">Focus 3</button>
Expand Down
39 changes: 33 additions & 6 deletions src/dev-app/button/button-demo.ts
Expand Up @@ -7,18 +7,45 @@
*/

import {Component} from '@angular/core';
import {MatButtonModule} from '@angular/material/button';
import {MatIconModule} from '@angular/material/icon';
import {FormsModule} from '@angular/forms';
import {
MatButton,
MatAnchor,
MatFabButton,
MatFabAnchor,
MatIconButton,
MatIconAnchor,
MatMiniFabButton,
MatMiniFabAnchor,
} from '@angular/material/button';
import {MatIcon} from '@angular/material/icon';
import {MatTooltip} from '@angular/material/tooltip';
import {MatCheckbox} from '@angular/material/checkbox';

@Component({
selector: 'button-demo',
templateUrl: 'button-demo.html',
styleUrls: ['button-demo.css'],
standalone: true,
imports: [MatButtonModule, MatIconModule],
imports: [
MatButton,
MatAnchor,
MatFabButton,
MatFabAnchor,
MatMiniFabButton,
MatMiniFabAnchor,
MatIconButton,
MatIconAnchor,
MatIcon,
MatTooltip,
MatCheckbox,
FormsModule,
],
})
export class ButtonDemo {
isDisabled: boolean = false;
clickCounter: number = 0;
toggleDisable: boolean = false;
isDisabled = false;
clickCounter = 0;
toggleDisable = false;
tooltipText = 'This is a button tooltip!';
disabledInteractive = false;
}
16 changes: 14 additions & 2 deletions src/material/button/_button-base.scss
Expand Up @@ -68,12 +68,17 @@
@include token-utils.create-token-slot(background-color, state-layer-color);
}

&.mat-mdc-button-disabled .mat-mdc-button-persistent-ripple::before {
@include token-utils.create-token-slot(background-color, disabled-state-layer-color);
}

&:hover .mat-mdc-button-persistent-ripple::before {
@include token-utils.create-token-slot(opacity, hover-state-layer-opacity);
}

&.cdk-program-focused,
&.cdk-keyboard-focused {
&.cdk-keyboard-focused,
&.mat-mdc-button-disabled-interactive:focus {
.mat-mdc-button-persistent-ripple::before {
@include token-utils.create-token-slot(opacity, focus-state-layer-opacity);
}
Expand All @@ -91,11 +96,18 @@
// and note that having pointer-events may have unintended side-effects, e.g. allowing the user
// to click the target underneath the button.
@mixin mat-private-button-disabled() {
&[disabled] {
// `[disabled]` shouldn't be necessary, but we keep it to maintain
// compatibility with apps setting it through host bindings.
&[disabled],
&.mat-mdc-button-disabled {
cursor: default;
pointer-events: none;
@content;
}

&.mat-mdc-button-disabled-interactive {
pointer-events: auto;
}
}

// Hides the touch target on lower densities.
Expand Down

0 comments on commit f23d8c1

Please sign in to comment.