diff --git a/src/dev-app/checkbox/BUILD.bazel b/src/dev-app/checkbox/BUILD.bazel
index 190471f72a4a..8535517d8cab 100644
--- a/src/dev-app/checkbox/BUILD.bazel
+++ b/src/dev-app/checkbox/BUILD.bazel
@@ -16,6 +16,7 @@ ng_module(
"//src/material/form-field",
"//src/material/input",
"//src/material/select",
+ "//src/material/tooltip",
"@npm//@angular/forms",
],
)
diff --git a/src/dev-app/checkbox/checkbox-demo.html b/src/dev-app/checkbox/checkbox-demo.html
index 8bd338ef7ae5..1c65e67460bc 100644
--- a/src/dev-app/checkbox/checkbox-demo.html
+++ b/src/dev-app/checkbox/checkbox-demo.html
@@ -26,19 +26,20 @@
mat-checkbox: Basic Example
(change)="isIndeterminate = false"
[indeterminate]="isIndeterminate"
[disabled]="isDisabled"
- [labelPosition]="labelPosition">
+ [disabledInteractive]="isDisabledInteractive"
+ [labelPosition]="labelPosition"
+ [matTooltip]="isDisabled ? 'Tooltip that only shows up when disabled' : null">
Do you want to foobar the bazquux ?
- {{printResult()}}
-
+
Toggle Indeterminate
Toggle Disabled
+
+ Toggle Disabled Interactive
Toggle Color
diff --git a/src/dev-app/checkbox/checkbox-demo.ts b/src/dev-app/checkbox/checkbox-demo.ts
index 6d26783d403d..873cb272093d 100644
--- a/src/dev-app/checkbox/checkbox-demo.ts
+++ b/src/dev-app/checkbox/checkbox-demo.ts
@@ -13,6 +13,7 @@ import {MAT_CHECKBOX_DEFAULT_OPTIONS, MatCheckboxModule} from '@angular/material
import {MatPseudoCheckboxModule, ThemePalette} from '@angular/material/core';
import {MatInputModule} from '@angular/material/input';
import {MatSelectModule} from '@angular/material/select';
+import {MatTooltip} from '@angular/material/tooltip';
export interface Task {
name: string;
@@ -114,15 +115,17 @@ export class MatCheckboxDemoNestedChecklist {
ClickActionNoop,
ClickActionCheck,
AnimationsNoop,
+ MatTooltip,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CheckboxDemo {
- isIndeterminate: boolean = false;
- isChecked: boolean = false;
- isDisabled: boolean = false;
+ isIndeterminate = false;
+ isChecked = false;
+ isDisabled = false;
+ isDisabledInteractive = false;
labelPosition: 'before' | 'after' = 'after';
- useAlternativeColor: boolean = false;
+ useAlternativeColor = false;
demoRequired = false;
demoLabelAfter = false;
diff --git a/src/material/checkbox/_checkbox-common.scss b/src/material/checkbox/_checkbox-common.scss
index 0a713417a551..e17a06009638 100644
--- a/src/material/checkbox/_checkbox-common.scss
+++ b/src/material/checkbox/_checkbox-common.scss
@@ -138,6 +138,21 @@ $_fallback-size: 40px;
@include token-utils.create-token-slot(border-color, selected-focus-icon-color);
@include token-utils.create-token-slot(background-color, selected-focus-icon-color);
}
+
+ // Needs extra specificity to override the focus, hover, active states.
+ .mdc-checkbox--disabled.mat-mdc-checkbox-disabled-interactive {
+ .mdc-checkbox:hover .mdc-checkbox__native-control ~ .mdc-checkbox__background,
+ .mdc-checkbox .mdc-checkbox__native-control:focus ~ .mdc-checkbox__background,
+ .mdc-checkbox__background {
+ @include token-utils.create-token-slot(border-color, disabled-unselected-icon-color);
+ }
+
+ .mdc-checkbox__native-control:checked ~ .mdc-checkbox__background,
+ .mdc-checkbox__native-control:indeterminate ~ .mdc-checkbox__background {
+ @include token-utils.create-token-slot(background-color, disabled-selected-icon-color);
+ border-color: transparent;
+ }
+ }
}
.mdc-checkbox__checkmark {
@@ -158,8 +173,12 @@ $_fallback-size: 40px;
}
@include token-utils.use-tokens($prefix, $slots) {
- .mdc-checkbox--disabled .mdc-checkbox__checkmark {
- @include token-utils.create-token-slot(color, disabled-selected-checkmark-color);
+ .mdc-checkbox--disabled {
+ &, &.mat-mdc-checkbox-disabled-interactive {
+ .mdc-checkbox__checkmark {
+ @include token-utils.create-token-slot(color, disabled-selected-checkmark-color);
+ }
+ }
}
}
@@ -193,8 +212,12 @@ $_fallback-size: 40px;
}
@include token-utils.use-tokens($prefix, $slots) {
- .mdc-checkbox--disabled .mdc-checkbox__mixedmark {
- @include token-utils.create-token-slot(border-color, disabled-selected-checkmark-color);
+ .mdc-checkbox--disabled {
+ &, &.mat-mdc-checkbox-disabled-interactive {
+ .mdc-checkbox__mixedmark {
+ @include token-utils.create-token-slot(border-color, disabled-selected-checkmark-color);
+ }
+ }
}
}
@@ -520,4 +543,15 @@ $_fallback-size: 40px;
);
}
}
+
+ // Needs extra specificity to override the focus, hover, active states.
+ .mdc-checkbox--disabled.mat-mdc-checkbox-disabled-interactive & {
+ .mdc-checkbox__native-control ~ .mat-mdc-checkbox-ripple .mat-ripple-element,
+ .mdc-checkbox__native-control ~ .mdc-checkbox__ripple {
+ @include token-utils.create-token-slot(
+ background-color,
+ unselected-hover-state-layer-color
+ );
+ }
+ }
}
diff --git a/src/material/checkbox/checkbox-config.ts b/src/material/checkbox/checkbox-config.ts
index bef49ab5856f..be9715448d52 100644
--- a/src/material/checkbox/checkbox-config.ts
+++ b/src/material/checkbox/checkbox-config.ts
@@ -18,8 +18,12 @@ export interface MatCheckboxDefaultOptions {
* https://material.angular.io/guide/theming#using-component-color-variants
*/
color?: ThemePalette;
+
/** Default checkbox click action for checkboxes. */
clickAction?: MatCheckboxClickAction;
+
+ /** Whether disabled checkboxes should be interactive. */
+ disabledInteractive?: boolean;
}
/** Injection token to be used to override the default options for `mat-checkbox`. */
@@ -36,6 +40,7 @@ export function MAT_CHECKBOX_DEFAULT_OPTIONS_FACTORY(): MatCheckboxDefaultOption
return {
color: 'accent',
clickAction: 'check-indeterminate',
+ disabledInteractive: false,
};
}
diff --git a/src/material/checkbox/checkbox.html b/src/material/checkbox/checkbox.html
index 8378598df791..045ee998e872 100644
--- a/src/material/checkbox/checkbox.html
+++ b/src/material/checkbox/checkbox.html
@@ -10,14 +10,15 @@
[attr.aria-labelledby]="ariaLabelledby"
[attr.aria-describedby]="ariaDescribedby"
[attr.aria-checked]="indeterminate ? 'mixed' : null"
+ [attr.aria-disabled]="disabled && disabledInteractive ? true : null"
[attr.name]="name"
[attr.value]="value"
[checked]="checked"
[indeterminate]="indeterminate"
- [disabled]="disabled"
+ [disabled]="disabled && !disabledInteractive"
[id]="inputId"
[required]="required"
- [tabIndex]="disabled ? -1 : tabIndex"
+ [tabIndex]="disabled && !disabledInteractive ? -1 : tabIndex"
(blur)="_onBlur()"
(click)="_onInputClick()"
(change)="_onInteractionEvent($event)"/>
@@ -43,9 +44,7 @@
(#14385). Putting a click handler on the caused this bug because the browser produced
an unnecessary accessibility tree node.
-->
-
+
diff --git a/src/material/checkbox/checkbox.scss b/src/material/checkbox/checkbox.scss
index e63f95417e5a..e21fcfc69279 100644
--- a/src/material/checkbox/checkbox.scss
+++ b/src/material/checkbox/checkbox.scss
@@ -42,14 +42,24 @@
}
}
- &.mat-mdc-checkbox-disabled label {
- cursor: default;
+ &.mat-mdc-checkbox-disabled {
+ &.mat-mdc-checkbox-disabled-interactive {
+ pointer-events: auto;
- @include token-utils.use-tokens(
- tokens-mat-checkbox.$prefix,
- tokens-mat-checkbox.get-token-slots()
- ) {
- @include token-utils.create-token-slot(color, disabled-label-color);
+ input {
+ cursor: default;
+ }
+ }
+
+ label {
+ cursor: default;
+
+ @include token-utils.use-tokens(
+ tokens-mat-checkbox.$prefix,
+ tokens-mat-checkbox.get-token-slots()
+ ) {
+ @include token-utils.create-token-slot(color, disabled-label-color);
+ }
}
}
diff --git a/src/material/checkbox/checkbox.spec.ts b/src/material/checkbox/checkbox.spec.ts
index d7efa1223b32..7d858e856b1b 100644
--- a/src/material/checkbox/checkbox.spec.ts
+++ b/src/material/checkbox/checkbox.spec.ts
@@ -444,6 +444,28 @@ describe('MDC-based MatCheckbox', () => {
expect(checkboxNativeElement.querySelector('svg')!.getAttribute('focusable')).toBe('false');
}));
+ it('should be able to mark a checkbox as disabled while keeping it interactive', fakeAsync(() => {
+ testComponent.isDisabled = true;
+ fixture.changeDetectorRef.markForCheck();
+ fixture.detectChanges();
+
+ expect(checkboxNativeElement.classList).not.toContain(
+ 'mat-mdc-checkbox-disabled-interactive',
+ );
+ expect(inputElement.hasAttribute('aria-disabled')).toBe(false);
+ expect(inputElement.tabIndex).toBe(-1);
+ expect(inputElement.disabled).toBe(true);
+
+ testComponent.disabledInteractive = true;
+ fixture.changeDetectorRef.markForCheck();
+ fixture.detectChanges();
+
+ expect(checkboxNativeElement.classList).toContain('mat-mdc-checkbox-disabled-interactive');
+ expect(inputElement.getAttribute('aria-disabled')).toBe('true');
+ expect(inputElement.tabIndex).toBe(0);
+ expect(inputElement.disabled).toBe(false);
+ }));
+
describe('ripple elements', () => {
it('should show ripples on label mousedown', fakeAsync(() => {
const rippleSelector = '.mat-ripple-element:not(.mat-checkbox-persistent-ripple)';
@@ -1111,6 +1133,7 @@ describe('MatCheckboxDefaultOptions', () => {
[color]="checkboxColor"
[disableRipple]="disableRipple"
[value]="checkboxValue"
+ [disabledInteractive]="disabledInteractive"
(change)="onCheckboxChange($event)">
Simple checkbox
@@ -1120,13 +1143,14 @@ describe('MatCheckboxDefaultOptions', () => {
})
class SingleCheckbox {
labelPos: 'before' | 'after' = 'after';
- isChecked: boolean = false;
- isRequired: boolean = false;
- isIndeterminate: boolean = false;
- isDisabled: boolean = false;
- disableRipple: boolean = false;
- parentElementClicked: boolean = false;
- parentElementKeyedUp: boolean = false;
+ isChecked = false;
+ isRequired = false;
+ isIndeterminate = false;
+ isDisabled = false;
+ disableRipple = false;
+ parentElementClicked = false;
+ parentElementKeyedUp = false;
+ disabledInteractive = false;
checkboxId: string | null = 'simple-check';
checkboxColor: ThemePalette = 'primary';
checkboxValue: string = 'single_checkbox';
@@ -1143,9 +1167,9 @@ class SingleCheckbox {
imports: [MatCheckbox, FormsModule],
})
class CheckboxWithNgModel {
- isGood: boolean = false;
- isRequired: boolean = true;
- isDisabled: boolean = false;
+ isGood = false;
+ isRequired = true;
+ isDisabled = false;
}
@Component({
diff --git a/src/material/checkbox/checkbox.ts b/src/material/checkbox/checkbox.ts
index 58a58877252c..aee9aaac1016 100644
--- a/src/material/checkbox/checkbox.ts
+++ b/src/material/checkbox/checkbox.ts
@@ -98,6 +98,7 @@ const defaults = MAT_CHECKBOX_DEFAULT_OPTIONS_FACTORY();
// Add classes that users can use to more easily target disabled or checked checkboxes.
'[class.mat-mdc-checkbox-disabled]': 'disabled',
'[class.mat-mdc-checkbox-checked]': 'checked',
+ '[class.mat-mdc-checkbox-disabled-interactive]': 'disabledInteractive',
'[class]': 'color ? "mat-" + color : "mat-accent"',
},
providers: [
@@ -211,6 +212,10 @@ export class MatCheckbox
*/
@Input() color: string | undefined;
+ /** Whether the checkbox should remain interactive when it is disabled. */
+ @Input({transform: booleanAttribute})
+ disabledInteractive: boolean;
+
/**
* Reference to the MatRipple instance of the checkbox.
* @deprecated Considered an implementation detail. To be removed.
@@ -241,6 +246,7 @@ export class MatCheckbox
this.color = this._options.color || defaults.color;
this.tabIndex = parseInt(tabIndex) || 0;
this.id = this._uniqueId = `mat-mdc-checkbox-${++nextUniqueId}`;
+ this.disabledInteractive = _options?.disabledInteractive ?? false;
}
ngOnChanges(changes: SimpleChanges) {
@@ -422,7 +428,10 @@ export class MatCheckbox
// It is important to only emit it, if the native input triggered one, because
// we don't want to trigger a change event, when the `checked` variable changes for example.
this._emitChangeEvent();
- } else if (!this.disabled && clickAction === 'noop') {
+ } else if (
+ (this.disabled && this.disabledInteractive) ||
+ (!this.disabled && clickAction === 'noop')
+ ) {
// Reset native input when clicked with noop. The native checkbox becomes checked after
// click, reset it to be align with `checked` value of `mat-checkbox`.
this._inputElement.nativeElement.checked = this.checked;
diff --git a/tools/public_api_guard/material/checkbox.md b/tools/public_api_guard/material/checkbox.md
index a5978a62daa7..51621165fdb0 100644
--- a/tools/public_api_guard/material/checkbox.md
+++ b/tools/public_api_guard/material/checkbox.md
@@ -59,6 +59,7 @@ export class MatCheckbox implements AfterViewInit, OnChanges, ControlValueAccess
protected _createChangeEvent(isChecked: boolean): MatCheckboxChange;
get disabled(): boolean;
set disabled(value: boolean);
+ disabledInteractive: boolean;
disableRipple: boolean;
// (undocumented)
_elementRef: ElementRef;
@@ -82,6 +83,8 @@ export class MatCheckbox implements AfterViewInit, OnChanges, ControlValueAccess
// (undocumented)
static ngAcceptInputType_disabled: unknown;
// (undocumented)
+ static ngAcceptInputType_disabledInteractive: unknown;
+ // (undocumented)
static ngAcceptInputType_disableRipple: unknown;
// (undocumented)
static ngAcceptInputType_indeterminate: unknown;
@@ -123,7 +126,7 @@ export class MatCheckbox implements AfterViewInit, OnChanges, ControlValueAccess
// (undocumented)
writeValue(value: any): void;
// (undocumented)
- static ɵcmp: i0.ɵɵComponentDeclaration;
+ static ɵcmp: i0.ɵɵComponentDeclaration;
// (undocumented)
static ɵfac: i0.ɵɵFactoryDeclaration;
}
@@ -141,6 +144,7 @@ export type MatCheckboxClickAction = 'noop' | 'check' | 'check-indeterminate' |
export interface MatCheckboxDefaultOptions {
clickAction?: MatCheckboxClickAction;
color?: ThemePalette;
+ disabledInteractive?: boolean;
}
// @public (undocumented)