Problem/Motivation
The state:readonly handler in core/misc/states.js sets the readonly attribute on <input> and <textarea> elements, but VoiceOver (and some other screen readers) does not announce readonly fields any differently from editable fields. This causes an accessibility barrier: users perceive the field as editable, but their input has no effect.
The readonly attribute exists as a distinct concept from disabled because:
- disabled fields do not submit their values with the form.
- disabled fields are skipped in the tab order.
- readonly fields do submit values and are focusable — they just prevent editing.
However, without an ARIA announcement, there is no perceivable difference for screen reader users between a readonly field and an editable one.
Steps to reproduce
1. Create a form with a text field that uses #states to become readonly based on another field's value.
2. Trigger the readonly state.
3. Focus the readonly field with VoiceOver enabled.
4. VoiceOver announces the field as a normal editable text field — there is no indication it is readonly.
Proposed resolution
Add aria-disabled="true" alongside the readonly property in the state:readonly handler. This causes screen readers to announce the element as "dimmed" (VoiceOver) or "unavailable" (NVDA/JAWS), making the readonly state perceivable.
When the readonly state is removed, the aria-disabled attribute should be removed entirely (not set to "false") to avoid leaving stale ARIA attributes in the DOM.
Issue fork drupal-3582687
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
sasanikolic commentedComment #6
cilefen commentedComment #7
rkollerI've manually checked with the form_test module, based on the steps outlined in the issue summary in #2921627: Do not use a CSS-only required marker in forms per WCAG 2.0 within the "javascript states API" section (thanks to @kentr for the pointer). I agree that the current state in Core has several problems. For one, the fields arent programmatically labeled as readonly (i am only testing in VoiceOver), but at the same time looking at the form_test module sighted users are also unable to clearly distinguish a regular field from a readonly field - you have either to read the field label or you have to notice after clicking within the field that there is no cursor available, an additional visual cue for distinguishing fields in those different states is missing completely.
Programmatically the MR is adding a differentiator by using the
aria-disabled="true"attribute, the only problem, semantically a disabled element is different from a readonly element (https://stackoverflow.com/a/7730719). That way, usingaria-disabledis sort of misleading and semantical the wrong pick. The appropriate pick would bearia-readonlybut looking at https://adrianroselli.com/2024/11/avoid-read-only-controls.html and https://adrianroselli.com/2022/11/brief-note-on-aria-readonly-support-ht... citing Adrian Rosellis final verdictit doesn't sound like applying the readonly state is a desirable approach. So I wonder if the usage of the readonly state should be revisited in general - how things are announced in the aural interface and how things are communicate and visually presented in the ui for sighted users? @kentr pointed to a comment in one of the two articles where adrian roselli suggest some viable approach https://adrianroselli.com/2024/11/avoid-read-only-controls.html#comment-... . On a related note I guess how to handle the readonly state should probably be revisited alongside with the related disabled state. that disabled state contains also a few problems.
Comment #8
kentr commentedSummarizing so far:
disabledandreadonlyare distinct states with different purposes.readonlycontrols to begin with.Additionally:
With VoiceOver on the MDN basic
readonlyexample, it's also not announcing that the field is read-only as I focus the fields.VoiceOver does say "selectable". This is consistent with what I experience on a field whose
readonlystate is set with#states.I'd say that this should be Closed (works as designed).
It might be helpful as a followup to put a documentation note somewhere that
readonlycontrols are generally problematic and discouraged. One place I can think of is in the Accessibility Coding Standards.Comment #9
smustgrave commented@sasanikolic as the reporter does that feedback make sense?
Comment #10
sasanikolic commented@smustgrave Yes, I think the discussion here was fruitful with the conclusion that the
readonlystate should be revisited, together with the related disabled state. Mixingaria-disabledtogether withreadonlyis not a viable approach as I initially thought.