Use Legend and Fieldset

‘I am legend’ set in bold red type against black, an image of a vampire visible in the text. It’s 2022 and people are still afraid to use <fieldset> and <legend>. I understand the layout challenges can be frustrating, but swapping to an ARIA group role will result in a more inaccessible experience.

A Solution

Try this:

<fieldset>
 <legend>Choose</legend>
 <div aria-hidden="true">Choose</div>
 […]
</fieldset>
legend: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; 
}

If the <legend> is never at risk of receiving focus (through some rogue script or whatever), then dump the :not(:focus):not(:active) from the selector.

I took three existing examples of mine and adjusted them to use this approach.

The unmodified original is from my post Under-Engineered Dependency Questions. It uses CSS flex (debug of this flex example):

See the Pen <legend> with `flex` by Adrian Roselli (@aardrian) on CodePen.

I made this one on a lark and it has no associated post. There is also a debug version of this grid-template-columns example.

See the Pen <legend> with `grid-template-columns` by Adrian Roselli (@aardrian) on CodePen.

I grabbed the scrolling example from my post Under-Engineered Multi-Selects to try this with position: sticky (which also has a debug version):

See the Pen <legend> with `grid-template-columns`, `position: sticky` by Adrian Roselli (@aardrian) on CodePen.

Why I Suggest This

The rest of this post goes into more detail on layout and accessibility challenges, along with some alternatives. You can stop reading here and hope my example does what it claims, or you can read more to decide if there is a better approach based on what I present.

Layout

Essentially the <legend> element does not participate in <fieldset>’s layout box. This is not new information. Folks have been frustrated by this for years.

Looking at the grid bugs and flexbox bugs trackers, we see that there have been issues with fieldsets that go beyond the intentional layout model:

That covers layout, but what about how this information is exposed to users?

Accessibility Bugs

I made a demo to compare how a <fieldset> / <legend> construct compares with a group / aria-labelledby construct. The debug view, for your own testing.

See the Pen Untitled by Adrian Roselli (@aardrian) on CodePen.

Overall an (expected) improvement from when Steve Faulkner wrote about it in 2007 and tracking with more recent PowerMapper results. It corresponds with my testing from 2019 as well.

TalkBack on Android

Providing group labels has always been a problem with TalkBack, no matter the technique. I cannot find related bugs, but if you have them please share.

VoiceOver on iOS

VoiceOver is the primary mobile screen reader for almost 72% of users. It is also the one I see most commonly referenced by front-end generalists (as opposed to accessibility practitioners).

A stroll through Accessibility Support shows us that group support is poor on iOS (and Android). In other words, it is effectively non-existent. Using it is no different than not. I found no bugs detailing this, but it appears to be well-enough known by library developers.

Conversely, Bug 147466 - Voiceover on iOS not announcing legend associated with a fieldset when browsing in safari, opened July 2015 (updated February 2021) notes that <legend> is not announced when in a landmark that is not in a form or region, nor apparently when it is a form in a main region.

What Do We Do?

Given the layout concerns and browser bugs, what are our options? Here are a few, with caveats, for you to make your own decisions.

Not Yet: Use display Properties

I have seen folks use display: table-cell before, but this was only effective in some browsers. It also ran the risk of Chrome applying layout-table semantics to the element, which gets handed off to the accessibility tree for screen readers.

In fact, changing display properties has been risky for years because browsers would drop all semantics from elements. Riskiest of all was using display: contents, even though the CSS spec has suggested it for <legend> since at least 2017, despite knowing it has been a major accessibility bug which Apple, that last holdout, has not fixed it in Safari, despite promises in March and again in June.

Using display properties is too risky unless you can absolutely guarantee all of your audience is using the latest browsers with the latest hardware (because in many cases hardware limits how current you can get).

No: Wrap the <legend>

I have seen a couple cases where folks wrap the <legend> in a <div> and then apply their layout to the <div>.

Some quick screen reader testing will show this is not feasible since it breaks the relationship between the <fieldset> and the <legend>.

Meh: Use aria-describedby

