Angular ESLint Rules for Accessible HTML Content
Tags: angular, a11y, lint, tooling
Published: 2023-01-10 19:14:04

Content accessibility for built-in HTML elements is the third and final category in this series on Angular ESLint accessibility rules. These rules validate several HTML attributes that developers commonly overlook regarding accessibility. They check for alt text on images, accessible content in buttons, links, and headings, and proper form labels and table header associations. Angular ESLint accessibility rules provide immediate guidance on accessibility best practices right in the code, resulting in more accessible and user-friendly Angular applications.

# The Rules
Previous articles in this series discussed how to add Angular ESLint and configure these rules in `.eslintrc.json` config files, so let's get straight into the rules:

* [Alt Text](#rule-alt-text)

    `@angular-eslint/template/accessibility-alt-text`
* [Elements Have Content](#rule-elements-have-content)

    `@angular-eslint/template/accessibility-elements-content`
* [Label Has Associated Control](#rule-label-has-associated-control)

    `@angular-eslint/template/accessibility-label-has-associated-control`

* [Table Scope](#rule-table-scope)

    `@angular-eslint/template/accessibility-table-scope`

* [No Distracting Elements](#rule-no-distracting-elements)

    `@angular-eslint/template/no-distracting-elements`

* [Button Has Type](#bonus-rule-button-has-type)

    `@angular-eslint/template/button-has-type`

---

## Rule: Alt Text
[**`@angular-eslint/template/accessibility-alt-text`**](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin-template/docs/rules/accessibility-alt-text.md)

The `accessibility-alt-text` rule validates that all images have alternative text. Images must have an `alt` attribute to meet [WCAG 2.1 Success Criterion 1.1.1: Non-text Content](https://www.w3.org/WAI/WCAG21/Understanding/non-text-content.html) that states all non-text content should have a text alternative that serves the equivalent purpose.

Follow these guidelines for providing meaningful and concise `alt` text:
* Alt text should express the relevant detail in an image to stand in for the same purpose, meaning, and intent.
* Alt text should not be redundant or repeat information from the image caption.
* Alt text should be short and to the point, ideally 150 characters or less.

When there is no `alt` attribute on an `<img>`, some screen readers will announce the image `src` instead. Including an empty `alt` attribute for decorative images indicates to screen readers that these images do not convey additional meaning or information:
```html
<img alt="" src="decorative.gif">
```

In addition to checking for the `alt` attribute on `<img>` elements, the `accessibility-alt-text` rule also validates `<input type="image">`, `<area>`, and `<object>` elements, which support these attributes as alternative text:

| HTML element | alternative text attributes |
| --------- | --------- |
| [`<img>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img) | `alt` |
| [`<input type="image">`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/image) | `alt`, `aria-label`, `aria-labelledby` |
| [`<area>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/area) | `alt`, `aria-label`, `aria-labelledby` |
| [`<object>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/object) | `aria-label`, `aria-labelledby`, `title` |

---

## Rule: Elements Have Content
[**`@angular-eslint/template/accessibility-elements-content`**](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin-template/docs/rules/accessibility-elements-content.md)

The `accessibility-elements-content` rule ensures that `<a>`, `<button>`, and `<h1>` - `<h6>` elements have content. Screen readers announce these elements by their role and name (and level for headings.) Links, buttons, and headings are semantic elements with inherent meaning and implicit roles, and the [accessible name](https://www.w3.org/WAI/ARIA/apg/practices/names-and-descriptions/) for these elements comes from their content, title, or aria-label.

A button, link, or heading name may also come from a child element with plain text content, a title, or aria-label. Don't add redundant and unnecessary aria-labels where the accessible name comes from a child element. Use the [Accessibility pane in Chrome DevTools](https://developer.chrome.com/docs/devtools/accessibility/reference/#pane) to inspect an element's computed name:

![Accessibility pane in Chrome DevTools showing button name computed from SVG title](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ze46w6rhfu4ze6otr0o3.png)

---

## Rule: Label Has Associated Control
[**`@angular-eslint/template/accessibility-label-has-associated-control`**](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin-template/docs/rules/accessibility-label-has-associated-control.md)

The HTML [`<label>`](https://www.w3schools.com/tags/tag_label.asp) element gives an accessible name to form controls. Labels are important for helping users understand the purpose of `<input>`, `<select>`, `<textarea>`, `<output>`, `<meter>`, and `<progress>` elements.

* A control can have more than one `<label>`.
* A `<label>` cannot be associated with more than one control.
* Do not use `<label>` independently without an associated control.

A `<label>` is associated with a form control with either an implicit or explicit association, and both styles are widely supported. Nesting a control inside `<label>` creates an **implicit** association between the label and control:
```html
<label>
  <input type="checkbox">
  Blue
</label>
```

Whereas, assigning an `id` to the control and using a matching `for` attribute on the `<label>` creates an **explicit** association:
```html
<label for="city">City</label>
<input id="city" type="text">
```

The `accessibility-label-has-associated-control` rule determines if a label has an implicit association with a control nested inside the label or if the label has the `for` attribute. The rule does not look elsewhere in the template for a control with an id matching the label's `for` attribute and does not validate explicit label and control pairings. This is a natural limitation of static code analysis tools like Angular ESLint, so you’ll want to use additional testing techniques to validate the compiled application. The `for` attribute could be a bound value, and Angular ESLint template rules do not validate bound values. Rules are generally limited to validating individual template nodes and their children, and the control could even be in another component's template separate from the label.

### Configuration Options for Label Has Associated Control

The `accessibility-label-has-associated-control` rule supports the [configuration options](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin-template/docs/rules/accessibility-label-has-associated-control.md#rule-options) `labelComponents` and `controlComponents`. The `labelComponents` option adds validation for custom label components. The `controlComponents` option configures the rule to recognize an implicit association on custom controls nested inside a label.

#### Option: Custom Label Components
Configure `"labelComponents"` for `"my-custom-label"` with `"forControl"`:
```json
"@angular-eslint/template/accessibility-label-has-associated-control": [
  "error",
  {
    "labelComponents": [
      {
        "selector": "my-custom-label",
        "inputs": ["forControl"]
      }
    ]
  }
]
```

Validation on a custom label component to either require the `forControl` input or to have a nested control:
```html
<my-custom-label forControl="myId">
  Custom
</my-custom-label>
<input id="myId" type="text"/>

<my-custom-label>
  Custom
  <input type="text"/>
</my-custom-label>
```

#### Option: Custom Control Components
Configure `"controlComponents"` for `"my-custom-control"`:
```json
"@angular-eslint/template/accessibility-label-has-associated-control": [
  "error",
  {
    "controlComponents": ["my-custom-control"]
  }
]
```

Validation on a custom control nested inside a label with an implicit association:
```html
<label>
  Custom
  <my-custom-control></my-custom-control>
</label>
```

---

## Rule: Table Scope
[**`@angular-eslint/template/accessibility-table-scope`**](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin-template/docs/rules/accessibility-table-scope.md)

The `accessibility-table-scope` rule validates the `scope` attribute for table headers. The `scope` attribute specifies which cells belong with a table header (`<th>`.) Table header `scope` accepts the following values: 

* **row**: associates a table header with all the cells in that row.
* **col**: associates a table header with all the cells in that column.
* **rowgroup**: associates a table header that spans multiple rows with all the cells in that row group.
* **colgroup**: associates a table header that spans multiple columns with all the cells in that column group.

When table header scope is not specified, browsers and assistive technologies infer the relationship between table headers and their cells. Tables with headers in a single row or column do not need the `scope` attribute. [More complex tables](https://www.w3.org/WAI/tutorials/tables/) with irregular, multi-level, or both row and column headers should explicitly define which cells belong to which headers by using the `scope` attribute on their table headers or the `id` attribute on table headers with the `headers` attribute on table cells.

---

## Rule: No Distracting Elements
[**`@angular-eslint/template/no-distracting-elements`**](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin-template/docs/rules/no-distracting-elements.md)

The `no-distracting-elements` rule disallows usage of the `<blink>` and `<marquee>` elements, which are both deprecated and no longer recommended for use.

The `<blink>` element is more than distracting. Flashing content can trigger seizures in people with photosensitive epilepsy. [WCAG 2.1 Success Criterion 2.3.1](https://www.w3.org/WAI/WCAG21/Understanding/three-flashes-or-below-threshold.html) dictates that there are no more than three flashes in a one second period or that the flash is below the threshold.

The scrolling content in a `<marquee>` element can create barriers for anyone who struggles with moving objects or people with cognitive disabilities like attention deficit disorder. [WCAG 2.1 Success Criterion 2.2.2](https://www.w3.org/WAI/WCAG21/Understanding/pause-stop-hide.html) states that users must be able to stop or hide any moving, blinking, scrolling, or auto-updating information.

---

## Bonus Rule: Button Has Type
[**`@angular-eslint/template/button-has-type`**](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin-template/docs/rules/button-has-type.md)

I mention this last rule as a bonus because it is not specifically an accessibility rule but is commonly missed and can lead to surprising functionality. The `button-has-type` rule checks for the `type` attribute on HTML `<button>` elements. A `<button>` inside a `<form>` without a `type` acts as a submit button and submits the form when pressed:
```html
<form>
  <label><input type="checkbox">Yes</label>

  <button>Submits the Form</button>

  <button type="button">Not a Submit</button>

  <button type="reset">Resets the Form</button>

  <button type="submit">Submits the Form</button>
</form>
```

---

# _That's All, Folks!_
The Angular ESLint rules covered in this series enable developers to create more accessible and user-friendly Angular applications by helping to ensure keyboard accessibility, ARIA compliance, and accessible HTML content. The [first article](https://dev.to/angular/angular-eslint-rules-for-keyboard-accessibility-236f) discusses rules for ensuring that all interactive elements are reachable with a keyboard and that focus is not improperly managed. The [second article](https://dev.to/angular/angular-eslint-rules-for-aria-3ba1) discusses Angular ESLint rules to check that ARIA roles have the required attributes and that ARIA attributes are valid. This third and final article discusses Angular ESLint rules to ensure accessible HTML content, such as image alt text, accessible names for form controls, buttons, links, and headings, and table heading scope. That covers all the rules pertaining to accessibility available with Angular ESLint.

Static code analysis with Angular ESLint has the advantage of identifying issues early in development, but performing further automated and manual browser-based testing is essential for ensuring full accessibility. These Angular ESLint accessibility rules inspect individual nodes and elements used in the template code. It is also important to validate accessibility within the context of the entire application to check that headings are properly nested, that there are no nested interactive controls, that landmarks like `<header>`, `<aside>`, `<nav>`, and `<main>` are used and labeled correctly, etc. Incorporating accessibility testing into each stage of the development and release process ensures that our Angular applications are usable by a wide range of users, including those with disabilities.

**Recommended Testing Tools and Libraries:**
* [Lighthouse in Chrome DevTools](https://developer.chrome.com/docs/lighthouse/overview/#devtools)
* [Accessibility pane in Chrome DevTools](https://developer.chrome.com/docs/devtools/accessibility/reference/#pane)
* [WebAIM Wave Browser Extension](https://wave.webaim.org/extension/)
* [Axe DevTools Browser Extension](https://chrome.google.com/webstore/detail/axe-devtools-web-accessib/lhdoppojpmngadmnindnejefpokejbdd)
* [Angular Testing Library](https://testing-library.com/docs/angular-testing-library/intro/)
* [AxePuppeteer](https://github.com/dequelabs/axe-core-npm/blob/develop/packages/puppeteer/README.md) for Puppeteer
* [Cypress-axe library](https://github.com/component-driven/cypress-axe) for Cypress

**References:**

* [Understanding SC 1.1.1:Non-text Content (Level A)](https://www.w3.org/WAI/WCAG21/Understanding/non-text-content.html) on W3C's Understanding WCAG 2.1
* [Decorative Images](https://www.w3.org/WAI/tutorials/images/decorative/) on W3C Web Accessibility Initiative
* [Providing Accessible Names and Description](https://www.w3.org/WAI/ARIA/apg/practices/names-and-descriptions/) on W3C Web Accessibility Initiative ARIA Authoring Practices Guide
* [Tables Tutorial](https://www.w3.org/WAI/tutorials/tables/) on W3C Web Accessibility Initiative
* [Understanding SC 2.3.1:Three Flashes or Below Threshold (Level A)](https://www.w3.org/WAI/WCAG21/Understanding/three-flashes-or-below-threshold.html) on W3C's Understanding WCAG 2.1
* [Understanding SC 2.2.2:Pause, Stop, Hide (Level A)](https://www.w3.org/WAI/WCAG21/Understanding/pause-stop-hide.html) on W3C's Understanding WCAG 2.1