readonly
Makes form controls non-editable while keeping them focusable and included in form submission. The polite alternative to disabled.
Overview
The readonly attribute makes a form control non-editable while keeping it focusable, tabbable, and included in form submission. The user can see the value, select and copy the text, and tab through it — but cannot change it.
Use readonly when a value must be submitted with the form but should not be modified by the user. Common examples: order IDs, calculated totals, server-assigned values, and confirmation screens.
Applies to: <input> (text-like types only), <textarea>
Values
| Attribute | Focusable | Editable | Submitted | Works On |
|---|---|---|---|---|
readonly | Yes | No | Yes | Text inputs, textarea |
disabled | No | No | No | All form controls |
The key difference: readonly keeps the value in the form payload. disabled removes it entirely.
<form class="stacked"> <label for="order-id">Order ID</label> <input type="text" id="order-id" name="order_id" value="ORD-2025-00482" readonly /> <label for="notes">Notes</label> <textarea id="notes" name="notes" rows="3">Delivered to front door.</textarea> <label for="amount">Amount</label> <input type="number" id="amount" name="amount" value="149.99" readonly /> <button type="submit">Update Notes</button></form>
Readonly vs Disabled
These two attributes look similar but behave very differently. Choosing the wrong one is a common source of bugs.
<!-- readonly: focusable, submitted, not editable --><label for="ro-field">Readonly</label><input type="text" id="ro-field" value="Submitted with form" readonly /> <!-- disabled: not focusable, NOT submitted, not editable --><label for="dis-field">Disabled</label><input type="text" id="dis-field" value="Excluded from form" disabled />
| Behavior | readonly | disabled |
|---|---|---|
| User can edit | No | No |
| User can focus/tab | Yes | No |
| User can select/copy text | Yes | Varies by browser |
| Value submitted with form | Yes | No |
| Constraint validation runs | No | No |
| Visual appearance | Subtle (your CSS) | Grayed out |
Form Submission
This is the primary reason to choose readonly over disabled. Readonly fields are included in FormData and submitted to the server.
const form = document.querySelector('form');form.addEventListener('submit', (e) => { e.preventDefault(); const data = new FormData(form); // readonly fields ARE included: console.log(data.get('order_id')); // "ORD-2025-00482" // disabled fields are NOT included: // data.get('disabled_field') → null});
Elements That Ignore readonly
The readonly attribute only works on text-like inputs and <textarea>. It has no effect on:
<select>— the user can still change the selection<input type="checkbox">— the user can still toggle it<input type="radio">— the user can still change the selection<input type="range">— the slider is still draggable<input type="color">— the picker still opens
<!-- readonly does NOT work on these elements --><label for="color">Color</label><select id="color" readonly> <option>Red</option> <option selected>Blue</option> <option>Green</option></select><!-- The user CAN still change the selection! --> <!-- Workaround: use disabled + hidden input --><label for="color-display">Color</label><select id="color-display" disabled> <option selected>Blue</option></select><input type="hidden" name="color" value="Blue" />
For these elements, use disabled paired with a <input type="hidden"> to carry the value, or use JavaScript to prevent changes.
Styling
Use the :read-only and :read-write pseudo-classes to style readonly fields distinctly from editable ones.
/* Style readonly inputs differently from editable ones */input:read-only,textarea:read-only { background: var(--color-surface-raised); border-color: var(--color-border); color: var(--color-text); cursor: default;} /* Editable fields get the normal interactive style */input:read-write,textarea:read-write { background: var(--color-surface); border-color: var(--color-border);}
Note: The :read-only pseudo-class also matches elements that are not editable by nature (like <p> or <div>). For precise targeting, combine it with an element selector: input:read-only.
JavaScript API
The readOnly property (camelCase) reflects the attribute.
<h2>Accessibility</h2><ul> <li>Screen readers announce readonly fields as "read-only" or "not editable". The value is still read aloud.</li> <li>Because readonly fields are focusable, keyboard users encounter them during tab navigation. This is usually the desired behavior — the user can review the value in context.</li> <li>If a field is readonly because the user lacks permission, consider adding a visible explanation (e.g., "This field is managed by your administrator").</li></ul> <h2>Limitations</h2><ul> <li><code>readonly</code> is a boolean attribute. Writing <code>readonly="false"</code> still makes the field readonly. Remove the attribute entirely to make it editable again.</li> <li>Constraint validation is skipped for readonly fields. A readonly field with <a href="/docs/attributes/pattern/"><code>pattern</code></a> or <a href="/docs/attributes/required/"><code>required</code></a> will not trigger validation errors.</li> <li>Users can still submit the form with modified values via devtools or by removing the attribute. Always validate on the server.</li> <li>There is no <code>:read-only</code> support for <code><select></code> in any useful way, since the attribute itself has no effect on selects.</li></ul> <section> <h2>See Also</h2> <ul> <li><a href="/docs/attributes/disabled/"><code>disabled</code></a> — fully deactivates a control (no focus, no submission)</li> <li><a href="/docs/attributes/hidden/"><code>inert</code></a> — makes visible content non-interactive</li> <li><a href="/docs/attributes/pattern/"><code>pattern</code></a> — validation constraints (skipped on readonly fields)</li> <li><a href="/docs/elements/native/input/"><code><input></code></a> element reference</li> <li><a href="/docs/elements/native/textarea/"><code><textarea></code></a> element reference</li> </ul></section>