Problem/Motivation

We make widespread use of the aria-describedby attribute to programmatically associate an HTML <input with the Form API #description. There are also some other uses of the aria-describedby attribute, to describe things like buttons and dialogs.

The WAI-ARIA recommendation defines the aria-labelledby aria-describedby attribute values as an ID reference list. In other words, these can point to multiple element IDs, like so:

aria-describedby="description-1 description-2"

When multliple IDREFs are used like this, the AccessibleName and AccessibleDescription (in the accessibility tree made available to assistive tech) are computed by concatenating all the referenced elements.

However, the forms system mistakenly assumes that there will only be a single IDREF in the aria-describedby attribute. FormBuilder::doBuildForm() sets up the element description like so:

// Add the aria-describedby attribute to associate the form control with its
$element['#attributes']['aria-describedby'] = $element['#id'] . '--description';

This makes it tricky to manipulate aria-describedby attribute properly in form alter hooks, and other areas of form rendering, without risking a conflict with something that another module might do.

Initial use case: We want to make use of multiple IDREFs in aria-describedby to associate form error messages with inputs, in addition to the existing FAPI descriptions. See #3083103: Programmatically associate error messages with inputs. We don't want to break existing #description associations.

However there are other possible uses for multiple IDREFs, so I'm filing this issue as a separate task.

Proposed resolution

Treat aria-labelledby and aria-describedby as arrays, to make handling multiple IDREFs much easier.

Remaining tasks

  • Update FormBuilder::doBuildForm() to use an array for aria-describedby.
  • Review other places where aria-describedby is used, and ensure they are also flexible enough to accept multiple IDREFs.
  • Review places where aria-labelledby is used, and ensure they are also flexible enough to accept multiple IDREFs.
  • Assess this for backwards compatibility.

User interface changes

None.

API changes

TBD.

Data model changes

None.

Comments

andrewmacpherson created an issue. See original summary.

andrewmacpherson’s picture

Priority: Normal » Major

Treating this as major, because the WCAG technique ARIA1: Using the aria-describedby property to provide a descriptive label for user interface controls relates to TWO success criteria at level A, and we already have a high-impact use case.

mgifford’s picture

In the interim, it is good to look at this module and see what can be brought forward from it into Core https://www.drupal.org/project/a11y_form_helpers

andrewmacpherson’s picture

Yeah, I've been aware of the a11y_form_helpers module for a while. That works around the problem by concatenating the IDREFs into one string, and overwriting the original value. That's a bit fragile, because any other module or theme might also want to overwrite the original value.

andrewmacpherson’s picture

Given that many render elements will already be setting this to a string, it's probably worth putting a BC routine in the render pipeline somewhere, which checks if a string was passed, and wraps it up as an array with one item. That's a fairly trivial pattern, I think?

My first attempt at a backwards-compatibility review...

The ['#attributes']['aria-describedby'] is currently treated as a string. If any existing sites and/or contrib modules wanted to change it, what approaches can they currently use?

  1. Use a form alter hook (or a field widget alter hook) to change the value of the string. Say, point the aria-describedby to a different IDREF. Here the alter hook would do a brute force over-write of the string: $element['#attributes']['aria-describedby'] = "NEW_ID_REF";.
  2. Sites which want to use multiple IDREFs in might do so by concatenating them as a white-space separated string. For an example, see A11yFormHelpersRenderElement::preRenderElement() in the A11Y: Form helpers module.
  3. Ignore the aria-describedby attribute that was passed into the Twig template. Something like <fieldset aria-describedby="my_new_idref" {{ attributes|without('aria-describedby') }}>
  4. Manipulate the aria-describedby attribute in client-side JS.

In all these cases, the site won't benefit from the change to an array, but I don't think they would break either. They're all brute-force over-writes, just in different places.

Are there any approaches I have missed?

andrewmacpherson’s picture

Title: Forms system should permit multiple IDREFs in aria-describedby attribute » Forms system should permit multiple IDREFs in aria-labelledby and aria-describedby attribute
Issue summary: View changes

All of this applies equally to aria-labelledby. Updating issue scope.

Note that we're not using aria-errormessage yet, but that the ARIA 1.1 rec. says it takes a single IDREF, not multipleIDREFs. So that attribute is NOT in scope here.

The full list of ARIA attributes (from ARIA 1.1) which are defined as an "ID reference list" is:

  • aria-controls
  • aria-describedby
  • aria-flowto (I don't think we use this yet)
  • aria-labelledby
  • aria-owns

It would be good to treat all of these as arrays in the render pipeline, like we currently do for the class attribute.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.