Testing aria-readonly

aria-readonly indicates that a widget's value cannot be edited, but the widget itself is still fully operable, focusable, selectable, and copyable, unlike a disabled control. It's the ARIA equivalent of the native readonly attribute, for use on custom widgets that don't have a native readonly state of their own.

On a native input though, aria-readonly alone does nothing functional. It's announcement-only, it tells assistive technology the field is read-only, but doesn't stop typing unless the field is also functionally restricted some other way (native readonly, disabled, or, for a custom widget, something like contenteditable="false"). The examples below start with the native input combinations, then repeat the same valid and redundant patterns on a custom widget, before finishing with an invalid value.

Depending on the widget, exactly one pattern below is the one to actually reach for: Example 1 (native readonly) for a native element, Example 6 (aria-readonly="true" + contenteditable="false") for a custom widget. Everything else on this page demonstrates a gap, a redundancy, a conflict, or an invalid value, not a recommended pattern.

Example 1: readonly on native element

A native input's own readonly attribute needs no ARIA at all, it's mapped to the accessibility tree automatically. This is the ideal pattern for a native element.

Expected result: the field should be announced as read-only. It should remain focusable, and its value should be selectable and copyable, but not editable, this is the baseline every other example is compared against.

HTML markup
<input type="text" value="ABC-123-XYZ" readonly>

Example 2: aria-readonly="true" on a native input

No native readonly, so nothing actually stops the user editing the value. This is the core gap: aria-readonly carries no enforcement of its own on a native input, that's already handled by native readonly if it's present.

Expected result: the field remains fully editable, typing and pasting both work normally. Assistive technology should still announce it as read-only, since that's all this attribute does, worth confirming directly whether every AT/browser combination actually surfaces this announcement, ARIA state support is never guaranteed.

HTML markup
<input type="text" value="ABC-123-XYZ" aria-readonly="true">

Example 3: aria-readonly="false" on a native input

false is already the default value of aria-readonly, so stating it explicitly changes nothing. Flagged for redundancy, not for being wrong.

Expected result: behaves identically to an ordinary field with no aria-readonly attribute at all, editable, and announced as editable. No difference from omitting the attribute entirely.

HTML markup
<input type="text" value="ABC-123-XYZ" aria-readonly="false">

Example 4: readonly + aria-readonly="true" on a native input

Native readonly already handles both the enforcement and the announcement on its own, so adding aria-readonly="true" duplicates what's already implied. Redundant, not conflicting, both attributes agree.

Expected result: behaves identically to Example 1, typing and paste are rejected, and it's announced as read-only. The aria-readonly="true" adds nothing on top of native readonly.

HTML markup
<input type="text" value="ABC-123-XYZ" readonly aria-readonly="true">

Example 5: readonly + aria-readonly="false" on a native input

The two attributes disagree. Native readonly is what actually governs behaviour, the browser still blocks typing and paste regardless of what aria-readonly says. But aria-readonly="false" tells assistive technology the opposite of what's actually happening.

Expected result: the field stays browser-enforced read-only, typing and paste are still rejected, native readonly wins functionally. Whether assistive technology announces it as read-only (matching actual behaviour) or as editable (matching the ARIA state) is genuinely unsettled and needs confirming directly across AT/browser combinations, don't assume either outcome.

HTML markup
<input type="text" value="ABC-123-XYZ" readonly aria-readonly="false">

Example 6: aria-readonly="true" on a custom widget

This is a custom text field, built from a contenteditable element with role="textbox" rather than a native input. Since it has no native readonly attribute to rely on, aria-readonly is what communicates the same state, this is the scenario aria-readonly actually exists for. contenteditable="false" is what does the functional enforcement here, not aria-readonly itself. This is the ideal pattern for a custom widget.

Confirmation number
ABC-123-XYZ

Expected result: the field should be announced as read-only, the same as Example 1. It should remain focusable, and typing should be blocked by contenteditable="false", not by aria-readonly.

HTML markup
<div
  role="textbox"
  aria-labelledby="ex6-label"
  aria-readonly="true"
  contenteditable="false"
  tabindex="0"
>ABC-123-XYZ</div>

Example 7: aria-readonly="false" on a custom widget

false is already the default value of aria-readonly, so stating it explicitly on a genuinely editable custom widget changes nothing. Same redundancy as Example 3, just on a non-native element.

Confirmation number
ABC-123-XYZ

Expected result: behaves as an ordinary editable custom widget, typing works normally, and it should be announced as editable. No functional difference from omitting the attribute entirely, the same as Example 3's native case.

HTML markup
<div
  role="textbox"
  aria-labelledby="ex7-label"
  aria-readonly="false"
  contenteditable="true"
  tabindex="0"
>ABC-123-XYZ</div>

Example 8: invalid value

aria-readonly is a boolean-valued property, only true or false are valid. This example uses a common authoring mistake, echoing the HTML attribute name itself as the ARIA value.

Expected result: per spec, an invalid token on a boolean ARIA property should fall back to the default (false), so the field should behave and be announced as ordinary and editable. Invalid-value handling is inconsistent across implementations though, worth confirming directly rather than assuming spec-correct fallback behaviour.

HTML markup
<input type="text" value="ABC-123-XYZ" aria-readonly="readonly">

Readonly is not the same as disabled. A read-only field stays focusable, its value can still be selected and copied, and for native form fields, it's still submitted with the form. A disabled field is removed from the tab order, its value typically cannot be selected, and it's excluded from form submission entirely. If a value should still be readable and copyable, but not editable, readonly is usually the correct choice, not disabled.

Note for testing: confirm with your standard browser and screen reader combinations that Examples 1 and 6 are announced as read-only, that Examples 2 and 7's announcements hold even though the field stays editable, that Example 5's conflicting combination doesn't produce an unpredictable announcement, and that the distinction from a disabled field (still focusable, still selectable) actually holds in practice.