A frustrating accessibility bug — and a simple workaround
We recently stumbled across a strange and frustrating issue affecting our React application’s accessibility on iOS Safari. Thanks to the sharp eyes of my colleagues Danny and Milan, who reported the behavior to our Design System team, we were able to track down a pretty specific — and surprisingly deep — bug involving date and time input fields, VoiceOver, React’s event system, and WebKit itself.
🕵️ The Problem
In our React application, whenever a user used VoiceOver on iOS Safari and tried to interact with an <input type="date">
, the expected native date-picker simply didn't open. There was no interaction at all. Nothing. The user was essentially blocked from interacting with that field altogether — a serious accessibility issue.
We also realised that the datetime-local
, week
and month
(Update: color
as well) types are affected too, not just the date
and time
types.
After some investigation and multiple rounds of trial and error, we confirmed:
- ✅ The bug occurs only on iOS Safari with VoiceOver enabled
- ✅ It affects React applications
- ✅ It does not appear in Vue or Angular apps
- ❌ It prevents any interaction with the date/time picker
🔍 Digging Deeper: A React-Specific Issue?
This led us to suspect that React’s synthetic event system might be involved. React uses event delegation under the hood — meaning event listeners are attached at the document level, not directly on elements.
After further research, we came across an open GitHub issue on React’s tracker (#33541) that confirmed our findings:
If there is a surrounding element with a
click
event listener, Safari on iOS with VoiceOver won't open the native date/time picker when the input is tapped.
And indeed, in our React app, some components had generic click handlers at container or layout levels — nothing unusual, but enough to trigger the problem.
🐞 WebKit Is the Root Cause
A member of the React team has since analyzed this and traced the issue to WebKit itself — a bug was filed accordingly:
To be clear: React is not broken here — but because of its use of event delegation, it's more likely to run into this issue than frameworks like Vue or Angular, which bind events more directly.
🛠️ Workarounds We Tried
Naturally, we explored a few workarounds:
-
Changing the input type or using
inputmode
dynamically: Complicated and fragile; led to inconsistent UX -
Using
pointer-events: none
on surrounding elements or (additional) wrapping labels: Didn't resolve the issue; also interfered with other UI behaviors -
Calling
event.stopPropagation()
orstopImmediatePropagation()
: Ineffective and introduced new bugs due to broken event chains
All of these attempts either didn’t work or caused more problems than they solved.
✅ The Best Temporary Fix: role="textbox"
Eventually, we landed on the simplest workaround that reliably restores interaction with the picker:
<label for="date-input">date of journey</label>
<input type="date" role="textbox" id="date-input" />
This shouldn’t be necessary — and ideally won’t be forever — but for now, adding role="textbox"
helps VoiceOver recognize the input as interactable, and allows the native date/time picker to open as expected.
The obvious downside of this approach is that the announced role for VoiceOver users wouldn't be perfect. Instead of "date", "textbox" would be read out as the role. However, this comes after the defined label for this form element, so it would most likely be compensated for by the unique and clear labelling of form fields, which is always necessary.
If you'd like to further optimize, you could use the aria-roledescription
-HTML-attribute, that provides a human-readable, localized description of the role — i.e. how the role should be announced, not what it is technically.
💡 Pro tip: You may want to apply this conditionally — for example, only for iOS Safari — to avoid affecting screen reader behavior on other platforms. We came up with the following code generated by AI:
const isIOSSafari = () => {
if (
typeof window === 'undefined' ||
typeof navigator === 'undefined'
)
return false;
const ua = navigator.userAgent;
// iOS detection
const isIOS = /iP(ad|hone|od)/.test(ua);
// Safari detection (not Chrome or Firefox on iOS)
const isSafari =
!!ua.match(/Safari/) && !ua.match(/CriOS|FxiOS|OPiOS|EdgiOS/);
return isIOS && isSafari;
}
💬 Conclusion
This bug highlights how subtle and frustrating accessibility issues can be — especially when multiple systems (screen readers, browsers, frameworks) interact in complex ways.
If your React app includes date or time inputs and supports iOS users with assistive technology (which you obviously always should), we highly recommend applying this temporary fix until WebKit ships a proper solution.
Thanks again to Danny and Milan for spotting the issue early, and to the broader React and WebKit communities for investigating and escalating the problem.
It's always best to stick with standard, built-in semantic HTML elements rather than using something from the internet or reinventing the wheel yourself. However, even these may be affected by browser bugs from time to time, so we need to mitigate these issues.
Let’s continue building inclusive, accessible web experiences — even when it gets tricky.
🔗 Resources
Top comments (0)