The goal of accessibility is to unlock the full potential of the Web and enable people with disabilities to participate equally.
The @angular-eslint
repo contains a number of linting rules that can help enforce accessibility best practices in Angular component templates.
Most of the accessibility rules that are enabled in the Intershop PWA are contained in the plugin @angular-eslint/template/accessibility
that is configured in the .eslintrc.json
file of the project.
To check whether the rules are followed in your custom code or not, run npm run lint
.
Generally it is checked if valid aria-*
and role=*
attributes are used and that every necessary element is reachable with the keyboard, and that an action (like pressing enter) can be performed on them.
If an element has to be made more descriptive by adding a title-attribute or an aria-label, we decided to use the title and not the label, because a title provides visual feedback and can also be read as a label by screen-readers.
❌ Wrong HTML structure, title and aria-label would be read by a screen-reader
<button [title]="Close" [aria-label]="close">
<span>x</span>
</button>
<button [title]="Close" [aria-label]="close">
<span>x</span>
</button>
✔️ Preferred HTML structure
<button [title]="Close">
<span>x</span>
</button>
It is generally advised to use native HTML-elements instead of giving roles to container-elements like a <div>
, because native elements already bring most accessibility functionalities with them, like tab-focus and confirm by pressing enter.
If, for example, instead of a <button>
a <div role="button">
is used, the functionality of pressing enter to activate the button has to be manually implemented via code.
<div role="button">
<span>x</span>
</div>
✔️ Use native HTML-elements if they exist
<button [title]="Close">
<span>x</span>
</button>
ESLint provides a plugin that includes most of the necessary accessibility-rules.
Only some individual rules that do not come with this plugin are specifically written down here.
plugin:@angular-eslint/template/accessibility
For reference on which rules the plugin currently includes, please check the official repository:
@angular-eslint/template/no-positive-tabindex
If an unreachable element has to be made reachable by providing a tabindex
, the index should never be a positive number, only 0
(element is tab focusable) or -1
(element is not tab focusable).
The tab-order has to be determined by the HTML-structure, not by the index.
click-events-have-key-events
problemsTo address this issue, make sure that every <a>
tag in the HTML files includes a routerLink
attribute.
If a link is only intended to trigger an action and not used to navigate the user to another page, it is recommended to use <button>
tags instead of anchor tags, because buttons inherently provide better support for keyboard interactions and enhance overall accessibility.
To make the buttons look like links use the css classes btn btn-link btn-link-action
for text links and btn-tool btn-link
for icon links.
In case you have to use anchor tags nevertheless e.g. because of styling issues make sure you define a keypress.enter action and the tabindex="0" attribute for the anchor tag.
Other HTML elements (<div>
, <span>
, etc.) with a click()
event that report this ESLint error can be fixed by adding a (keydown.enter)
event that should be assigned with the click()
event's method.
In addition, a tabindex="0"
needs to be added to such elements to make them tab focusable.
Implicit form submission using the "Enter" key is vital to assistive technologies, see also HTML5 specification.
Therefore, the form
tag has to include an input
of type="submit"
, for example
<form>
<label for="foo">Name:</label>
<input type="text" name="foo" id="foo" />
<input type="submit" value="Submit" />
</form>
or a button of type "submit"
<form>
<label for="foo">Name:</label>
<input type="text" name="foo" id="foo" />
<button type="submit">Submit</button>
</form>
Dialogs (or modals) are separated into three sections:
where the form is positioned inside the model body and the buttons are positioned inside the modal footer.
The following simplified example shows the wrong HTML structure:
<div class="modal-body">
<form (ngSubmit)="submit()">
<formly-form></formly-form>
</form>
</div>
<div class="modal-footer">
<button type="button" (click)="submit()">Submit</button>
<button type="button" (click)="cancel()">Cancel</button>
</div>
The button with the text "Submit" calls the same function foo()
as the form (ngSubmit)
but the form would not be submitted using the "Enter" key because the submit button is positioned outside the form
tag.
The following example shows the correct HTML structure:
✔️ Correct HTML structure
<form (ngSubmit)="submit()">
<div class="modal-body">
<formly-form></formly-form>
</div>
<div class="modal-footer">
<button type="submit">Submit</button>
<button type="button" (click)="cancel()">Cancel</button>
</div>
</form>
where
form
tag surrounds both the formly form (including the form elements) and the submit buttonsubmit()
is only called at the form
tagtype="submit"
and does not call submit()
using (click)=""
type="button"
to prevent any default behavior