Skip to content

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

Merged
cixzhang merged 1 commit into
facebook:mainfrom
bhamodi:a11y/timeinput-typed-invalid
Jul 10, 2026
Merged

fix(core): announce typed-invalid state in TimeInput#3718
cixzhang merged 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

@cixzhang cixzhang left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @bhamodi — typed-invalid announcement is verbatim parity with the sibling inputs; fires on transition, not per keystroke. 🙏

@cixzhang cixzhang merged commit 7e7b5b0 into facebook:main Jul 10, 2026
14 of 15 checks passed
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.

2 participants