You could use aria-describedby to assign the group label to each item within the group as its accessible description. This is not its name, so it can be missed, but it will at least be exposed.

When used with a group role, it will not give quick insight to a user when they have entered a new group, so they will need to listen the full announcement for each control if unfamiliar with the page.

Ugh: Use aria-labelledby

This is a bit riskier because you would override the accessible name of every control in the group container. You would use a space-separated list of id values to build it back up, concatenating the group label and the control name.

I say it is risky because it is too easy to append the wrong id value, and is compounded when done so with generated controls and no manual screen reader testing (an automated tool cannot know if the accessible name is right or wrong, only that it is present).

It also makes for verbose controls. Every control will announce the group label and it cannot be skipped. A <fieldset> / <legend> construct only announces the group label when moving into the group.

This can also make a frustrating experience for voice users. As long as the control’s visible label is part of the accessible name, you will be fine to pass Success Criterion 2.5.3 Label in Name. You will almost definitely want to make sure the group label comes after the field name, however (Technique G208 Including the text of the visible label as part of the accessible name speaks to this).

Sure: Hide the <legend>

Use <fieldset> and <legend>, not a group role. Visually hide the <legend> and replicate its content into a node in the <fieldset>. Add aria-hidden="true" to that node.

This approach, unfortunately, replicates content on the page, so it violates the DRY (don’t repeat yourself) principle. But that also allows you to automate it more easily since you can replicate the string (risks that I noted with aria-labelledby apply, but only once as opposed to for each control).

This does not fix TalkBack’s lack of support for <fieldset>, nor does it address iOS/VoiceOver’s bugs with <fieldset>s in some kinds of regions. But it does not add more problems that come from using the group role. This is also potentially easier to remove once <fieldset> support is fixed — strip the duplicate node and remove the visually-hidden styles.

Maybe? I do not know all wacky layout requirements and arbitrary use cases. Consider this post and its comments a form of peer review.

But please, if you have an idea and want to comment here, come with receipts — identify your code (a linked example is best), explain why it is what it is (the use case or design goal), and outline browser and assistive technology support (version numbers help).

Let’s not claim something is accessible without any insight into how it performs, what decisions were made, and what features apply.

7 Comments

Reply

Thanks for another fantastic post!
on my iOS 15.5 + Safari, VO doesn’t announce fieldset/legend or group+labelledby
here: https://codepen.io/WW3/pen/VwXeKdo?editors=1100

Neil Osman; . Permalink
In response to Neil Osman. Reply

Comment out the <main>. The last paragraph in the VoiceOver on iOS section addresses that bug.

Reply

Another benefit of using <fieldset> is that it will show up in the HTMLFormControlsCollection of the <form> (i.e. HTMLFormElement.elements). Recently, something I’ve experimented to remediate layout issues and maintain the benefits of using <fieldset> is to drop the <legend> and use aria-labelledby on the <fieldset>. This seems valid as <legend> isn’t required for <fieldset>. I’m not sure if this is what you are referring to in the section under the heading “Ugh: Use aria-labelledby. I haven’t tested this approach much outside of VoiceOver with Safari. I’d be interested to see how it compares.

In response to Nathan Knowler. Reply

Nathan, I forked Neil’s pen (prior comment), because it contains the construct your describe, and tested it with JAWS/Chrome and NVDA/Firefox. In those two combinations it announced the same as if the <legend> were there instead.

I was referring to a construct like I saw with React Spectrum’s calendar control, where each field gets an accName from a compound aria-labelledby.

Reply

Thanks for sharing!

Ogechi Ike; . Permalink
Reply

I’m building a design system and focusing on a11y I just stumble upon some trouble with “ elements inside “ with `display: flex` but messing around with it I found that `float: left` on the “ make it work like it should both with parent’s being `flex` or `grid`, it’s strange to have a `float: left` in my code in 2022 but its the most straightforward solution I found, lemme know what you think!

In response to Du Ruiz. Reply

In a vacuum, there is nothing wrong with using float. Obviously there are risks when users scale text or shrink viewports. But if it tests well and does not create other exciting barriers, then don’t feel bad about using it.

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>