Linting Custom Components with the REST endpoint
A walkthrough for using axe DevTools Linter for linting custom components with the REST endpoint
This article shows how to use the axe DevTools Linter REST endpoint to find accessibility errors in custom components.
This article is for users of the axe DevTools Linter REST endpoint. If you use the axe Accessibility Linter extension for VS Code or the plugin for JetBrains, see Linting Custom Components with the axe Accessibility Linter Extension for VS Code or the Plugin for JetBrains for more information.
Prerequisites
You will need access to either the SaaS version or an on-premises version of axe DevTools Linter. See Obtaining an axe DevTools Linter SaaS API Key or Setting up the On-Premises Edition of axe DevTools Linter for more information.
You will also need a REST tool that:
- Can send POST requests
- Can add
Authorization
headers (for the SaaS version of axe DevTools Linter) - Allows JSON request bodies to be created
A Custom Component Linting Walkthrough
When you use axe DevTools Linter to lint source code, you provide a JSON body containing the source and configuration in your HTTP request. For example, the following HTML shows the use of the img
element:
<img src="path/to/image.jpg"/>
(This is a greatly oversimplified example only to demonstrate linting rather than an actual real-world example.)
The JSON body of the request to be sent to axe DevTools Linter would look like this:
{
"source": "<img src=\"path/to/image.jpg\"/>",
"filename": "image-demo.html"
}
You send this JSON to axe DevTools Linter as a REST POST request to the /linter-source
endpoint. For more information, see The Lint Endpoint in the reference documentation.
To follow along with this walkthrough, you can use any REST tool that can send POST requests with JSON request bodies. The following examples show the request body sent to axe DevTools Linter and the JSON response body, which shows the accessibility errors found by axe DevTools Linter.
Because this img
element doesn't have an alt
attribute, you'll get an accessibility error from axe DevTools Linter:
{
"report": {
"errors": [
{
"column": 1,
"description": "Ensures <img> elements have alternate text or a role of none or presentation",
"endColumn": 31,
"helpURL": "https://dequeuniversity.com/rules/axe/4.4/image-alt?application=axe-linter",
"lineContent": "<img src=\"path/to/image.jpg\"/>",
"lineNumber": 1,
"linterType": "html",
"ruleId": "image-alt"
}
]
}
}
A Custom Image Component
The following sample shows an example of a custom-image
custom component:
<custom-image src="path/to/image.jpg"></custom-image>
To send the HTML to axe DevTools Linter using a POST request, use the following as the JSON body:
{
"source": "<custom-image src=\"path/to/image.jpg\"></custom-image>",
"filename": "custom-image.html"
}
The server responds with no accessibility errors because there is no mapping between custom-image
and img
, so axe DevTools Linter cannot flag a missing alt
attribute:
{
"report": {
"errors": []
}
}
Mapping custom-image
to img
If you provide a mapping between custom-image
to img
, axe DevTools Linter can map your custom component as a standard HTML element and locate accessibility errors. You can specify the mapping by using the global-components
configuration option (part of the config
object):
{
"config": {
"global-components": {
"custom-image": "img"
}
},
"filename": "c-image.html",
"source": "<custom-image src=\"path/to/image.jpg\"></custom-image>\n\n"
}
axe DevTools Linter now responds with the following:
{
"report": {
"errors": [
{
"column": 1,
"description": "Ensures <img> elements have alternate text or a role of none or presentation",
"endColumn": 54,
"helpURL": "https://dequeuniversity.com/rules/axe/4.4/image-alt?application=axe-linter",
"lineContent": "<custom-image src=\"path/to/image.jpg\"></custom-image>",
"lineNumber": 1,
"linterType": "html",
"ruleId": "image-alt"
}
]
}
}
You can also indicate the same mapping as above with either of these syntaxes:
{
"config": {
"global-components": {
"custom-image": {
"element": "img"
}
}
}
}
Or, alternatively, by abbreviating element
as el
:
{
"config": {
"global-components": {
"custom-image": {
"el": "img"
}
}
}
}
When you use an element mapping as shown above, all the attributes from the custom component are copied to the emitted element, and that emitted element is linted.
Fixing the Accessibility Problem
You can add an alt
attribute to your custom-image
to fix the accessibility problem (as shown below with the JSON body of the request):
{
"config": {
"global-components": {
"custom-image": "img"
}
},
"filename": "c-image.html",
"source": "<custom-image src=\"path/to/image.jpg\" alt=\"alt text\"></custom-image>\n\n"
}
The server responds with the following empty errors
array because your custom component has the required alt
attribute (which was copied—with all other attributes on the custom-image
component—to the emitted img
element):
{
"report": {
"errors": []
}
}
Mapping the alternative-text
Attribute
If your custom image component instead uses a different attribute to indicate alternative text, you can specify that attribute in the configuration. For instance, suppose your custom-image
component uses an alternative-text
attribute instead of alt
, as shown below:
<custom-image src="path/to/image.jpg" alternative-text="alt text"></custom-image>
In this case, you could specify a mapping between the alternative-text
attribute and the alt
attribute as shown with the attributes
array in the JSON request body shown below:
{
"config": {
"global-components": {
"custom-image": {
"element": "img",
"attributes": [
{
"alternative-text": "alt"
}
]
}
}
},
"filename": "c-image.html",
"source": "<custom-image src=\"path/to/image.jpg\" alternative-text=\"alt text\"></custom-image>\n\n"
}
Note that the global-components
configuration slightly differs from the earlier mapping of one custom component to one HTML element. With only elements, you use a mapping from a string ("custom-image") to another string ("img"). With the inclusion of the attributes
array, you are now required to use the element
(or el
) property to specify the emitted HTML element.
axe DevTools Linter responds with the following because the alt-text
rule has been satisfied by your alternative-text
attribute:
{
"report": {
"errors": []
}
}
Because you specified the attributes
array in the configuration, when the server maps from custom-image
to img
, only the attributes specified in the attributes
array are copied to the emitted HTML element.
You can also abbreviate attributes
as attrs
:
{
"config": {
"global-components": {
"custom-image": {
"attrs": [
{
"alternative-text": "alt"
}
],
"element": "img"
}
}
}
}
Special Attribute Values: <text>
, aria-*
and <element>
Suppose you use a custom-button
component as follows:
<custom-button aria-controls="expand-region" aria-expanded="false" aria-colindex="1" message="Show Region"></custom-button>
(The custom button will, using JavaScript and CSS that isn't included here, hide and show a div
.)
There are two problems with this usage:
- If you map this
custom-button
component directly to abutton
element, there will be no text content to be displayed on the button. The component author's intent, however, is that themessage
attribute should be used as text content:<button>
value of themessage
attribute</button>
- The emitted
button
element has an implicit role ofbutton
, so thearia-colindex
attribute is incorrect and should be removed.
If you send the above code to axe DevTools Linter (with no global-components
mapping), you receive this response:
{
"report": {
"errors": []
}
}
The Special <text>
Value
To address the first problem (text content for the button
element coming from a message
attribute), you can use the special <text>
value which maps an attribute to the emitted element's text content. In this case, the message
attribute's text should be copied to the emitted button
element's text content.
To configure the request to alert axe DevTools Linter that the message
attribute should be considered as text content for the HTML button
element, you can use the special <text>
value and send the following request:
{
"config": {
"global-components": {
"custom-button": {
"attributes": [
{
"message": "<text>"
}
],
"element": "button"
}
}
},
"filename": "aria-button.html",
"source": "<custom-button aria-controls=\"expand-region\" aria-expanded=\"false\" aria-colindex=\"1\" message=\"Show Region\"></custom-button>\n"
}
Because you defined the message
attribute as <text>
, you told axe DevTools Linter to consider that attribute as replacing textual content of the HTML button
element with the value of the message
attribute.
Unfortunately, by using the attributes
array, the only attribute that was passed through to the emitted button
element was only the message
attribute; any attributes not in the attributes
array are not passed through. This means that the incorrect aria-colindex
was not caught by the server.
Using aria-*
You can use the special aria-*
value to pass all the ARIA attributes, as shown below:
{
"config": {
"global-components": {
"custom-button": {
"attributes": [
{
"message": "<text>"
},
"aria-*"
],
"element": "button"
}
}
},
"filename": "aria-button.html",
"source": "<custom-button aria-controls=\"expand-region\" aria-expanded=\"false\" aria-colindex=\"1\" message=\"Show Region\"></custom-button>\n"
}
The server responds with:
{
"report": {
"errors": [
{
"column": 1,
"description": "Ensures ARIA attributes are allowed for an element's role",
"endColumn": 124,
"helpURL": "https://dequeuniversity.com/rules/axe/4.4/aria-allowed-attr?application=axe-linter",
"lineContent": "<custom-button aria-controls=\"expand-region\" aria-expanded=\"false\" aria-colindex=\"1\" message=\"Show Region\"></custom-button>",
"lineNumber": 1,
"linterType": "html",
"ruleId": "aria-allowed-attr"
}
]
}
}
This error occurs because the button
element has an implicit role="button"
and using aria-colindex
is invalid with buttons. With aria-*
, all ARIA attributes are copied to the emitted element; this includes copying the invalid aria-colindex
attribute.
<element>
With complex components, you might want to emit a different HTML element than the default element in specific cases. For instance, you might have a button component that typically behaves like a button and, in other states, like a placeholder image. The <element>
value lets you specify an attribute on your custom component that determines the emitted element.
{
"config": {
"global-components": {
"my-button": {
"element": "button",
"attributes": [
{
"use": "<element>"
},
'src',
'alt'
]
}
}
},
"filename": "aria-button.html",
"source": "<my-button use=\"img\" src=\"globe.jpg\"></my-button>"
}
In this case, the use
attribute on the my-button
component indicates the element to emit. Because the emitted img
element doesn't contain an alt
attribute, you'll receive an error:
{
"report": {
"errors": [
{
"ruleId": "image-alt",
"helpURL": "https://dequeuniversity.com/rules/axe/4.10/image-alt?application=axe-linter",
"description": "Images must have alternative text",
"lineNumber": 1,
"column": 1,
"linterType": "html",
"lineContent": "<my-button use=\"img\" src=\"globe.jpg\"></my-button>",
"endColumn": 50
}
]
}
}
Passing All Attributes Implicitly
Note that if you'd used only element mapping (where the mapping does not use the attributes
array), all attributes would be, by default, copied to the button
element as shown below:
{
"config": {
"global-components": {
"custom-button": "button"
}
},
"filename": "aria-button.html",
"source": "<custom-button aria-controls=\"expand-region\" aria-expanded=\"false\" aria-colindex=\"1\" message=\"Show Region\"></custom-button>\n"
}
With this server response, you can see that all of the attributes from custom-button
are checked for accessibility problems, and two errors are found:
{
"report": {
"errors": [
{
"column": 1,
"description": "Ensures ARIA attributes are allowed for an element's role",
"endColumn": 124,
"helpURL": "https://dequeuniversity.com/rules/axe/4.4/aria-allowed-attr?application=axe-linter",
"lineContent": "<custom-button aria-controls=\"expand-region\" aria-expanded=\"false\" aria-colindex=\"1\" message=\"Show Region\"></custom-button>",
"lineNumber": 1,
"linterType": "html",
"ruleId": "aria-allowed-attr"
},
{
"column": 1,
"description": "Ensures buttons have discernible text",
"endColumn": 124,
"helpURL": "https://dequeuniversity.com/rules/axe/4.4/button-name?application=axe-linter",
"lineContent": "<custom-button aria-controls=\"expand-region\" aria-expanded=\"false\" aria-colindex=\"1\" message=\"Show Region\"></custom-button>",
"lineNumber": 1,
"linterType": "html",
"ruleId": "button-name"
}
]
}
}
The above example shows that a practical first step when beginning to lint custom components would be to start with an element mapping (thereby copying all attributes to the emitted, standard HTML element) and then seeing what attributes need to be added to the configuration (whether the custom component's attributes should be mapped to different attributes or whether you need to use <text>
or aria-*
).
Default Attributes
Default attributes let you set values for attributes in your configuration file rather than map one attribute to another. For example, the following sample configuration shows a custom-menu
component mapped to an li
element with a role
of menu:
{
"config": {
"global-components": {
"custom-menu": {
"element": "li",
"attributes": [
{
"role": {
"name": null,
"default": "menu"
}
}
]
}
}
}
}
Because the role
attribute has a default value of menu, set in the configuration file, users do not need to specify a role
attribute when they use the custom-menu
component in their code. The implication is that your custom component's implementation creates these attributes on the output element and sets their values rather than requiring users to set them when they use your component.
Optionally, the name
value is set to null
in the configuration, which causes axe DevTools Linter to ignore any role
attributes that users have specified on custom-menu
in linted code.
The value specified with default
should be a string.
See Also
- axe DevTools Linter REST API Reference contains more information on using the various REST endpoints provided by axe DevTools Linter.
- Obtaining an axe DevTools Linter SaaS API Key shows how to obtain an API key to use the Software as a Service (SaaS) version of axe DevTools Linter.