Avoid Read-only Controls

It’s weird to me that after I urged everyone not to disable form controls, a bunch of them decided that making them read-only was somehow better. But here we are.

A rough-hewn wooden box with the words “Read only memory, ROM, 9517” stenciled on the side and “ROM” stenciled on the end above a small hole for grabbing.
What’s in the box? Photo by Michael Heiss, no edits, CC BY-NC-SA 2.0.

HTML

The readonly attribute is only allowed on text fields (<input> and <textarea>).

The WHATWG HTML specification for devs argues that making other controls (buttons, checkboxen) read-only has no useful distinction from being disabled. Other than being focusable, of course.

Focus

Read-only fields and controls are in the tab order. Without an obvious cue, it’s unclear to users why they cannot edit the field. However, with an apparent style it’s still unclear why they cannot edit the field.

Of course, there are some browser inconsistencies. Firefox (132 as of this writing) on Windows and Android won’t let a read-only date, date-time, or time input take focus. It’s functionally treated as disabled, and has the sad gray disabled text as well.

Styles

There is no default style for read-only text fields. The browsers assign none and CSS has never offered one (eg: CSS Basic User Interface Module). It’s up to you to come up with one and have it make sense to users. CSS Selectors Level 4 Working Draft offers a :read-only pseudo-class selector. Previously authors had to (may still have to) rely on an attribute selector using [readonly]. Remember that WCAG contrast requirements apply to read-only controls, so you can’t get away with sad gray-on-gray action.

Until Material Design launched its poorly-researched field styles, I had found that a simple bottom border was not awful, but really only for trained users. I touch on this in my post Under-Engineered Text Boxen.

As noted above, Firefox on Windows and Android sets the values of date, date-time, and time inputs in sad gray text. I’ve done no investigation into why.

Chrome on Android, not to be outdone by Firefox with the sad gray text, also sets the values for date, date-time, and time inputs in grayed text, but goes further by doing the same to the month and week inputs. They are focusable, however.

Editable?

Because HTML read-only text fields participate in form submission, it’s easy to subvert them using browser devtools. More than once I have gotten around a problematic form (and at least one login) by removing readonly with devtools and letting my autocomplete or password manager handle the rest. This presumes the developer honors HTML forms and hasn’t wired up some bloated JavaScript golem to do the work.

ARIA

For the case of the <div> roled-up with ARIA to become a widget or problematic form control aria-readonly functionally does nothing. Except maybe (but not always) tell a screen reader user the field is read-only. The author still has to script it disallow input (or not script it to be functional, I guess?).

However, unlike HTML, aria-readonly is allowed on the roles checkbox, combobox, grid, gridcell, listbox, radiogroup, slider, spinbutton, and textbox. This doesn’t mean users will understand it in those contexts, so it’s not an invitation to read-only your unnecessary grids or confounding listboxen. This also doesn’t mean it will be exposed to screen reader users.

Screen Readers

With no default nor industry-wide visual design language, the only users who consistently stand a reasonable chance of knowing a control is read-only (on any site) are screen reader users. Because read-only is a property (readonly and aria-readonly), it gets exposed. Sometimes.

JAWS

In JAWS 2024 with Chrome 131 the text, email, number, password, search, telephone, and URL inputs announce as read-only along with the textarea. Note that this is a sub-set of fields that allow readonly.

The HTML text input and textarea announce as read-only with aria-readonly="true".

Of the ARIA widgets, only the checkbox, spinbutton, and textbox roles report their aria-readonly="true", which is also a sub-set of roles that allow aria-readonly.

NVDA

In NVDA 2024.4 with Firefox 132 the text, email, month, password, search, telephone, URL, and week inputs announce as read-only along with the textarea. Note that this is a sub-set of fields that allow readonly. Also note the date, date-time, and time inputs still do not get focus.

The HTML text input and textarea do not announce as read-only with aria-readonly="true".

Of the ARIA widgets, only the checkbox and textbox roles report their aria-readonly="true", which is also a sub-set of roles that allow aria-readonly.

VoiceOver / macOS

