:read-write 真正能够匹配到的,其实只有三类原生可编辑表单元素:input(但会排除 hidden、radio、checkbox、file 等类型)、textarea,以及 select(不过 Safari 对这部分的支持相对较弱)。像 contenteditable 元素、range/color 这类输入控件,以及 button 等元素,都不在它的匹配范围内。还有一个很容易被忽略的细节:它是否命中,只取决于 HTML 属性本身是否声明,和 JS 运行时的状态变化没有直接关系。

哪些元素能被 :read-write 匹配到
:read-write 并不是“看起来能点击、能输入的元素都算”。从 CSS 伪类的实际匹配规则来看,它真正识别的只有三类原生可编辑元素:(但会排除 type="hidden"、"radio"、"checkbox"、"file" 等不属于文本输入的类型)、 和 。另外还需要注意, 在不同浏览器中的表现并不完全一致,Chrome 和 Firefox 通常支持较好,而 Safari 对它的 :read-write 匹配能力相对偏弱。
常见误判:
div[contenteditable="true"]默认并不会匹配:read-write—— 即使元素已获得焦点,也不会自动触发,除非你额外配合:focusinput[type="range"]或input[type="color"]虽然可以交互,但并不接受文本输入,多数浏览器不会将其识别为:read-writebutton、output、自定义 Web Component 即使绑定了事件或由 JS 控制,也完全不属于:read-write的作用范围
:read-write 的匹配逻辑取决于属性,不是 JS 状态
它只判断 HTML 属性是否存在,并不会根据 JavaScript 动态修改后的逻辑状态来决定匹配结果。比如:
→ 不匹配:read-write(布尔属性写法中,只要属性存在就立即生效)→ 依然匹配:read-only,因为只要readonly属性存在,属性值是真是假都不影响结果element.readOnly = false(JS 设置)→ 样式的确会更新,但前提是该属性原本就存在;如果 DOM 中一开始没有写readonly,JS 先设为true再改为false才会触发切换oninput="return false"或event.preventDefault()→ 完全不会影响:read-write的匹配,因为这个 CSS 伪类根本不会读取这些逻辑
和 :disabled、:read-only 共存时的样式优先级
这三个伪类的语义并不相同,但在渲染时存在明确的优先顺序::disabled > :read-only > :read-write。这意味着:
- 如果一个
input同时带有disabled和readonly属性,那么最终一定是:disabled样式生效,:read-only和:read-write都会被覆盖 :read-only和:read-write彼此互斥,但并不是所有表单元素都一定会进入这两者之一:例如input[type="hidden"]既不匹配:read-write,也不匹配:read-only,属于两个伪类都不会命中的情况- 不要指望用
:read-write去“撤销”:read-only的背景色——这两个伪类更适合分别独立定义完整样式,而不是依赖层叠覆盖来修正
实际用法建议:聚焦态比可写态更可靠
如果你的目标是给用户明确反馈“当前可以编辑”,那么 :read-write:focus 看起来很合理,但在实际 CSS 开发中问题并不少:
- Safari 直到 iOS 15.4+ 才开始支持
:read-write,旧版本浏览器中会直接失效 :read-write本身不提供交互反馈(例如 hover、active),它只反映静态的可写状态- 在真正需要强调“当前正在操作”或“当前输入中”的场景下,
:focus更通用,兼容性也更稳定
推荐写法:
input:not([readonly]):not([disabled]):focus,
textarea:not([readonly]):not([disabled]):focus {
border-width: 2px;
}
[contenteditable="true"]:focus {
border: 2px solid #007bff;
outline: none;
}
如果你必须区分“可编辑但未聚焦”和“已经聚焦”的状态,仅靠纯 CSS 很难通过 :read-write 完成——因为它不会响应鼠标悬停,也不能体现初始渲染前后的状态变化,这类需求通常还是要依赖 class 切换或 JS 事件监听来实现。
