Let's talk about ARIA
When building custom, accessible UI components, you need to use ARIA. There is no alternative - without using ARIA, you can not expose the component state to the browser, which in turn can not expose it to the OS accessibility APIs.
As mentioned in the Handbook's introduction, ARIA provides attributes you can use to provide data for accessibility APIs, supplementing HTML attributes. ARIA consists of roles, states, and properties, which we'll go into more detail soon. In basic terms, it's the web's accessibility API.
While ARIA allows you to enhance accessibility by providing additional information to the browser and the OS, it has a flip side. Using ARIA wrong can have devastating effects on the user experience for some or all users using assistive technologies, which might not be easily noticeable, as ARIA changes are, by default, reflected only in the accessibility tree. In the WAI-ARIA Authoring Practices document, the very first non-introductory section is titled "No ARIA is better than Bad ARIA" and explains the consequences of using ARIA wrong.
Using an ARIA role to expose the role of a UI component
Exposing a role of a DOM element gives assistive technologies information about how to handle that element. HTML tags have a default (implicit) role based on their semantic meaning: div
has no implicit role, article
has the role article
, button
has the role button
, input type="range"
has the role slider
, etc.
In certain cases, however, the default role won't match the element's actual meaning or use. When that occurs, you should provide a single, valid role for the element.
When determining which role to use, we suggest this approach:
- If the component you're building is a common design pattern, check whether implementation guidelines and examples are available in WAI-ARIA Authoring Practices
- If not, refer to MDN ARIA roles documentation or the WAI-ARIA specification.
Take caution when choosing a role. As mentioned previously, setting a wrong role on an element may negatively impact the UX for assistive technology users.
ARIA roles suggest what an element behaves like. For instance, setting a role="button"
attribute on an element that doesn't have a click
event handler set will inform assistive technologies that something will happen when the user activates the button, while in fact - nothing will happen. Setting role="navigation"
on an <ul>
element will change its role from list
, meaning that assistive technologies will not expect <li>
elements as its children (unless their implicit listitem
role is overridden). This is a conformance violation, as it causes an unexpected state for assistive technologies parsing the accessibility tree. Setting role="presentation"
removes semantics.
Always reference documentation to find the appropriate role when overriding an element's implicit role.
Using ARIA state and property attributes to expose the state of a UI component
Using ARIA states and properties allows you to express the dynamic characteristics of an object that may change in response to user action or automated processes. They're mostly the same, with some semantic differences between those terms - the ARIA specification itself refers to both as "attributes".
Properties are attributes that are essential to the nature of a given object or represent a data value associated with the object. A change in a property may significantly impact the meaning or presentation of an object.
A state is a dynamic property expressing characteristics of an object that may change in response to user action or automated processes. States do not affect the essential nature of the object but represent data associated with the object or user interaction possibilities.
One major difference is that the values of properties (such as aria-labelledby) are often less likely to change throughout the application life-cycle than the values of states (such as aria-checked) which may frequently change due to user interaction.
For instance, using the aria-selected
attribute on a tab trigger with the role tab
will provide information on whether or not the tab is active. Using aria-expanded
on an accordion trigger provides information on whether the corresponding accordion panel is expanded or not.
When using vanilla Javascript and shared mutable state, it's common to implement this stateful behavior in one of these ways:
- using state classes such as
is-open
andis-active
to provide state information - using data attributes such as
data-open
ordata-active
to provide state information
You can replace both of these approaches with ARIA attributes. This makes ARIA attributes the source of truth for the component's state, which allows you not to worry about additionally changing ARIA attributes on state changes and gives you a performance edge over using classes.
As CSS supports attribute selectors, e.g. [aria-expanded="true"]
, you can use ARIA attributes and their values in styling and scripting.
For example, you can do things like these:
.accordion-panel {
display: none;
&[aria-expanded='true'] {
display: block;
}
}
const openTabTrigger = tabBlock.querySelector('[aria-selected="true"]');
const openTabId = openTabTrigger.getAttribute('aria-controls');
Using ARIA live regions to announce changes in content
Using ARIA live regions, you can mark whole regions as live and make screen readers and other assistive technologies announce changes in them automatically.
To mark a region as live, use aria-live="polite"
or aria-live="assertive"
. Note that polite
waits for the user to be idle, while assertive
interrupts any announcement in progress and disrupts the users. Therefore, assertive
should be used only for time-sensitive and critical notifications that require the user's immediate attention.
For more information on using ARIA live regions, refer to the MDN article on ARIA live regions.
This is covered in more detail in our discussion of the Status Messages success criterion under "Guidelines - Level AA - Robust".
Tips on ARIA use
Some of these are taken from Notes on ARIA Use in HTML, a part of the Using ARIA document.
Be aware that Firefox doesn't support ARIA properties on the Element interface
You might find that ARIA attributes are available as properties of the Element
interface. (See Element: Properties included from ARIA on MDN)
However, none of them are writeable in Firefox. Read support works, although it's not officially supported. This means that using element.ariaChecked = true
will not change the aria-checked
attribute. You must use setAttribute
to set the values of ARIA attributes.
Avoid custom elements if you can
If a native HTML element is available, it probably provides great accessibility support by default. Using it will save time for you and improve accessibility for users.
There are circumstances in which that isn't possible, such as a feature not being available in HTML or user agents you need to support, or design requirements not being implementable for that component.
Don't change semantics if you don't have to, and make sure semantics make sense when you do
If a native HTML element's semantics make sense, use them without overriding.
However, if you're building components such as tabs, you'll need to override an element's semantics - especially if you're using a non-interactive element (such as a <div>
) as the basis of an interactive element. In those cases, you should use the most appropriate HTML element available.
If you're building a tab, <h2 role="tab">Tab</h2>
makes little sense, while <div role="tab">Tab</div>
has semantics that is in-line with expectations. Use HTML elements with semantics similar to ARIA roles used or an abstract role you can safely override.
role=presentation
or aria-hidden="true"
on focusable elements
Don't use Doing so will result in some users focusing on an element that's invisible to them.
Note that aria-hidden="true"
also applies to an element's children.
Note that elements can have just one role
This means that if you list multiple ARIA roles, only one of them is applied - the very first. An attribute role="tab button"
is interpreted to mean role="tab"
.
A consequence is that setting an ARIA role on an element overrides its implicit role.