:valid和:invalid这两个伪类来源于CSS Selectors Level 4,但真正让它们广泛应用的是HTML5表单约束验证机制。简单来说,当浏览器判断一个表单控件满足所有约束条件时,该控件匹配:valid;只要有一条约束未通过,就匹配:invalid。这个判断不是CSS层做的,而是浏览器原生能力,因此状态更新无需任何脚本参与。

不过很多开发者在第一次使用时就会遇到一个问题:用户还没输入任何内容,非必填字段并不会触发:invalid,但带有required的字段却会在一开始就被判定为无效。这种初始状态的差异会影响视觉反馈的时机,进而影响用户体验。因此,本文会从验证前提、样式设计到状态控制逐步展开。
一、伪类生效的基础:HTML5约束验证
要让:valid和:invalid真正起作用,表单控件必须带有可被浏览器识别的约束属性。常见的约束条件包括required、pattern、type="email"、min、max、minlength、maxlength以及step。这些属性会被浏览器内置的约束验证引擎读取,并在用户输入、控件失焦、表单提交等时刻重新计算有效性。
当一个<input>或<select>满足所有约束时,浏览器会将其内部有效性状态置为true,CSS层对应的匹配结果就是:valid;反之,只要任意一条约束失败,有效性状态为false,控件就匹配:invalid。需要注意的是,如果控件本身没有任何约束属性,比如一个普通的<input type="text">,它在大多数情况下既不算:valid也不算:invalid,而是处于一种中性状态。因此如果没有约束,这两个伪类就不会产生视觉差异。
另一个关键点在于初始状态。对于带有required的输入框,浏览器在页面加载后立即检查,发现值为空,就会将其判为无效。这意味着:invalid样式会从用户看到表单的第一秒就开始生效,即使他还没有机会填写任何内容。这种“过早报错”是实际使用中最常见的体验问题,后文会专门讨论如何控制反馈时机。
二、设计清晰的验证态样式
只用颜色来区分有效和无效并不够,因为色弱用户可能无法准确判断。更好的做法是同时改变边框颜色、背景色,并显示一段明确的提示文字。例如合法的输入框可以用绿色边框和浅绿色背景,非法的输入框用红色边框和浅红色背景,同时用文字说明具体错误原因。
下面是一个基础示例。HTML结构里把输入框和提示文字放在同一个容器中,方便使用相邻兄弟选择器来控制提示的显示与隐藏。
<form>
<div class="field">
<label for="username">用户名</label>
<input id="username" name="username" type="text" required minlength="3" maxlength="20">
<span class="hint">请输入3到20个字符</span>
</div>
<div class="field">
<label for="email">邮箱</label>
<input id="email" name="email" type="email" required>
<span class="hint">请输入有效邮箱地址</span>
</div>
</form>
对应的CSS通过:valid和:invalid修改输入框样式,并使用相邻兄弟选择器让提示文字只在无效时出现。
.field input {
border: 1px solid #ccc;
border-radius: 4px;
padding: 8px;
outline: none;
}
.field input:valid {
border-color: #2ecc71;
background-color: #f0fff4;
}
.field input:invalid {
border-color: #e74c3c;
background-color: #fff5f5;
}
.field .hint {
display: none;
font-size: 12px;
margin-top: 4px;
}
.field input:invalid + .hint {
display: block;
color: #e74c3c;
}
这种实现完全不依赖JavaScript,适合登录框、注册页等场景。不过它有一个明显缺点:错误提示文字是静态的,无法根据具体失败原因变化。如果用户在邮箱字段输入了abc,提示仍然是“请输入有效邮箱地址”,但不会提示缺少@符号。要生成更精确的动态提示,通常还是需要结合约束验证API(如validity.typeMismatch)来做,纯CSS无法读取具体的失败原因。
可访问性方面也要特别注意。纯CSS显示的错误提示不会自动被屏幕阅读器朗读,因为没有任何aria-live或role="alert"。对于无障碍要求较高的项目,建议在HTML中预先放置一个<span role="alert">,并通过JavaScript更新内容,CSS只负责视觉样式。
三、控制反馈时机与可访问性
前面提到,带有required的字段会在初始状态下直接匹配:invalid,这会让表单一打开就显得充满错误。较新的浏览器开始支持:user-invalid伪类,它只在用户与控件交互后(比如输入过内容或尝试提交过表单)才匹配无效状态。这样可以有效推迟错误提示的出现时机。
如果不考虑旧浏览器兼容,可以直接使用:user-invalid来替代原来的:invalid样式。
.input-field:user-invalid {
border-color: #e74c3c;
background-color: #fff5f5;
}
.input-field:user-invalid + .hint {
display: block;
color: #e74c3c;
}
对于需要兼容旧版本浏览器的项目,可以用组合选择器模拟类似效果。一种常见做法是:当输入框获得焦点时不显示错误,只有失焦且已经输入过内容后,才应用:invalid样式。这可以借助:not(:focus)和:placeholder-shown来实现,但:placeholder-shown要求输入框设置了placeholder属性,否则不生效。
/* 只在用户离开输入框且已经输入过内容时显示无效样式 */
.input-field:not(:focus):not(:placeholder-shown):invalid {
border-color: #e74c3c;
background-color: #fff5f5;
}
.input-field:not(:focus):not(:placeholder-shown):invalid + .hint {
display: block;
color: #e74c3c;
}
上述做法的思路是:初始为空且显示placeholder时,不触发无效样式;一旦用户输入内容,placeholder消失,如果仍然无效就显示错误。这种方案在大多数场景下能带来更温和的体验。不过它仍有局限,例如用户输入后马上删除内容,placeholder重新出现,错误样式又会消失,可能造成状态闪烁。
无论采用哪种方案,都应该保证输入框在聚焦时有清晰的焦点样式,并且错误提示不依赖于颜色本身。可以使用下划线、边框粗细变化、图标或文字前缀来辅助表达。对于使用屏幕阅读器的用户,建议同时提供aria-describedby指向提示元素,确保错误信息能够被读取出来。
总体来说,:valid和:invalid让表单验证样式变得异常轻量,配合HTML5约束验证可以实现零脚本的即时反馈。但在真实项目中,控制反馈时机和补充可访问性信息仍然需要一些额外技巧。理解这两个伪类的底层机制,并根据实际体验需求选择合适的延迟策略,才能把原生验证能力转化为真正友好的界面反馈。