My Priority of Methods for Labeling a Control

Here is the priority I follow when assigning an accessible name to a control:

  1. Native HTML techniques,
  2. aria-labelledby pointing at existing visible text,
  3. Visibly-hidden content that is still in the page,
  4. aria-label.

Too often folks will grab ARIA first to provide an accessible name for a thing. Or they may sprinkle hidden content around a form. Sometimes this is to satisfy a (minimalist?) design, other times it is just part of the tooling. In most cases the impact of those decisions is unknown. The assumption that they do the same thing, give the same output to all users, is wrong.

The priority I follow is intended to honor ARIA best practices and internationalization concerns. I also gathered feedback on the Twitters (which is just anecdata).

This does not talk about hint text (such as something defined by aria-describedby), also known as an accessible description.

Native HTML

This includes using a <label> for form elements, or the value attribute (for <input type="submit">) or inner text (for <button>).

It is well-understood by browsers, with more than two decades of support. It provides a larger hit area when tapping or clicking an associated field. Screen readers understand the pairing. Voice recognition tools understand it for selecting fields.

Well, one caveat on that last one. Dragon does not like fields wrapped in <label>. If you are old-school you probably remember when Internet Explorer did not like it either. So do this:

<label for="Name">Name:</label>
<input type="text" id="Name">

Instead of this:

<label>
   Name:
   <input type="text">
</label>

Content in <label> also gets picked up by all translation services (Google, Bing, some older ones whose names I have forgotten but used years ago). If you have reason to build screens with mixed language, <label>, <input type="submit">, <button>, and other controls will happily take a lang attribute.

aria-labelledby with Visible Text

This relies on ARIA, but by associating visible text on the page all users can at least access it. aria-labelledby works as the selectable name for voice control tools (on Windows, I am still testing on iOS and macOS). aria-labelledby can be translated and the container of the referenced text can (almost definitely) take the lang attribute in the case of mixed language content.

aria-labelledby will accept a space-separated list of id references, meaning you can build a more robust accessible name from disparate pieces of text in the page, including the control itself. I demonstrate this technique in my post Uniquely Labeling Fields in a Table and paste a couple code samples here:

<td>
  <input type="text" id="c01" aria-labelledby="Row01 ColClaimed">
</td>
<td>
  <button type="button" id="b01" aria-labelledby="b01 Row01">Remove</button>
</td>

It does not increase the hit area of a control when used as its visible label. Some older screen reader and browser pairings (much older) that pre-date broad support for aria-labelledby will struggle.

Perhaps the biggest risk here is that aria-labelledby overrides all the other methods for providing an accessible name. This sometimes surprises developers who throw it on a control, overriding an associated <label>.

Visibly Hidden Content

This is generally achieved by taking plain text on the page and hiding it off-screen somehow. Often this is done with text that is already associated with a control, such as a <label>, content referenced by aria-labelledby, or the inner text of a <button> or link (think icon fonts). You may see this in the wild with a sr-only or visually-hidden class.

In cases where CSS is used to provide icons or other information (via ::after or background images), when the CSS breaks this information may be lost. The hidden text would also become visible (if the hiding technique is in the same CSS resource), making this a viable approach when supporting progressive enhancement.

This content will still be translated and can still accept a lang attribute (by putting it on the container hiding it).

The only users who will know it is there are screen reader users, however. It is not visible. It does not help the click/tap size of the control. It is not available to voice users to select a control. It cannot be copied.

Hidden text is also used too casually to provide information for just screen reader users, creating overly-verbose content. For sighted screen reader users, it can be a frustrating experience to not be able to find what the screen reader is speaking, potentially causing the user to get lost on the page while visually hunting for it.

If the wrong technique is used to hide content it can have additional problems. Content hidden solely by positioning it outside the viewport (left:-9999px) can create confusing scrollbars or come into view if the page is translated. Content confined to a tiny box can be spoken letter by letter if not addressed. The styles you use must take these issues into account.

The CSS I use:

/* Method to visually hide something but still */
/* keep it available to screen readers */
.visually-hidden {
  position: absolute;
  top: auto;
  overflow: hidden;
  clip: rect(1px 1px 1px 1px); /* IE 6/7 */
  clip: rect(1px, 1px, 1px, 1px);
  width: 1px;
  height: 1px;
  white-space: nowrap;
}

You can use :not() to avoid accidentally hiding things that have focus or are active, if you are targeting modern browsers only:

.visually-hidden:not(:focus):not(:active) {
  position: absolute;
  overflow: hidden;
  clip: rect(0 0 0 0); 
  clip-path: inset(50%);
  width: 1px;
  height: 1px;
  white-space: nowrap; 
}

