Skip to content

Commit 95f576c

Browse files
committed
fix(a11y): don't set aria description if it's the same as the node's aria-label
As per a recent discussion, reworks the `AriaDescriber` not to set the `aria-describedby`, if the message will be exactly the same as an existing `aria-label` on the element. This avoids the same text being read out twice. Fixes #15048.
1 parent bb69483 commit 95f576c

File tree

2 files changed

+22
-3
lines changed

2 files changed

+22
-3
lines changed

src/cdk/a11y/aria-describer/aria-describer.spec.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,13 @@ describe('AriaDescriber', () => {
132132
// Use `querySelectorAll` with an attribute since `getElementById` will stop at the first match.
133133
expect(document.querySelectorAll(`[id='${MESSAGES_CONTAINER_ID}']`).length).toBe(1);
134134
});
135+
136+
it('should not describe messages that match up with the aria-label of the element', () => {
137+
component.element1.setAttribute('aria-label', 'Hello');
138+
ariaDescriber.describe(component.element1, 'Hello');
139+
ariaDescriber.describe(component.element1, 'Hi');
140+
expectMessages(['Hi']);
141+
});
135142
});
136143

137144
function getMessagesContainer() {

src/cdk/a11y/aria-describer/aria-describer.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ export class AriaDescriber implements OnDestroy {
8383

8484
/** Removes the host element's aria-describedby reference to the message element. */
8585
removeDescription(hostElement: Element, message: string) {
86-
if (!this._canBeDescribed(hostElement, message)) {
86+
if (!this._isElementNode(hostElement)) {
8787
return;
8888
}
8989

@@ -218,10 +218,22 @@ export class AriaDescriber implements OnDestroy {
218218

219219
/** Determines whether a message can be described on a particular element. */
220220
private _canBeDescribed(element: Element, message: string): boolean {
221-
return element.nodeType === this._document.ELEMENT_NODE && message != null &&
222-
!!`${message}`.trim();
221+
if (!this._isElementNode(element)) {
222+
return false;
223+
}
224+
225+
const trimmedMessage = message == null ? '' : `${message}`.trim();
226+
const ariaLabel = element.getAttribute('aria-label');
227+
228+
// We shouldn't set descriptions if they're exactly the same as the `aria-label` of the element,
229+
// because screen readers will end up reading out the same text twice in a row.
230+
return trimmedMessage ? (!ariaLabel || ariaLabel.trim() !== trimmedMessage) : false;
223231
}
224232

233+
/** Checks whether a node is an Element node. */
234+
private _isElementNode(element: Node): element is Element {
235+
return element.nodeType === this._document.ELEMENT_NODE;
236+
}
225237
}
226238

227239

0 commit comments

Comments
 (0)