Skip to content

fix(core): announce typed-invalid state in TimeInput#3718

Open
bhamodi wants to merge 1 commit into
facebook:mainfrom
bhamodi:a11y/timeinput-typed-invalid
Open

fix(core): announce typed-invalid state in TimeInput#3718
bhamodi wants to merge 1 commit into
facebook:mainfrom
bhamodi:a11y/timeinput-typed-invalid

Conversation

@bhamodi

@bhamodi bhamodi commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Summary

packages/core/src/TimeInput/TimeInput.tsx computes isInputValid (lines 440-451) — false when the typed value fails parseTimeInput or falls outside min/max — but before this change used it only for the red-border style (!isInputValid && styles.inputInvalid). The screen-reader signals were missing:

  • aria-invalid was gated solely on status?.type === 'error' (line 638), so a typed out-of-range time never set aria-invalid.
  • There was no live region announcing the rejected entry.

Because the value silently reverts on blur, a screen-reader user got no feedback that their entry was invalid — only sighted users saw the red border. This is a WCAG 3.3.1 (Error Identification) gap.

Every sibling typed-format input already handles this the same way — DateInput (aria-invalid at DateInput.tsx:613-615, alert at 633-635), NumberInput (NumberInput.tsx:643-644, 659-661), and DateTimeInput (DateTimeInput.tsx:1006-1008) — so TimeInput was the lone inconsistency. This fix brings it to parity.

Changes

  • packages/core/src/TimeInput/TimeInput.tsx: OR the parsed-invalid flag into aria-invalid (status?.type === 'error' || !isInputValid) and add a VisuallyHidden assertive live region announcing "Invalid time" while the typed input is invalid, mirroring DateInput.
  • packages/core/src/TimeInput/TimeInput.test.tsx: added 4 tests — aria-invalid="true" on out-of-range typed input, no aria-invalid on valid input, an alert announcing "Invalid time" when invalid, and an empty alert when valid.

Test plan

  • node_modules/.bin/vitest run --root . packages/core/src/TimeInput35 pass (was 31), including the 4 new tests.
  • Confirmed failing-first: ran the new tests before the fix — the aria-invalid, alert-message, and empty-alert tests all failed (no alert element existed and aria-invalid was never set), proving they catch the gap.
  • node_modules/.bin/tsc --project packages/core/tsconfig.json --noEmit — clean.
  • node_modules/.bin/eslint packages/core/src/TimeInput/TimeInput.tsx packages/core/src/TimeInput/TimeInput.test.tsx — clean.

Notes

Found during a broader a11y audit of the input components. Scoped to the aria-invalid change and the live region plus tests. DateInput.doc.mjs doesn't document this behavior (and the repo check:sync passes), so no doc change was needed to stay in parity with the siblings.

@vercel

vercel Bot commented Jul 9, 2026

Copy link
Copy Markdown

@bhamodi is attempting to deploy a commit to the Meta Open Source Team on Vercel.

A member of the Team first needs to authorize it.

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Jul 9, 2026
@bhamodi bhamodi force-pushed the a11y/timeinput-typed-invalid branch from ee44f62 to 921a34e Compare July 9, 2026 05:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant