Skip to content

Validated form controls: attach validation errors to the interactive element - #81305

Open
dhasilva wants to merge 4 commits into
WordPress:trunkfrom
dhasilva:fix/validated-controls-describedby-interactive-target
Open

Validated form controls: attach validation errors to the interactive element#81305
dhasilva wants to merge 4 commits into
WordPress:trunkfrom
dhasilva:fix/validated-controls-describedby-interactive-target

Conversation

@dhasilva

@dhasilva dhasilva commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

What

For validated controls that use a visually hidden element as a validation delegate — ValidatedFormTokenField, ValidatedToggleGroupControl, and ValidatedCustomSelectControl — the error message's aria-describedby was attached to the delegate rather than to the element the user actually focuses. A screen reader user who moves focus back to the control never hears the error.

Fixes #76741.

How

ControlWithError gains an optional resolver:

getInteractiveTarget?: () => Element | null | undefined;

Only the aria-describedby effect consults it, falling back to getValidityTarget(). Every other use of getValidityTarget() — the invalid listeners, setCustomValidity(), the native popover suppression, data-validity-visible — is unchanged, because those genuinely belong on the delegate, which is what the Constraint Validation API acts on.

Each delegate control already contained the selector it needed inside its onFocus handler. Those are now module-level constants shared by both the focus delegation and the description, so the two cannot drift.

ValidatedToggleGroupControl is the exception and uses two distinct selectors. Focus belongs on the option holding the roving tabindex ([data-active-item="true"]), but the description belongs on the group container that Ariakit.RadioGroup renders: a group-level description is what a screen reader announces on entering the group, and unlike the active item that element is present regardless of interaction state.

Notes for reviewers

  • ValidatedCustomSelectControl is included even though it is not currently exported anywhere — it is the third delegate-based control and the issue names it explicitly.
  • The description moves rather than being duplicated: the delegate is tabIndex={-1} and visually hidden, so nothing reads its aria-describedby.
  • The it.skip in test/toggle-group-control.tsx is intentionally left skipped. It covers a different pre-existing bug — BaseControl not associating the help prop — which this PR does not address.
  • Two existing test comments asserted the old behavior and are updated.

Testing instructions

  1. npm run test:unit -- packages/components/src/validated-form-controls
  2. In Storybook, open any of the three controls, submit the enclosing form with the field empty, and confirm with a screen reader (or the accessibility inspector) that the error is announced as the description of the combobox / radiogroup rather than of the hidden delegate.

dhasilva and others added 4 commits August 6, 2026 20:21
Adds an optional `getInteractiveTarget` resolver to `ControlWithError`, and
uses it in `ValidatedFormTokenField`. Controls that validate through a hidden
delegate element previously attached the validity message's `aria-describedby`
to the delegate, so screen reader users never heard the error on the element
they actually focus.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ement

The group container is the correct target for a group-level description, and
unlike the active option it is present regardless of interaction state.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@dhasilva
dhasilva requested review from a team and ajitbohra as code owners August 7, 2026 00:02
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown

Warning: Type of PR label mismatch

To merge this PR, it requires exactly 1 label indicating the type of PR. Other labels are optional and not being checked here.

  • Required label: Any label starting with [Type].
  • Labels found: [Package] Components.

Read more about Type labels in Gutenberg. Don't worry if you don't have the required permissions to add labels; the PR reviewer should be able to help with the task.

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: dhasilva <thehenridev@git.wordpress.org>
Co-authored-by: mirka <0mirka00@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Package] Components /packages/components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Validated form controls: Error messages don't reach the interactive element on delegate-based controls

1 participant