dequelabs / dequelabs/axe-core

Accessible name calculation should not allow fallback from empty <label>s

Open
#4,620 0 comments 1 reaction 0 assignees View on GitHub
false negative rules
Dominant language
JavaScript
Stars
7.5k
Forks
933
Avg merge
2d 23h
Merged PRs (30d)
17

Description

### Product

axe-core

### Product Version

4.10.1

### Latest Version

- [x] I have tested the issue with the latest version of the product

### Issue Description

The following checks:

- `non-empty-placeholder` (used by the `label` rule)
- `non-empty-value` (used by the `input-button-name` rule)
- `non-empty-title` (used by many `*-name` rules)
- `button-has-visible-text` (used by the `button-name` rule)

...currently work by just checking the specific attribute/content they're associated with, ignoring other context. However, it turns out that in some browsers (notably Chrome), the presence of an empty `` (either implicitly or explicitly linked) will result in these cases being ignored during accessible name calculations. We think this is likely an HTML-AAM violation on Chrome and Safari's parts, but for accessibility-supported purposes, axe-core should respect the least-functional of the fallback behaviors (Chrome's).

I verified that this applies to at least the `label`, `button-name`, and `input-button-name` rules, but note that this probably applies to the other rules that rely on `non-empty-title` as well (we'll need to add integration tests of that as part of this update)

**Note that we'll also want to make corresponding updates to axe's own accessible name calculations, in addition to those specific checks.**

#### Standard interpretation

[HTML-AAM §4.1.1](https://w3c.github.io/html-aam/#input-type-text-input-type-password-input-type-number-input-type-search-input-type-tel-input-type-email-input-type-url-and-textarea-element-accessible-name-computation) step 3 is consistent with this for `title` attributes:

> 1. If the control has an [aria-label](https://www.w3.org/TR/wai-aria-1.2/#aria-label) or an [aria-labelledby](https://www.w3.org/TR/wai-aria-1.2/#aria-labelledby) attribute the [accessible name](https://www.w3.org/TR/accname-1.2/#dfn-accessible-name) is to be calculated using the algorithm defined in [Accessible Name and Description: Computation and API Mappings](https://w3c.github.io/accname/).
> 2. Otherwise use the associated label element or elements [accessible name(s)](https://www.w3.org/TR/accname-1.2/#dfn-accessible-name) - if more than one label is associated; concatenate by DOM order, delimited by spaces.
> 3. If the [accessible name](https://www.w3.org/TR/accname-1.2/#dfn-accessible-name) is still empty, then: use the control's title attribute.
> 4. Otherwise use the control's [placeholder](https://w3c.github.io/html-aam/#att-placeholder) value.
> 5. If none of the above yield a usable text string there is no [accessible name](https://www.w3.org/TR/accname-1.2/#dfn-accessible-name).

The word "Otherwise" in step 4 is a bit ambiguous, but based on [the PR introducing the language](https://github.com/w3c/html-aam/issues/167), I think it was probably meant to be read similarly to step 3 (ie, as if "Otherwise" were replaced with "If the [accessible name](https://www.w3.org/TR/accname-1.2/#dfn-accessible-name) is still empty, then: "), which would also be consistent with the current `label` behavior.

#### Accessibility Supported status

Unfortunately, the accessibility supported state isn't really consistent with any reasonable interpretation of the standard. Firefox falls back like the `label` rule expects, but Chrome never fallsand Safari will treat elements with empty ``s as having empty accessible names in most cases, even if a non-empty placeholder or title is present:

```html

content

content

```

The accessible names as reported by browsers are:

| Case | Chrome 130.0.6723.59 | Safari 17.6 (19618.3.11.11.5) | Firefox 131.0.3 |
| - | - | - | - |
| label `fail15` | `""` | `""` | `"placeholder value"` |
| label `fail16` | `""` | `"placeholder value"` | `"placeholder value"` |
| label `fail17` | `""` | `""` | `"title value"` |
| label `fail18` | `""` | `""` | `"title value"` |
| button-name `fail5` | `""` | `""` | `"title value"` |
| button-name `fail6` | `""` | `""` | `"title value"` |
| button-name `fail7` | `""` | `""` | `"content"` |
| button-name `fail8` | `""` | `"content"` | `"content"` |
| input-button-name `fail5` | `""` | `""` | `"value attr"` |
| input-button-name `fail6` | `""` | `"value attr"` | `"value attr"` |

#### Expectation

All 10 of the cases above should be violations, since at least one of our accessibility-supported browser combos report empty accessible names for them.

#### Actual
All 10 of those cases pass

#### How to Reproduce
See above

#### Additional context

##### Original report

See [this axe-community slack thread](https://axecommunity.slack.com/archives/C01BSF8J6LS/p1728999543482409). Thanks @pattonwebz for the original report!

##### `aria-label`/`aria-labelledby` out-of-scope

Note that the lack of fallback here *only applies to empty `` elements*! All 3 browsers *do* fall back from empty `aria-label` and `aria-labelledby` cases to titles and placeholders:

```html

```

| Case | Chrome | Safari | Firefox |
| - | - | - | - |
| label `pass18` | `"placeholder value"` | `"placeholder value"` | `"placeholder value"` |
| label `pass19` | `"placeholder value"` | `"placeholder value"` | `"placeholder value"` |
| label `pass20` | `"title value"` | `"title value"` | `"title value"` |
| label `pass21` | `"title value"` | `"title value"` | `"title value"` |

##### Suggested check fixes

At the check level, I think the simplest fix would probably be to update the 4 checks in question to return `false` if an implicit or explicit label is present, which a custom check message explaining why they're inapplicable.

If we write the integration tests and find that actually there are *some* cases where the current behavior with `non-empty-title` is correct, we could instead consider something similar to the exiting `"none": ["hidden-explicit-label"]` check config in the `label` rule, adding a special check that just reports on this specific incompatibility. I think that'd probably result in worse check messaging in the unaffected cases, though; I think it'd be better to avoid adding more verbosity to those rules' remediation messages, they're already a bit dense.

##### Suggested accessible name calculation fixes

I think for this it'd be cleaner to do it a bit differently from the checks and closer to how it seems like Chrome is operating in practice. Currently, `accessible-text-virtual.js` and `native-text-alternative.js` both contain `reduce` calls that iterate over the name calculation steps and treat cases where a step returns `''` as meaning "fall back to the next step". Since it seems like the chrome behavior really boils down to "if the name from `` is empty, treat it as a special halting empty name", I'd lean towards updating those core loops to support steps indicating a similar concept, maybe something like a `Symbol('halting-empty-string')` that is understood to exit-fast from the steps, and update the `labelText` naming method implementation to return that if it sees a present-but-empty case.

##### Other updates

We'll want to audit everywhere else our `label`, `labelVirtual`, and `labelText` functions are called to double check if there are any other cases repeating name calculation logic that need similar updates

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.