Repository navigation
ARIA IDL Updates Mini‐Explainer (ARIA PR #2484)
This is a mini-explainer for: ARIA PR #2484.
Web Interface Definition Language (IDL), its purpose and ARIA's Web IDL is thoroughly documented here: https://github.com/w3c/aria/blob/main/documentation/aria-idl.md.
Essentially, WebIDL (IDL for short) defines the interfaces and APIs implemented in browsers including attributes. Attributes have two aspects that represent the same thing:
- The content attribute which is found in HTML markup, e.g.,
<button aria-current> - The IDL attribute which is manipulated via JavaScript and offers type coercion/validation, e.g.,
buttonElement.ariaCurrent
To keep both facets of an attribute in sync, IDL attributes can mirror their content attribute which is called reflection:
Reflection is primarily about improving web developer ergonomics by giving them typed access to content attributes through reflected IDL attributes. The ultimate source of truth, which the web platform builds upon, is the content attributes themselves.
Content attributes are simply strings set in the HTML markup, or using the setAttribute() DOM API; on the other hand, IDL attributes use JavaScript dot notation because they are properties of JavaScript DOM objects. Also, the value of a content attribute can differ from its IDL attribute, e.g.,:
// example HTML file
<input id="my-input" type="foo">
...
<script>
let input = document.getElementById("my-input");
console.log(input.getAttribute("type")) // "foo", content attribute value
console.log(input.type) // "text", IDL attribute value (and invalid value default)
</script>This example demonstrates one of the primary advantages of IDL attributes, which is attribute validation. For the type attribute, when its value is invalid, the browser falls back to a value of text (i.e., invalid value default); the value text is also used when the type attribute is missing (i.e., missing value default).
Each ARIA IDL attribute currently reflects as one of the following three types:
-
Nullable
FrozenArray<Element>?- E.g.,ariaActiveDescendantElement -
Nullable
Element?- E.g.,ariaLabelledByElements,ariaDescribedByElements -
Nullable
DOMString?- E.g.,ariaCurrent,ariaChecked,ariaHidden,ariaLabel,ariaPressed,ariaSort
ARIA IDL attributes that reflect as FrozenArray<Element>? and Element? behave without issue in IDL.
However, taking a closer look at the vast majority of ARIA IDL attributes that currently reflect as DOMString?: this means that the IDL attribute reflects either a null value (a content attribute's absence), or the string value of the corresponding content attribute. This is OK for attributes such as aria-label where the IDL attribute simply string-reflects (e.g., <button aria-label="my accessible button">).
However, like <input>'s type attribute, a large set of ARIA content attributes have a predefined set of values that correspond to unique states. For example, aria-checked supports true, false, mixed. These types of ARIA attributes also currently string-reflect in browsers, e.g.,:
// example HTML file
<button id="my-button" aria-pressed="blah">
...
<script>
let button = document.getElementById("my-button");
console.log(button.getAttribute("aria-pressed")) // "blah", content attribute value
console.log(button.ariaPressed) // "blah", IDL attribute value
</script>Contrast the developer experience for the type attribute (for <input> elements) with the ariaPressed attribute. Notably, browsers aren't currently doing any validation of ARIA IDL DOMString? attributes, unlike the HTML type attribute. This is because many ARIA content attributes that reflect as DOMString?, and that should behave like the type attribute, aren't defined as enumerated attributes per HTML spec with predefined, JavaScript-enforced keywords, states and missing/invalid value defaults.
To improve ARIA's WebIDL, ARIA PR #2484 converts a large swath of ARIA content attributes to be enumerated:
- aria-atomic
- aria-autocomplete
- aria-busy
- aria-checked
- aria-current
- aria-disabled
- aria-expanded
- aria-haspopup
- aria-hidden
- aria-invalid
- aria-live
- aria-modal
- aria-multiline
- aria-multiselectable
- aria-orientation
- aria-pressed
- aria-readonly
- aria-required
- aria-selected
- aria-sort
Doing so will have many benefits including:
- Easier authoring: Authors benefit from normalized, spec-defined values for attributes that better align with HTML's reflection framework.
- Validation and error handling: Browsers normalize invalid and missing values to known defaults.
-
Feature detection: Developers and user agent implementors can reliably check for supported keywords, and easily provide fallback behavior (e.g., a new
diagonalvalue foraria-orientationwould return eitherdiagonalor the invalid value default). - Spec alignment and interoperability: Ensure all browsers handle values the same way.
- Support accessibility: Support greater flexibility for accessibility APIs that handle validation downstream.
- Reduce ambiguity around "undefined": Clarify what happens for "undefined" literal string value assignment, as opposed to an Undefined state (i.e., null).
- More exhaustive testing via WPT: Enables complete test coverage with known permissible values.
- Support future ARIA spec work: New ARIA content attributes will benefit from clear direction in IDL behavior.
Here's a table for all the updated attributes, with keyword, states, and missing/invalid default states:
| Content Attribute | IDL Attribute | Keywords → States | Missing Value Default | Invalid Value Default | Notes |
|---|---|---|---|---|---|
aria-atomic |
ariaAtomic |
"true" → True "false" → False |
null | False | Empty string ("") → False |
aria-autocomplete |
ariaAutoComplete |
"inline" → Inline "list" → List "both" → Both "none" → None |
None | None | |
aria-busy |
ariaBusy |
"true" → True "false" → False |
False | False | Empty string ("") → False |
aria-checked |
ariaChecked |
"true" → True "false" → False "mixed" → Mixed |
Undefined (null) | Undefined (null) | Empty string ("") → Undefined (null) |
aria-current |
ariaCurrent |
"page" → Page "step" → Step "location" → Location "date" → Date "time" → Time "true" → True "false" → False |
False | True | Empty string ("") → False |
aria-disabled |
ariaDisabled |
"true" → True "false" → False |
False | False | Empty string ("") → False |
aria-expanded |
ariaExpanded |
"true" → True "false" → False |
Undefined (null) | Undefined (null) | Empty string ("") → Undefined (null) |
aria-haspopup |
ariaHasPopup |
"true" → True "false" → False "menu" → Menu "dialog" → Dialog "listbox" → Listbox "tree" → Tree "grid" → Grid |
null | False | |
aria-hidden |
ariaHidden |
"true" → True "false" → False |
False | False | Empty string ("") → False |
aria-invalid |
ariaInvalid |
"true" → True "false" → False "spelling" → Spelling "grammar" → Grammar |
False | True | Empty string ("") → False |
aria-live |
ariaLive |
"polite" → Polite "assertive" → Assertive "off" → Off |
Off | Off | |
aria-modal |
ariaModal |
"true" → True "false" → False |
False | False | Empty string ("") → False |
aria-multiline |
ariaMultiLine |
"true" → True "false" → False |
False | False | Empty string ("") → False |
aria-multiselectable |
ariaMultiSelectable |
"true" → True "false" → False |
False | False | Empty string ("") → False |
aria-orientation |
ariaOrientation |
"horizontal" → Horizontal "vertical" → Vertical |
Undefined (null) | Undefined (null) | Empty string ("") → Undefined (null) |
aria-pressed |
ariaPressed |
"true" → True "false" → False "mixed" → Mixed |
Undefined (null) | Undefined (null) | Empty string ("") → Undefined (null) |
aria-readonly |
ariaReadOnly |
"true" → True "false" → False |
False | False | Empty string ("") → False |
aria-required |
ariaRequired |
"true" → True "false" → False |
False | False | Empty string ("") → False |
aria-selected |
ariaSelected |
"true" → True "false" → False |
Undefined (null) | Undefined (null) | Empty string ("") → Undefined (null) |
aria-sort |
ariaSort |
"ascending" → Ascending "descending" → Descending "other" → Other "none" → None |
None | None |
Some attributes allow an empty string assignment which can map to a False state ("false") or Undefined state (null). Note that enumerated ARIA content attributes with "true"/"false" as keywords are boolean-like but do not behave the same way as HTML boolean attributes.
For example, <button disabled=""> evaluates to true for the disabled IDL attribute, whereas <button aria-disabled=""> evaluates to "false" for the ariaDisabled IDL attribute.
How is the "undefined" string being treated for enumerated ARIA attributes? Which attributes support this in ARIA spec, and how does this differ from undefined the value?
When the following ARIA attributes are assigned a literal string value of "undefined", they map to values that align with ARIA spec and the intent of authors:
-
aria-checked: "undefined" maps to Undefined state (null) -
aria-expanded: "undefined" maps to Undefined state (null) -
aria-hidden: "undefined" maps to False state -
aria-orientation: "undefined" maps to Undefined state (null) -
aria-pressed: "undefined" maps to Undefined state (null) -
aria-selected: "undefined" maps to Undefined state (null)
For more details, please see the new section in ARIA PR #2484 titled "Relationship Between the Undefined State and "undefined" String Value".
Where multiple keywords map to a single state, the canonical keyword is the keyword that is returned for reflection purposes. For example, if an enumerated attribute maps the keywords "false" and the empty string to the False state, the canonical keyword would likely be "false".
See HTML Keywords and enumerated attributes.
What about ARIA content attributes that are numeric? Should their IDL attributes be numeric rather than string-reflect?
Ensuring that attributes reflect as the best possible type is beneficial for authors thus, a similar exercise could be performed for improving numeric ARIA attributes.
Although changing the IDL type of numeric ARIA attributes (such as DOMString? to long) is not possible due to webcompat, the ARIA WG could explore providing numeric IDL counterparts to existing numeric attributes. For example:
| Content Attribute | IDL Attribute | Type |
|---|---|---|
aria-colcount |
ariaColCount |
DOMString? |
🆕 ariaColCountNumeric
|
Long |
|
aria-colindex |
ariaColIndex |
DOMString? |
🆕 ariaColIndexNumeric
|
Unsigned Long |
|
aria-colspan |
ariaColSpan |
DOMString? |
🆕 ariaColSpanNumeric
|
Unsigned Long |
|
aria-level |
ariaLevel |
DOMString? |
🆕 ariaLevelNumeric
|
Unsigned Long |
|
aria-posinset |
ariaPosInSet |
DOMString? |
🆕 ariaPosInSetNumeric
|
Unsigned Long |
|
aria-rowcount |
ariaRowCount |
DOMString? |
🆕 ariaRowCountNumeric
|
Long |
|
aria-rowindex |
ariaRowIndex |
DOMString? |
🆕 ariaRowIndexNumeric
|
Long |
|
aria-rowspan |
ariaRowSpan |
DOMString? |
🆕 ariaRowSpanNumeric
|
Long |
|
aria-setsize |
ariaSetSize |
DOMString? |
🆕 ariaSetSizeNumeric
|
Long |