Posted Jan 11, 2020

If you know anything about web accessibility, then you probably already know that all elements need to have a (or failing that, an aria-supplied label).

What you may not know is what to do when you have something like this:

What do you have for sale? Radio button one says 'spam', radio button two says 'more spam'.

The radio buttons have… two labels. How do you have two labels?

2 labels 1 input

Now you might be forgiven for thinking that the way to solve this problem is as simple as it sounds:

Unfortunately this doesn’t work. According to the spec, a may contain:

Phrasing content, but with no descendant labelable elements unless it is the element’s labeled control, and no descendant label elements.

You also can’t do this:

Unfortunately, the for attribute only takes a single ID.

Great. So now what?

to the rescue

Enter the dynamic duo:



You might already be familiar with the

element as a way to group form controls and labels—that’s the way it’s described on the MDN page—but unfortunately that description is rather disappointingly vague and doesn’t really explain what

is meant for (more on that later). After all, a

groups things together as well—so what’s the big deal aside from the special default styling?

Well, when paired with its sidekick, the

element, it helps screen reader users by providing that second label that we need. Consider the following code:

What do you have for sale?

When a screen reader encounters this code, it will announce the legend to the user along with the label.

Note 1: As per the spec, the

element must be the first child of the


Note 2: screen readers handle this information in slightly different ways though. JAWS will announce the legend for every input in the fieldset, whereas NVDA only announces it upon entering the fieldset. This latter point is something to keep in mind as you design your form, since NVDA users may not realize once they’ve exited the fieldset.

But wait, there’s more!

I mentioned earlier that fieldset elements are meant for something more than just grouping related form controls. Well, strictly speaking that’s true, but we should be more specific about what we mean by “related”. After all, you could argue that most form inputs are related—that’s why they’re part of the same form. So, more accurately, we should say that fieldsets are meant for grouping related form controls that would be unclear or confusing if not grouped.

So what do I mean by that? Well consider a checkout form that asks for both a billing address and a shipping address:

Two sets of address inputs, one for billing address, the other for shipping address.

As a sighted user, I can easily distinguish which “street” input goes with which address, but for a screen reader user this could get confusing in a hurry. So, once again, here comes our superhero element friends to help us out:

Billing Address
Shipping Address

As with before, this may not be perfect for NVDA users due to the way it chooses to handle the legend, but it’s a heck of a lot better than nothing.

What if I don’t want to use



Actually there is a reason you may want to set aside the first rule of ARIA, and not use these elements. Remember how we mentioned earlier that


have default styling? Yeah… about that… it turns out they have very special default styling. This can make it rather difficult to work with, as you usually have to resort to floating and some funky margins to get the result you want.

So if we want to avoid that nonsense then we can achieve a similar result by doing this:

What if I don’t like groups, either?

You know, because you like making life difficult. [sigh]

If you’re unable to wrap your fields in a grouping like this, there are some other ways around the problem. Just note that the drawback to these approaches is that things are going to get a little verbose for your screen reader users, since the full text will be announced for every input (as opposed to the

or group options, where the verbosity is at the discretion of the screen reader software).

For this route, your options are…

Use hidden text

Some aria-labelledby magic on the inputs themselves

Here we can take advantage of the fact that, unlike the for attribute, aria-labelledby can take a string-delimited list of IDs, instead of just a single one.

Billing Address

A good, ol’-fashioned aria-label

Not sure that aria-label qualifies as old-fashioned, but it makes for a good heading. Just be sure that your aria-label includes the same text as the visible label, ideally at the start. This is to ensure that people who use voice control software can focus the input by name.

Wrapping Up

So there you have it. A whole mess of options for ensuring that your form fields are properly identified for everyone.