Angular ESLint Rules for ARIA
Tags: angular, a11y, lint, tooling
Published: 2022-11-14 15:16:34

The acronym ARIA stands for **A**ccessible **R**ich **I**nternet **A**pplications. The purpose of ARIA is to make web content more accessible for people with disabilities. ARIA can enhance accessibility in custom-designed user interface controls and components, but incorrect use of ARIA can hinder accessibility and create a worse experience than no ARIA at all. Standard, semantic HTML components don't need ARIA to be accessible. As the saying goes, [no ARIA is better than bad ARIA](https://www.w3.org/WAI/ARIA/apg/practices/read-me-first/).

Advanced use of ARIA is a specific discipline that should be encapsulated into custom UI components that are used to create a consistent experience across an application. Creating such a reusable component library is a common practice in Angular development. Angular ESLint has rules for checking ARIA, but Angular does not add this validation out of the box. This post walks you through how to add Angular ESLint to a component library project, then dives deeper into the ARIA rules. I'll cover what each rule looks for in the Angular template code and how it uses the underlying libraries to validate ARIA roles and attributes.

## Add Angular ESLint to an Angular Library Project
In the [previous post](https://dev.to/angular/angular-eslint-rules-for-keyboard-accessibility-236f) I covered how to add Angular ESLint to an Angular project. This post shows how to create an Angular library project with Angular ESLint support.

To generate a new Angular workspace run the `ng new` schematic without creating the default application: 
```
ng new my-workspace --create-application false
cd my-workspace
```

Add Angular ESLint to the workspace with the `ng add` schematic:
```
ng add @angular-eslint/schematics  
```

Generate an Angular library using Angular ESLint's library schematic that runs the Angular library schematic under the hood then adds support for Angular ESLint:
```
ng generate @angular-eslint/schematics:lib
```

The workspace will have both root and project level eslint configuration. The project `.eslintrc.json` files extend from the root level config, so rules and their options can be set at the global level and added or overridden at the project level. Configure the ARIA template rules under "*.html" overrides:
<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1nq3x4uukqhts8r6axkz.png" alt="code editor showing eslint configuration">
  <figcaption>Root and project level `.eslintrc.json` config</figcaption>
</figure>

<hr>
Next let's take a deeper look at the two ARIA rules from Angular ESLint that are configured in the `.eslintrc.json` above:
* [Valid ARIA](#rule-valid-aria)

    `@angular-eslint/template/accessibility-valid-aria`
* [Role Has Required ARIA](#rule-role-has-required-aria)

    `@angular-eslint/template/accessibility-role-has-required-aria`

## Angular ESLint ARIA Rules
The Angular ESLint ARIA rules run against Angular template code both inline and in separate template files and identify incorrect usage of [WAI-ARIA roles](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles) and their associated ARIA attributes. Ensuring [WAI-ARIA criteria](https://www.w3.org/WAI/WCAG21/Understanding/name-role-value.html) are met enables user agents and assistive technologies to gather information about and interact with web content and controls.

These rules originated from the [eslint-plugin-jsx-a11y](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y) project and were ported over and implemented for Angular ESLint. Angular ESLint and eslint-plugin-jsx-a11y both use the [aria-query](https://github.com/A11yance/aria-query) and [axobject-query](https://github.com/A11yance/axobject-query) libraries to query information about ARIA roles and attributes.

### Rule: Valid ARIA
#### `@angular-eslint/template/accessibility-valid-aria`
The `accessibility-valid-aria` rule checks `aria-*` attributes to be valid ARIA. The rule looks for HTML elements with bound or plain text ARIA attributes. Then it looks up the [ARIA property definition](https://github.com/A11yance/aria-query/blob/main/src/ariaPropsMap.js) from the aria-query library to get the ARIA attribute value type and allowed values. 
 
If aria-query doesn’t have a definition for the matched ARIA attribute, the rule reports an invalid attribute value violation with the suggested fix to remove it:
<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jut5snedknn95djm2vi7.png" alt="code editor showing eslint violation">
  <figcaption>Violation of rule @angular-eslint/template/accessibility-valid-aria: The `aria-control` is an invalid ARIA attribute</figcaption>
</figure>

If the ARIA attribute value is a literal value, it checks the value to be of the defined value type and one of the allowed values if provided. If the ARIA attribute value doesn’t match the ARIA property definition, the rule reports a violation that the attribute’s value is invalid:
<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1byik8yzrykxxxjj8tpa.png" alt="code editor showing eslint violation">
  <figcaption>Violation of rule @angular-eslint/template/accessibility-valid-aria: The `aria-valuenow` has an invalid value</figcaption>
</figure>

The template parser can only check the nodes and static values in the template itself, so a dynamically bound attribute value does not get flagged as a violation:
<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/axxo8s9qp7v37uueiw5f.png" alt="code editor with no eslint violations">
  <figcaption>No violation on bound attribute value</figcaption>
</figure>

<hr>
 
### Rule: Role Has Required ARIA
#### `@angular-eslint/template/accessibility-role-has-required-aria`
_Note_: You must be on at least version [14.2.0](https://github.com/angular-eslint/angular-eslint/blob/main/CHANGELOG.md#1420-2022-11-15) of Angular ESLint to use the `accessibility-role-has-required-aria` rule.

This rule matches HTML elements with a plain text `role` attribute and checks that the given role has the required ARIA attributes using the role definition from the aria-query library. For example, the [switch role definition](https://github.com/A11yance/aria-query/blob/main/src/etc/roles/literal/switchRole.js) has one required property, aria-checked.
 
Using role mappings from the axobject-query library, it determines if the HTML element's semantic role matches the given role. The `role="switch"` can be applied to an `<input type="checkbox"/>` and the required `aria-checked` property is covered by the element’s semantic role. A `<span>` element does not have those same semantic properties and would be flagged as a violation if given the `role="switch"` without `aria-checked`:
<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p3yzpppyutwb0dz7cco0.png" alt="code editor showing eslint violation">
  <figcaption>Violation of rule @angular-eslint/template/accessibility-role-has-required-aria: The span with role="switch" does not have required ARIA properties: aria-checked</figcaption>
</figure>

The complete set of roles with required ARIA properties covered by the `accessibility-role-has-required-aria` rule is as follows:

| WAI-ARIA role | Required ARIA |
| --------- | --------- |
| [checkbox](https://github.com/A11yance/aria-query/blob/main/src/etc/roles/literal/checkboxRole.js) | aria-checked |
| [menuitemcheckbox](https://github.com/A11yance/aria-query/blob/main/src/etc/roles/literal/menuitemcheckboxRole.js) | aria-checked |
| [menuitemradio](https://github.com/A11yance/aria-query/blob/main/src/etc/roles/literal/menuitemradioRole.js) | aria-checked |
| [radio](https://github.com/A11yance/aria-query/blob/main/src/etc/roles/literal/radioRole.js) | aria-checked |
| [switch](https://github.com/A11yance/aria-query/blob/main/src/etc/roles/literal/switchRole.js) | aria-checked |
|  |  |
| [heading](https://github.com/A11yance/aria-query/blob/main/src/etc/roles/literal/headingRole.js)  | aria-level |
|  |  |
| [option](https://github.com/A11yance/aria-query/blob/main/src/etc/roles/literal/optionRole.js) | aria-selected |
| [treeitem](https://github.com/A11yance/aria-query/blob/main/src/etc/roles/literal/treeitemRole.js) | aria-selected |
|  |  |
| [combobox](https://github.com/A11yance/aria-query/blob/main/src/etc/roles/literal/comboboxRole.js) | aria-controls <br> aria-expanded | 
|  |  |
| [meter](https://github.com/A11yance/aria-query/blob/main/src/etc/roles/literal/meterRole.js) | aria-valuenow |
| [slider](https://github.com/A11yance/aria-query/blob/main/src/etc/roles/literal/sliderRole.js) | aria-valuenow |
|  |  |
| [scrollbar](https://github.com/A11yance/aria-query/blob/main/src/etc/roles/literal/scrollbarRole.js) | aria-controls <br> aria-valuenow |


## Suggestion for Adoption
Enabling Angular ESLint's accessibility rules all at once in a codebase with existing accessibility issues can produce a long list of violations that might initially seem overwhelming and difficult to prioritize. Enable these sets of rules incrementally to tackle one category at a time:

1. **Keyboard Accessibility**
    All content and functionality must be usable with the keyboard. Keyboard access is essential for assistive technologies and should be a top priority. The article in this series on [Angular ESLint Rules for Keyboard Accessibility](https://dev.to/angular/angular-eslint-rules-for-keyboard-accessibility-236f) covers the lint rules that can help. You won't be able to automate validation of all keyboard functionality with lint rules or automated testing. Be sure to incorporate [manual interaction testing](https://www.w3.org/WAI/test-evaluate/preliminary/#interaction) into your process to ensure a logical focus order is maintained along with a visible focus ring.

2. **ARIA Roles and Attributes**
    Providing accessible components from a shared library helps standardize your approach to UI/UX and accessibility across your application(s). Isolate most ARIA to your custom UI components and use Angular ESLint's ARIA rules to validate best practices.

3. **Content and Relationships**
    These rules ensure accessibility for non-text content and content relationships in forms and tables. Alternative text should convey the purpose and context for non-text content. Ensuring content accessibility also requires human input and manual review to make sure the content is meaningful and presented consistently. Stay tuned for my last article in this series about the Angular ESLint Accessibility Rules for Content and Relationships.

<hr>
## But Wait, There's More? There Sure Could Be
The eslint-plugin-jsx-a11y library, where these Angular ESLint ARIA rules originate, has even more rules for validating ARIA that could be borrowed from and added to Angular ESLint. These rule additions could also leverage aria-query and axobject-query to validate ARIA usage and encourage better practices with semantic HTML.

### Even More ARIA Rules in eslint-plugin-jsx-a11y
I would also like to implement these rules for Angular ESLint:

* [`aria-role`](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/aria-role.md)

    _Elements with ARIA roles must use a valid, non-abstract ARIA role._
    Valid ARIA roles must be used in order for user agents to understand and interact with those elements according to specifications. [Abstract roles](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles#6._abstract_roles) are the foundational roles upon which other WAI-ARIA roles are built and organized and are not to be used directly.

* [`role-supports-aria-props`](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/role-supports-aria-props.md)

    _Enforce that elements with explicit or implicit roles defined contain only `aria-*` properties supported by that role._
    It does no good to add ARIA attributes to elements with roles that don't support those attributes.

* [`no-aria-hidden-on-focusable`](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/no-aria-hidden-on-focusable.md)

    _Enforce that `aria-hidden="true"` is not set on focusable elements._
    Using [aria-hidden](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-hidden) hides an element and its children from the accessibility API. Elements that aren't accessible to assistive technologies should not be able to receive focus via keyboard.

* [`no-interactive-element-to-noninteractive-role`](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/no-interactive-element-to-noninteractive-role.md)

    _ARIA roles should not be used to convert an interactive element to a non-interactive element._
    This rule prevents interactive elements meant as controls from being assigned non-interactive roles meant for content or containers. A content role such as [meter](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/meter_role), for example, could not be assigned to an `<input>` or `<button>` element.

* [`no-noninteractive-element-to-interactive-role`](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/no-noninteractive-element-to-interactive-role.md)

    _ARIA roles should not be used to convert a non-interactive element to an interactive element._
    Elements meant for content or containers, such as `<main>`, `<h1>`, and `<li>`, should not be assigned interactive roles meant for controls such as the button or checkbox roles.

* [`no-redundant-roles`](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/no-redundant-roles.md)

    _ARIA roles should not redundantly match an element's default/implicit role already set by the browser._
    An `<input type="checkbox">` element already has the implicit role checkbox, so adding `role="checkbox"` would be redundant.

* [`prefer-tag-over-role`](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/prefer-tag-over-role.md)

    _Enforces using semantic DOM elements over the ARIA role property._
    This rule prevents content or container elements like `<span>` or `<div>` from being assigned roles like [banner](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/banner_role) or [heading](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/heading_role) where a built-in, semantic HTML element is preferable.

🎉 Shout out in the comments if you think having these rules in Angular ESLint would improve accessibility practices in your Angular codebase!

<hr>
**References:**
* [ARIA Authoring Practices Guide](https://www.w3.org/WAI/ARIA/apg/practices/read-me-first/) on W3C Web Accessibility Initiative
* [Easy Checks – A First Review of Web Accessibility](https://www.w3.org/WAI/test-evaluate/preliminary) on W3C Web Accessibility Initiative
* [WAI-ARIA roles](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles) on MDN
* [Understanding Success Criterion 4.1.2: Name, Role, Value](https://www.w3.org/WAI/WCAG21/Understanding/name-role-value.html) on W3C's Understanding WCAG 2.1
* [eslint-plugin-jsx-a11y](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y) on GitHub
* [aria-query](https://github.com/A11yance/aria-query) on GitHub
* [axobject-query](https://github.com/A11yance/axobject-query) on GitHub