readonly 是行为控制,aria-readonly 仅是语义声明;原生 input/textarea 用 readonly 即可,自定义组件需同时设 contenteditable="false" 和 aria-readonly="true",js 动态切换时须同步两者以确保无障碍语义一致。

readonly 是行为控制,aria-readonly 只是语义声明
readonly 是原生 HTML 属性,浏览器会据此阻止用户编辑、允许聚焦、保留表单提交值;而 aria-readonly 仅用于向屏幕阅读器等辅助技术“说明”当前状态,不触发任何 DOM 行为约束。它不会禁用输入、不会拦截粘贴、也不会影响焦点或提交逻辑。
常见错误写法:<input type="text" value="订单号" aria-readonly="true"> —— 这个输入框依然能被修改,但屏幕阅读器却在说“只读”,造成语义与实际严重脱节。
哪些元素必须同时用 readonly 和 aria-readonly
只有两类场景需要配对使用:
- 自定义可编辑组件(如
<div role="textbox" contenteditable="true">),这类元素没有原生 <code>readonly支持,必须设contenteditable="false"+aria-readonly="true"才能让 NVDA/JAWS/VoiceOver 正确识别 - React/Vue 等框架中动态切换只读态:JS 设置
el.readOnly = true后,必须同步调用el.setAttribute('aria-readonly', 'true'),否则服务端渲染(SSR)与客户端 hydration 状态不一致,无障碍树可能错乱 - 在已加
disabled的元素上再设aria-readonly="true"→ 冲突:disabled 已隐含“不可编辑”,ARIA 层叠会导致屏幕阅读器误读为“既禁用又只读” - 写
aria-readonly="false"声称“可编辑”,但实际没放开readonly或没移除事件拦截 → 辅助工具被误导,用户尝试输入却失败 - 只设
aria-readonly="true"却漏掉readonly或contenteditable="false",也没监听beforeinput/keydown并preventDefault()→ 用户照常能输、能粘贴、能删,无障碍体验反而更差 - 对原生表单控件,始终以
el.readOnly(JS)或readonly(HTML)为唯一事实来源 -
aria-readonly仅在 JS 动态控制时做同步:比如el.readOnly = isLocked; el.setAttribute('aria-readonly', isLocked.toString()) - 配合
aria-label明确内容意图(如aria-label="系统生成编号:ABC-2026-0723"),避免依赖placeholder或value让辅助工具猜测
原生 <input type="text"> 或 <textarea></textarea> 直接加 readonly 就够了,aria-readonly 属于冗余,除非你在 JS 中频繁切换状态且需显式同步语义。
容易踩的坑:disabled 上加 aria-readonly、false 值、漏事件拦截
以下组合全是反模式:
生产环境建议:readonly 为事实源,aria-readonly 仅作同步补充
真正关键的不是属性有没有,而是语义是否对齐:
最常被忽略的一点:只读状态若涉及富文本、日期选择器等封装组件,内部原生 input 必须透传 readonly,而不是只加在容器上——否则语义和行为都在父层断开了。