Note there are some other changes beside the selector.

I encourage you to read Scott O’Hara’s post Inclusively Hidden.

aria-label

aria-label overrides other methods for providing an accessible name, except aria-labelledby. Since the text is not visible a developer can forget it is there, overriding an associated <label>, making it potentially riskier than aria-labelledby‘s override.

aria-label is a challenge for internationalization. You cannot count on content in aria-label to be auto-translated (and in my experience is often missed in manual translation). It definitely cannot take a lang attribute on its own, meaning you have to put lang on the control itself, which may or may not be what you want.

The only users who will know it is there are screen reader users. It is not visible. It does not help the click/tap size of the control. It is not available to voice users to select a control. It cannot be copied.

It can also too easily be used to create overly-verbose content. It can be confusing in the wrong context for sighted screen reader users.

Sometimes aria-label is misunderstood to the point that it is used for hints, without understanding it will confuse screen reader users and strand voice users, ie:

Only those with <a href="[…]" aria-label="Learn more about permission levels">write access</a> to this repository can merge pull requests.

Similarly, avoid constructs / copy-paste errors like the following:

<label for="foo">First Name:</label>
<input type="text" id="foo" aria-label="Address Line 2">

Unlike visibly hidden content, aria-label will not become visible when the CSS or script for the page fails to load (this is neither good nor bad, but worth remembering).

Exceptions

Yes, there are exceptions.

I am talking about interactive controls, though some of these rules apply to non-interactive controls (such as landmarks or <iframes>). The ARIA approaches won’t work for other elements (this is a good thing) unless they have an appropriate role applied (this is generally only a good thing for complex widgets).

You may also have restrictive business cases (change them?), build processes (rebuild them?), or technologies (replace them?) that give you little control over what options are used in various contexts.

Conversely, you may have run tests with your own users (yay!) that demonstrate one approach is better than another. Use that one. Just be careful about extrapolating out to all cases.

This post does not mean I am right. I am, however, happy to be proven wrong or shown other cases. Other readers of this post may benefit from your comments.

Update: 24 August 2020

Léonie Watson has posted The difference between aria-label and aria-labelledby where she offers a bit of a decision tree for choosing between them.

Update: 16 July 2021

This post talked about giving an accessible name to a control. Other things get accessible names too, and Scott O’Hara breaks down the Accessibility of the section element.

It mostly reinforces the pointlessness of the <section> element, until it gets an accessible name (and even then, Safari misbehaves in one case).

Update: 7 November 2022

Eric Bailey has written something I have been saying for years — aria-label is a code smell for larger problems or misunderstandings. He hits the points I cover above and elsewhere on this site.

Update: 27 August 2024

Lloydi has re-ordered my list a bit in Context is king: long live the king!. He pushes the ARIA approaches to last and moves the visually-hidden text up to the second spot.

His approach is neither righter nor wronger. It comes down to what works for your users in the context. Which fits with the title of his post.

Hidden at the end in a disclosure widget is a tool to generate the HTML and CSS for each method if you provide it with the appropriate text that would act as the labels.

5 Comments

Reply

Curious about this: “[aria-label] is not available to voice users to select a control.”

By “not available” do you mean not visible? I thought voice control would work with it, as it should be able to locate the control based on its accessible name (however it’s derived). Hence WCAG 2.5.3.

In response to James. Reply

I mean not visible as well as the fact that not all voice control (side-eyes Dragon) honors aria-label to generate the accessible name. Also, in my own testing so far (not posted yet), native voice control seems squirrely on this.

2.5.3 does not apply if there is no visible text at all, but if there is any visible text then 2.5.3 kicks in and using aria-label is likely redundant.

In response to Adrian Roselli. Reply

Didn’t realize that Dragon did that. It kinda makes sense to me though, given how many sites out there have mismatched aria-labels slapped on things by authors who don’t know how it works (and don’t know about 2.5.3).

That being said, does this mean that it can’t identify an icon-only button with an aria-label?

In response to James. Reply

I think what you are asking is if you can use Dragon (or any voice control) to identify a control with no visible accessible name. The short answer is no, not by its accessible name. A voice user will need to say something like click button and then wait for a unique numeric indicator to appear next to it (actual process differs by tool).

Reply

One reason I prefer to use hidden content (class="sr-only") over aria-label is that it will still work if CSS fails to load. Progressive enhancement for the win!

Kim Johannesen; . Permalink

Leave a Comment or Response

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>