diff --git a/src/material/checkbox/checkbox.html b/src/material/checkbox/checkbox.html index 8378598df791..0dbee7296952 100644 --- a/src/material/checkbox/checkbox.html +++ b/src/material/checkbox/checkbox.html @@ -10,6 +10,9 @@ [attr.aria-labelledby]="ariaLabelledby" [attr.aria-describedby]="ariaDescribedby" [attr.aria-checked]="indeterminate ? 'mixed' : null" + [attr.aria-expanded]="ariaExpanded" + [attr.aria-controls]="ariaControls" + [attr.aria-owns]="ariaOwns" [attr.name]="name" [attr.value]="value" [checked]="checked" diff --git a/src/material/checkbox/checkbox.md b/src/material/checkbox/checkbox.md index 0260fa3cf6dd..0cfb43e1bd12 100644 --- a/src/material/checkbox/checkbox.md +++ b/src/material/checkbox/checkbox.md @@ -74,3 +74,9 @@ binding these properties, as demonstrated below. ``` + +Additionally, `MatCheckbox` now supports the following accessibility properties: + +- **`aria-expanded`**: Indicates whether the checkbox controls the visibility of another element. This should be a boolean value (`true` or `false`). +- **`aria-controls`**: Specifies the ID of the element that the checkbox controls. +- **`aria-owns`**: Specifies the ID of the element that the checkbox visually owns. diff --git a/src/material/checkbox/checkbox.spec.ts b/src/material/checkbox/checkbox.spec.ts index d7efa1223b32..cb63d40fbef0 100644 --- a/src/material/checkbox/checkbox.spec.ts +++ b/src/material/checkbox/checkbox.spec.ts @@ -776,6 +776,94 @@ describe('MDC-based MatCheckbox', () => { }); }); + describe('with provided aria-expanded', () => { + let checkboxDebugElement: DebugElement; + let checkboxNativeElement: HTMLElement; + let inputElement: HTMLInputElement; + + it('should use the provided postive aria-expanded', () => { + fixture = createComponent(CheckboxWithPositiveAriaExpanded); + checkboxDebugElement = fixture.debugElement.query(By.directive(MatCheckbox))!; + checkboxNativeElement = checkboxDebugElement.nativeElement; + inputElement = checkboxNativeElement.querySelector('input'); + + fixture.detectChanges(); + expect(inputElement.getAttribute('aria-expanded')).toBe('true'); + }); + + it('should use the provided negative aria-expanded', () => { + fixture = createComponent(CheckboxWithNegativeAriaExpanded); + checkboxDebugElement = fixture.debugElement.query(By.directive(MatCheckbox))!; + checkboxNativeElement = checkboxDebugElement.nativeElement; + inputElement = checkboxNativeElement.querySelector('input'); + + fixture.detectChanges(); + expect(inputElement.getAttribute('aria-expanded')).toBe('false'); + }); + + it('should not assign aria-expanded if none is provided', () => { + fixture = createComponent(SingleCheckbox); + checkboxDebugElement = fixture.debugElement.query(By.directive(MatCheckbox))!; + checkboxNativeElement = checkboxDebugElement.nativeElement; + inputElement = checkboxNativeElement.querySelector('input'); + + fixture.detectChanges(); + expect(inputElement.getAttribute('aria-expanded')).toBe(null); + }); + }); + + describe('with provided aria-controls', () => { + let checkboxDebugElement: DebugElement; + let checkboxNativeElement: HTMLElement; + let inputElement: HTMLInputElement; + + it('should use the provided aria-controls', () => { + fixture = createComponent(CheckboxWithAriaControls); + checkboxDebugElement = fixture.debugElement.query(By.directive(MatCheckbox))!; + checkboxNativeElement = checkboxDebugElement.nativeElement; + inputElement = checkboxNativeElement.querySelector('input'); + + fixture.detectChanges(); + expect(inputElement.getAttribute('aria-controls')).toBe('some-id'); + }); + + it('should not assign aria-controls if none is provided', () => { + fixture = createComponent(SingleCheckbox); + checkboxDebugElement = fixture.debugElement.query(By.directive(MatCheckbox))!; + checkboxNativeElement = checkboxDebugElement.nativeElement; + inputElement = checkboxNativeElement.querySelector('input'); + + fixture.detectChanges(); + expect(inputElement.getAttribute('aria-controls')).toBe(null); + }); + }); + + describe('with provided aria-owns', () => { + let checkboxDebugElement: DebugElement; + let checkboxNativeElement: HTMLElement; + let inputElement: HTMLInputElement; + + it('should use the provided aria-owns', () => { + fixture = createComponent(CheckboxWithAriaOwns); + checkboxDebugElement = fixture.debugElement.query(By.directive(MatCheckbox))!; + checkboxNativeElement = checkboxDebugElement.nativeElement; + inputElement = checkboxNativeElement.querySelector('input'); + + fixture.detectChanges(); + expect(inputElement.getAttribute('aria-owns')).toBe('some-id'); + }); + + it('should not assign aria-owns if none is provided', () => { + fixture = createComponent(SingleCheckbox); + checkboxDebugElement = fixture.debugElement.query(By.directive(MatCheckbox))!; + checkboxNativeElement = checkboxDebugElement.nativeElement; + inputElement = checkboxNativeElement.querySelector('input'); + + fixture.detectChanges(); + expect(inputElement.getAttribute('aria-owns')).toBe(null); + }); + }); + describe('with provided tabIndex', () => { let checkboxDebugElement: DebugElement; let checkboxNativeElement: HTMLElement; @@ -1206,6 +1294,38 @@ class CheckboxWithAriaLabelledby {} }) class CheckboxWithAriaDescribedby {} +/** Simple test component with an aria-expanded set with true. */ +@Component({ + template: ``, + standalone: true, + imports: [MatCheckbox], +}) +class CheckboxWithPositiveAriaExpanded {} + +/** Simple test component with an aria-expanded set with false. */ +@Component({ + template: ``, + standalone: true, + imports: [MatCheckbox], +}) +class CheckboxWithNegativeAriaExpanded {} + +/** Simple test component with an aria-controls set. */ +@Component({ + template: ``, + standalone: true, + imports: [MatCheckbox], +}) +class CheckboxWithAriaControls {} + +/** Simple test component with an aria-owns set. */ +@Component({ + template: ``, + standalone: true, + imports: [MatCheckbox], +}) +class CheckboxWithAriaOwns {} + /** Simple test component with name attribute */ @Component({ template: ``, diff --git a/src/material/checkbox/checkbox.ts b/src/material/checkbox/checkbox.ts index 58a58877252c..d25c310b505e 100644 --- a/src/material/checkbox/checkbox.ts +++ b/src/material/checkbox/checkbox.ts @@ -159,6 +159,19 @@ export class MatCheckbox /** The 'aria-describedby' attribute is read after the element's label and field type. */ @Input('aria-describedby') ariaDescribedby: string; + /** + * Users can specify the `aria-expanded` attribute which will be forwarded to the input element + */ + @Input({alias: 'aria-expanded', transform: booleanAttribute}) ariaExpanded: boolean; + + /** + * Users can specify the `aria-controls` attribute which will be forwarded to the input element + */ + @Input('aria-controls') ariaControls: string; + + /** Users can specify the `aria-owns` attribute which will be forwarded to the input element */ + @Input('aria-owns') ariaOwns: string; + private _uniqueId: string; /** A unique id for the checkbox input. If none is supplied, it will be auto-generated. */