In VoiceOver on macOS 15.1.1 with Safari 18.1.1 the text, email, month, number, search, telephone, URL, and week inputs announce as selectable along with the textarea. Note VoiceOver does not say “read-only” and it only announces “selectable” after the field value, selectedness, name, field type, and validity.

The HTML text input and textarea do not announce as read-only with aria-readonly="true".

Of the ARIA widgets, only the textbox role reported its aria-readonly="true", which is also a sub-set of roles that allow aria-readonly. It announced it by saying I was on selectable text.

Note that this deviates from VoiceOver / Safari on iPadOS.

TalkBack

Using TalkBack 15.1 with Chrome 131 the text, email, number, password, search, telephone, and URL inputs announce as disabled along with the textarea. Note that this is a sub-set of fields that allow readonly. Also note that TalkBack announced the fields as “disabled,” not “read-only.”

The HTML text input and textarea announce as disabled with aria-readonly="true". Which means they also announce the wrong state.

Of the ARIA widgets, the checkbox, combobox, listbox, radiogroup, slider, spinbutton, and textbox roles report their aria-readonly="true" as “disabled”. TalkBack / Chrome honors all the roles I tested, but does so by reporting the wrong state.

VoiceOver / iPadOS

Using VoiceOver on iPadOS 18.1.1 with Safari the text, email, number, password, search, telephone, and URL inputs announce as read-only along with the textarea. Note that this is a sub-set of fields that allow readonly.

The HTML text input and textarea do not announce as read-only with aria-readonly="true".

Of the ARIA widgets, only the textbox role reported its aria-readonly="true", which is also a sub-set of roles that allow aria-readonly.

Note that this deviates from VoiceOver / Safari on macOS.

Demo

I made a CodePen demo (and a debug version). It only sets basic text styles and focus styles for the ARIA widgets.

The ARIA widgets are non-functional. They exist solely to test exposure to the accessibility APIs, so they have the minimum properties, states, and values to be recognized.

The HTML Nu Checker (HTML validator) is linked so you can see the errors the validator offers. They give more detail than the HTML spec does on which input types can be read-only.

See the Pen Untitled by Adrian Roselli (@aardrian) on CodePen.

Verdict

In short, if you think read-only controls are a hassle to style and script and properly expose, imagine how much of a hassle they are to use. Probably avoid them.

7 Comments

Reply

I usually don’t want a user to interact with form fields while the form is submitting (via AJAX), but also wish to keep the values on-screen to let the user see the values being submitted.

What do you suggest for such a scenario? The only alternative in my mind apart from using disabled or readonly attributes seems to swap the form with a static key-value pairs representing the final form values.

In response to Jayesh Bhoot. Reply

Jayesh, do you find users are regularly editing the fields after they hit the submit button? If so, then you may get more traction from confirmation screens, warnings when a user tries to make a change, a faster response time on the AJAX submit, and so on. If you are not getting feedback that users try to edit the fields after submission then you may be chasing a problem that doesn’t exist.

In response to Adrian Roselli. Reply

Hmm. Then I should just cut my users some slack and let the form be during submission. Thank you!

Reply

Hi, I agree with what you’re saying here, I do think when a web view is a form, and data must be shown as form fields that cannot be edited, you’re not left with many options. If you use disabled then it’s not read by a screen reader so you need to use readonly. I’d love to hear more ways to do this?

In response to Nathaniel Flick. Reply

Nathaniel, screen readers can access disabled fields. You may be thinking that since you cannot tab to it that it cannot be found, but that is not the case. The virtual cursor will do the job just fine. Now, whether or not your users in that context are fine with that is a different question. But from a testing perspective, the disabled field is still there so don’t necessarily flag that as a bug.

In response to Nathaniel Flick. Reply

Nathaniel, your code has immediate problems:

  1. The aria-disabled on the buttons uses “smart quotes” instead of regular quotes (aria-disabled=”true” versus aria-disabled="true"), so it’s not exactly disabled.
  2. Because the buttons use aria-disabled, they can still take focus (and do when tabbing through the page).
  3. Since they are visually hidden and have no focus styles, they are also violating WCAG

So as the example stands, it is a non-starter.

Leave a Comment or Response

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>