commit 42718c5fca6a275b9ea0ad07b532a139ffe2f499
parent 5a074abbc342e1a9544820c98e0c31a4245e6c22
Author: Anna Kulyk <akulyk@mozilla.com>
Date: Mon, 13 Oct 2025 18:47:27 +0000
Bug 1992462 - Support aria-description in moz- input widgets r=tgiles,accessibility-frontend-reviewers,morgan
Differential Revision: https://phabricator.services.mozilla.com/D267640
Diffstat:
8 files changed, 69 insertions(+), 9 deletions(-)
diff --git a/toolkit/content/tests/widgets/lit-test-helpers.js b/toolkit/content/tests/widgets/lit-test-helpers.js
@@ -167,6 +167,7 @@ class InputTestHelpers extends LitTestHelpers {
async testCommonInputProperties(elementName) {
await this.verifyLabel(elementName);
await this.verifyAriaLabel(elementName);
+ await this.verifyAriaDescription(elementName);
await this.verifyName(elementName);
await this.verifyValue(elementName);
await this.verifyIcon(elementName);
@@ -794,6 +795,11 @@ class InputTestHelpers extends LitTestHelpers {
]);
}
+ /**
+ * Verifies that the aria-label attribute is applied to the input element.
+ *
+ * @param {string} selector - HTML tag of the element under test.
+ */
async verifyAriaLabel(selector) {
const ARIA_LABEL = "I'm not visible";
let ariaLabelTemplate = this.templateFn({
@@ -817,6 +823,32 @@ class InputTestHelpers extends LitTestHelpers {
}
/**
+ * Verifies that the aria-description attribute is applied to the input element.
+ *
+ * @param {string} selector - HTML tag of the element under test.
+ */
+ async verifyAriaDescription(selector) {
+ const ARIA_DESCRIPTION = "I'm not visible";
+ let ariaDescriptionTemplate = this.templateFn({
+ value: "default",
+ "aria-description": ARIA_DESCRIPTION,
+ });
+ let renderTarget = await this.renderTemplate(ariaDescriptionTemplate);
+ let input = renderTarget.querySelector(selector);
+
+ ok(!input.hasDescription, "No visible description text is rendered.");
+ ok(
+ !input.getAttribute("aria-description"),
+ "aria-description is not set on the outer element."
+ );
+ is(
+ input.inputEl.getAttribute("aria-description"),
+ ARIA_DESCRIPTION,
+ "The aria-description is set on the input element."
+ );
+ }
+
+ /**
* Verifies the behavior of nested elements for inputs that support nesting.
*
* @param {string} selector - HTML tag of the element under test.
diff --git a/toolkit/content/widgets/lit-utils.mjs b/toolkit/content/widgets/lit-utils.mjs
@@ -242,8 +242,8 @@ export class MozLitElement extends LitElement {
* @property {boolean} parentDisabled - When this element is nested under another input and that
* input is disabled or unchecked/unpressed the parent will set this property to true so this
* element can be disabled.
- * @property {string} ariaLabel
- * The aria-label text for cases where there is no visible label.
+ * @property {string} ariaLabel - The aria-label text when there is no visible label.
+ * @property {string} ariaDescription - The aria-description text when there is no visible description.
*/
export class MozBaseInputElement extends MozLitElement {
#internals;
@@ -260,6 +260,7 @@ export class MozBaseInputElement extends MozLitElement {
accessKey: { type: String, mapped: true, fluent: true },
parentDisabled: { type: Boolean, state: true },
ariaLabel: { type: String, mapped: true },
+ ariaDescription: { type: String, mapped: true },
};
static inputLayout = "inline";
diff --git a/toolkit/content/widgets/moz-checkbox/moz-checkbox.mjs b/toolkit/content/widgets/moz-checkbox/moz-checkbox.mjs
@@ -24,6 +24,8 @@ import "chrome://global/content/elements/moz-support-link.mjs";
* @property {string} iconSrc - The src for an optional icon
* @property {string} description - The text for the description element that helps describe the checkbox
* @property {string} supportPage - Name of the SUMO support page to link to.
+ * @property {string} ariaLabel - The aria-label text when there is no visible label.
+ * @property {string} ariaDescription - The aria-description text when there is no visible description.
*/
export default class MozCheckbox extends MozBaseInputElement {
static properties = {
@@ -57,8 +59,11 @@ export default class MozCheckbox extends MozBaseInputElement {
@click=${this.handleStateChange}
@change=${this.redispatchEvent}
?disabled=${this.disabled || this.parentDisabled}
- aria-describedby="description"
aria-label=${ifDefined(this.ariaLabel ?? undefined)}
+ aria-describedby="description"
+ aria-description=${ifDefined(
+ this.hasDescription ? undefined : this.ariaDescription
+ )}
accesskey=${ifDefined(this.accessKey)}
/>`;
}
diff --git a/toolkit/content/widgets/moz-input-search/moz-input-search.mjs b/toolkit/content/widgets/moz-input-search/moz-input-search.mjs
@@ -17,7 +17,8 @@ import MozInputText from "chrome://global/content/elements/moz-input-text.mjs";
* @property {string} description - The text for the description element that helps describe the input control
* @property {string} supportPage - Name of the SUMO support page to link to.
* @property {string} placeholder - Text to display when the input has no value.
- * @property {string} ariaLabel - The aria-label text for cases where there is no visible label.
+ * @property {string} ariaLabel - The aria-label text when there is no visible label.
+ * @property {string} ariaDescription - The aria-description text when there is no visible description.
*/
export default class MozInputSearch extends MozInputText {
// The amount of milliseconds that we wait before firing the "search" event.
@@ -86,6 +87,9 @@ export default class MozInputSearch extends MozInputText {
placeholder=${ifDefined(this.placeholder)}
aria-label=${ifDefined(this.ariaLabel ?? undefined)}
aria-describedby="description"
+ aria-description=${ifDefined(
+ this.hasDescription ? undefined : this.ariaDescription
+ )}
@input=${this.handleInput}
@change=${this.redispatchEvent}
/>
diff --git a/toolkit/content/widgets/moz-input-text/moz-input-text.mjs b/toolkit/content/widgets/moz-input-text/moz-input-text.mjs
@@ -18,6 +18,8 @@ import { MozBaseInputElement } from "../lit-utils.mjs";
* @property {string} description - The text for the description element that helps describe the input control
* @property {string} supportPage - Name of the SUMO support page to link to.
* @property {string} placeholder - Text to display when the input has no value.
+ * @property {string} ariaLabel - The aria-label text when there is no visible label.
+ * @property {string} ariaDescription - The aria-description text when there is no visible description.
*/
export default class MozInputText extends MozBaseInputElement {
static properties = {
@@ -60,6 +62,9 @@ export default class MozInputText extends MozBaseInputElement {
placeholder=${ifDefined(this.placeholder)}
aria-label=${ifDefined(this.ariaLabel ?? undefined)}
aria-describedby="description"
+ aria-description=${ifDefined(
+ this.hasDescription ? undefined : this.ariaDescription
+ )}
@input=${this.handleInput}
@change=${this.redispatchEvent}
/>
diff --git a/toolkit/content/widgets/moz-radio-group/moz-radio-group.mjs b/toolkit/content/widgets/moz-radio-group/moz-radio-group.mjs
@@ -24,6 +24,8 @@ import { MozBaseInputElement } from "../lit-utils.mjs";
* @property {string} value
* Selected value for the group. Changing the value updates the checked
* state of moz-radio children and vice versa.
+ * @property {string} ariaLabel - The aria-label text when there is no visible label.
+ * @property {string} ariaDescription - The aria-description text when there is no visible description.
* @slot default - The radio group's content, intended for moz-radio elements.
* @slot support-link - The radio group's support link intended for moz-radio elements.
*/
@@ -69,11 +71,14 @@ export class MozRadio extends SelectControlItemMixin(MozBaseInputElement) {
name=${this.name}
.checked=${this.checked}
aria-checked=${this.checked}
- aria-describedby="description"
tabindex=${this.itemTabIndex}
?disabled=${this.isDisabled}
accesskey=${ifDefined(this.accessKey)}
aria-label=${ifDefined(this.ariaLabel ?? undefined)}
+ aria-describedby="description"
+ aria-description=${ifDefined(
+ this.hasDescription ? undefined : this.ariaDescription
+ )}
@click=${this.handleClick}
@change=${this.handleChange}
/>`;
diff --git a/toolkit/content/widgets/moz-select/moz-select.mjs b/toolkit/content/widgets/moz-select/moz-select.mjs
@@ -22,6 +22,8 @@ import { MozBaseInputElement, MozLitElement } from "../lit-utils.mjs";
* @property {string} iconSrc - The src for an optional icon
* @property {string} description - The text for the description element that helps describe the input control
* @property {string} supportPage - Name of the SUMO support page to link to.
+ * @property {string} ariaLabel - The aria-label text when there is no visible label.
+ * @property {string} ariaDescription - The aria-description text when there is no visible description.
* @property {array} options - The array of options, populated by <moz-option> children in the
* default slot. Do not set directly, these will be overridden by <moz-option> children.
*/
@@ -141,8 +143,11 @@ export default class MozSelect extends MozBaseInputElement {
@input=${this.handleStateChange}
@change=${this.redispatchEvent}
?disabled=${this.disabled || this.parentDisabled}
- aria-describedby="description"
aria-label=${ifDefined(this.ariaLabel ?? undefined)}
+ aria-describedby="description"
+ aria-description=${ifDefined(
+ this.hasDescription ? undefined : this.ariaDescription
+ )}
>
${this.options.map(
option => html`
diff --git a/toolkit/content/widgets/moz-toggle/moz-toggle.mjs b/toolkit/content/widgets/moz-toggle/moz-toggle.mjs
@@ -15,8 +15,8 @@ import "chrome://global/content/elements/moz-label.mjs";
* @property {boolean} disabled - Whether or not the element is disabled.
* @property {string} label - The label text.
* @property {string} description - The description text.
- * @property {string} ariaLabel
- * The aria-label text for cases where there is no visible label.
+ * @property {string} ariaLabel - The aria-label text when there is no visible label.
+ * @property {string} ariaDescription - The aria-description text when there is no visible description.
* @slot support-link - Used to append a moz-support-link to the description.
* @fires toggle
* Custom event indicating that the toggle's pressed state has changed.
@@ -58,8 +58,11 @@ export default class MozToggle extends MozBaseInputElement {
value=${this.value}
?disabled=${disabled}
aria-pressed=${pressed}
- aria-describedby="description"
aria-label=${ifDefined(ariaLabel ?? undefined)}
+ aria-describedby="description"
+ aria-description=${ifDefined(
+ this.hasDescription ? undefined : this.ariaDescription
+ )}
accesskey=${ifDefined(this.accessKey)}
@click=${handleClick}
></button>`;