Skip to content

Commit

Permalink
fix(a11y): radio group label is not announced by screen readers (back…
Browse files Browse the repository at this point in the history
…port to 16.x) (#1371)

Backport aa406de from #1360. <br> ## PR
Checklist

Please check if your PR fulfills the following requirements:

- [ ] Tests for the changes have been added (for bug fixes / features)
- [ ] Docs have been added / updated (for bug fixes / features)
- [ ] If applicable, have a visual design approval

## PR Type

What kind of change does this PR introduce?

&lt;!-- Please check the one that applies to this PR using
&quot;x&quot;. --&gt;

- [x] Bugfix
- [ ] Feature
- [ ] Code style update (formatting, local variables)
- [ ] Refactoring (no functional changes, no api changes)
- [ ] Build related changes
- [ ] CI related changes
- [ ] Documentation content changes
- [ ] Other... Please describe:

## What is the current behavior?

&lt;!-- Please describe the current behavior that you are modifying, or
link to a relevant issue. --&gt;

Issue Number: [CDE-1196](https://jira.eng.vmware.com/browse/CDE-1196)

## What is the new behavior?
This was previously fixed using `aria-label`, after discussion with the
accessibility team, it was recommended to use
`aria-labelledby=&quot;[id-of-form-group-name]&quot;` instead of
`aria-label=&quot;[Name of Group]&quot;`. Also,`aria-labelledby` is
better from the localization perspective.

## Does this PR introduce a breaking change?

- [ ] Yes
- [x] No

&lt;!-- If this PR contains a breaking change, please describe the
impact and migration path for existing applications below. --&gt;

## Other information

Co-authored-by: Andrea A Fernandes <[email protected]>
  • Loading branch information
github-actions[bot] and andyfeds authored Apr 23, 2024
1 parent 43b4510 commit 86e661d
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 8 deletions.
6 changes: 4 additions & 2 deletions projects/angular/clarity.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -3200,13 +3200,15 @@ export class ClrRadio extends WrappedFormControl<ClrRadioWrapper> {
export class ClrRadioContainer extends ClrAbstractContainer implements AfterContentInit {
constructor(layoutService: LayoutService, controlClassService: ControlClassService, ngControlService: NgControlService, ifControlStateService: IfControlStateService);
// (undocumented)
ariaLabel: string;
ariaLabelledBy: string;
// (undocumented)
get clrInline(): boolean | string;
set clrInline(value: boolean | string);
// (undocumented)
protected controlClassService: ControlClassService;
// (undocumented)
groupLabel: ElementRef<HTMLElement>;
// (undocumented)
protected ifControlStateService: IfControlStateService;
// (undocumented)
protected layoutService: LayoutService;
Expand All @@ -3219,7 +3221,7 @@ export class ClrRadioContainer extends ClrAbstractContainer implements AfterCont
// (undocumented)
role: string;
// (undocumented)
static ɵcmp: i0.ɵɵComponentDeclaration<ClrRadioContainer, "clr-radio-container", never, { "clrInline": "clrInline"; }, {}, ["radios"], ["label", "clr-radio-wrapper", "clr-control-helper", "clr-control-error", "clr-control-success"], false, never>;
static ɵcmp: i0.ɵɵComponentDeclaration<ClrRadioContainer, "clr-radio-container", never, { "clrInline": "clrInline"; }, {}, ["groupLabel", "radios"], ["label", "clr-radio-wrapper", "clr-control-helper", "clr-control-error", "clr-control-success"], false, never>;
// (undocumented)
static ɵfac: i0.ɵɵFactoryDeclaration<ClrRadioContainer, [{ optional: true; }, null, null, null]>;
}
Expand Down
31 changes: 25 additions & 6 deletions projects/angular/src/forms/radio/radio-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,19 @@
* The full license information can be found in LICENSE in the root directory of this project.
*/

import { AfterContentInit, Component, ContentChildren, Input, Optional, QueryList } from '@angular/core';
import {
AfterContentInit,
Component,
ContentChild,
ContentChildren,
ElementRef,
Input,
Optional,
QueryList,
} from '@angular/core';

import { uniqueIdFactory } from '../../utils/id-generator/id-generator.service';
import { ClrLabel } from '../common';
import { ClrAbstractContainer } from '../common/abstract-container';
import { IfControlStateService } from '../common/if-control-state/if-control-state.service';
import { ContainerIdService } from '../common/providers/container-id.service';
Expand Down Expand Up @@ -47,17 +58,19 @@ import { ClrRadio } from './radio';
'[class.clr-form-control-disabled]': 'control?.disabled',
'[class.clr-row]': 'addGrid()',
'[attr.role]': 'role',
'[attr.aria-label]': 'ariaLabel',
'[attr.aria-labelledby]': 'ariaLabelledBy',
},
providers: [NgControlService, IfControlStateService, ControlClassService, ContainerIdService],
})
export class ClrRadioContainer extends ClrAbstractContainer implements AfterContentInit {
role: string;
ariaLabel: string;
ariaLabelledBy: string;

@ContentChildren(ClrRadio, { descendants: true }) radios: QueryList<ClrRadio>;
@ContentChild(ClrLabel, { read: ElementRef, static: true }) groupLabel: ElementRef<HTMLElement>;

private inline = false;
private _generatedId = uniqueIdFactory();

constructor(
@Optional() protected override layoutService: LayoutService,
Expand Down Expand Up @@ -88,14 +101,20 @@ export class ClrRadioContainer extends ClrAbstractContainer implements AfterCont

override ngAfterContentInit() {
this.setAriaRoles();
this.setAriaLabel();
this.setAriaLabelledBy();
}

private setAriaRoles() {
this.role = this.radios.length ? 'group' : null;
}

private setAriaLabel() {
this.ariaLabel = this.radios.length ? this.label?.labelText : null;
private setAriaLabelledBy() {
const _id = this.groupLabel?.nativeElement.getAttribute('id');
if (!_id) {
this.groupLabel?.nativeElement.setAttribute('id', this._generatedId);
this.ariaLabelledBy = this.radios.length ? this._generatedId : null;
} else {
this.ariaLabelledBy = this.radios.length ? _id : null;
}
}
}

0 comments on commit 86e661d

Please sign in to comment.