fix(TextField): associate validation text with input via aria-describedby - #3508
Conversation
…edby The inline validation error rendered in a <span> with no id, and the input's aria-describedby resolved to undefined in the error case, so screen readers could not associate the error with its field (WCAG 3.3.1, 1.3.1). Derive a validation-text id from the existing id prop, set it on the error span, and compose aria-describedby from it plus the existing max-length hint id. Also emit undefined instead of empty-string aria-owns / aria-activedescendant so React omits the invalid empty ARIA attributes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Code Review by Qodo
1. Screen readers can announce wrong errors
|
PR Summary by QodoAssociate TextField validation messages with inputs
AI Description
Diagram
High-Level Assessment
Files changed (4)
|
Code Review by Qodo
1. Screen readers can announce wrong errors
|
| const validationTextId = hasValidationText ? `${id}-validation-text` : undefined; | ||
| const describedBy = [validationTextId, allowExceedingMaxLengthTextId].filter(Boolean).join(" ") || undefined; |
There was a problem hiding this comment.
1. Screen readers can announce wrong errors 🐞 Bug ≡ Correctness
validationTextId is derived from the shared default id value of "input", so every ID-less instance renders the same validation target and description reference. When two validation-bearing TextField instances omit id, the second input cannot be uniquely associated with its own message and may resolve to the first field's error instead.
Agent Prompt
## Issue description
Multiple `TextField` instances that omit `id` generate duplicate validation-text IDs, preventing each input from being uniquely associated with its own message.
## Issue Context
The public `id` prop remains optional and defaults to the literal `"input"`. Generate a stable per-instance fallback identifier compatible with the package's supported React versions, use it consistently for the input, label, test IDs, and derived validation IDs, and add coverage for two ID-less validation-bearing fields.
## Fix Focus Areas
- packages/components/text-inputs/src/TextField/TextField.tsx[224-224]
- packages/components/text-inputs/src/TextField/TextField.tsx[349-351]
- packages/components/text-inputs/src/TextField/TextField.tsx[377-409]
- packages/components/text-inputs/src/TextField/TextField.tsx[476-480]
- packages/components/text-inputs/src/TextField/__tests__/TextField.test.tsx[342-396]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
|
📦 Bundle Size Analysis ✅ No bundle size changes detected. Unchanged Components
📊 Summary:
|
What & why
TextField's inline validation error rendered in a
<span>with noid, and the input'saria-describedbyresolved toundefinedin the error case. Screen reader users could not tell which error belonged to which field.WCAG: 3.3.1 Error Identification, 1.3.1 Info and Relationships.
Monday task: https://monday.monday.com/boards/18428773755/pulses/12971125245
Changes
hasValidationTextand derivevalidationTextId = \${id}-validation-text`` from it.idon the error<span>soaria-describedbyhas a real target.aria-describedbyfrom[validationTextId, allowExceedingMaxLengthTextId]rather than replacing the max-length hint — the char-count description is preserved when both features are used together.undefinedinstead of empty-stringaria-owns/aria-activedescendantso React omits the invalid empty ARIA attributes.aria-describedbypoints at an element that actually exists in the DOM, the required-error-after-blur path, composition with the max-length hint, and absence when there's no validation text.idrequirement) and added a "Validation in a form" Storybook story with two error-bearing fields.Non-breaking
The fix derives the accessible description from content the component already renders, using the existing
idprop. No prop added/renamed/retyped, no API change, no change to tab order, focus, or visible styling. Consumers get the fix on a version bump alone.Snapshot churn is expected
The 23 updated snapshots in
TextField.snapshot.test.tsx.snapare the removal of the invalidaria-owns=""/aria-activedescendant=""attributes, plus the two new attributes in the validation case — nothing else.Note for reviewers
iddefaults to the literal string"input", so twoid-less TextFields on one page produce duplicateinput-validation-textids. The docs guideline was sharpened to require a uniqueidrather than hiding this behind a generated one (useIdisn't available — the package's React floor is>=16.9.0).Testing
yarn workspace @vibe/text-inputs test→ 10 files, 143/143 passed (5 new).yarn workspace @vibe/text-inputs lint→ clean, 0 errors / 0 warnings.lerna run build --scope=@vibe/text-inputs --include-dependencies→ success.🤖 Generated with Claude Code