每隔一段时间,前端圈子里就会冒出一轮”原生表单验证是不是过时了”的讨论。起因通常是这样的:有人写了个 required、type="email",发现提示框丑得像 Windows 98,于是转头就上了一个几十 KB 的表单校验库。
那问题来了:都 2026 年了,HTML 自带的这套东西,到底还值不值得用?
答案是:值得用,但不能只靠它。下面把这件事拆开说清楚。
先说清楚,原生验证到底是什么
浏览器原生支持的表单校验,专业名字叫 Constraint Validation API。核心就是这几个 HTML 属性:
<form>
<input type="email" required />
<input type="number" min="0" max="100" />
<input pattern="[0-9]{6}" title="请输入6位数字" />
<button type="submit">提交</button>
</form>
写完这几行,浏览器就会在你点提交的时候自动拦截,弹出一句”请填写此字段”之类的提示,连一行 JS 都不用写。
除了这些声明式属性,还有一套 JS API 配合使用,比如:
const input = document.querySelector('input');
input.checkValidity(); // 返回 true/false
input.setCustomValidity('用户名已被占用'); // 自定义错误提示
input.reportValidity(); // 主动触发提示框
这套东西不是新技术,十几年前就有了,但很多人对它的印象还停留在”提示框丑、样式改不了、没法做复杂校验”的阶段,所以一直被绕开。
这两年它其实进步了不少
如果你上一次认真看原生表单校验还是三五年前,有几个更新值得知道:
1. :user-valid 和 :user-invalid 这两个 CSS 伪类,现在主流浏览器(Chrome、Edge、Firefox、Safari)都已经支持,并且即将进入 Baseline 的”广泛可用”阶段。
它们和老牌的 :valid/:invalid 最大的区别是:只有等用户实际操作过这个输入框之后才会生效。
这个区别很关键。以前用 :invalid 最大的槽点就是,页面一加载,所有必填框还没人碰就已经是红色的了,体验很差。现在写法可以是:
/* 用户还没碰过,不显示任何错误样式 */
input:user-invalid {
border-color: red;
}
input:user-valid {
border-color: green;
}
也就是说,纯 CSS 就能实现”只在用户填错之后才提示”这种以前得靠 JS 判断 touched 状态才能做到的交互,这在业内已经是比较成熟、可以放心用的方案了。
2. 提示文案和样式的可控性变高了。setCustomValidity() 可以完全自定义错误文案,不再是浏览器写死的英文或者奇怪的翻译;配合 ::-webkit-validation-bubble 之类的私有伪元素(仍不是标准,但常用),原生提示气泡的外观也能做一部分调整。
所以原生方案已经不是”用了就丑””用了就没法自定义”的状态了,只是很多老项目和老教程还停留在旧印象里。
但它确实有做不到的事
诚实地说,原生验证不是万能的,下面这几类场景它天生就搞不定:
- 跨字段校验:比如”确认密码要和密码一致””结束日期不能早于开始日期”。原生 API 只能校验单个字段,跨字段逻辑得自己写 JS。
- 异步校验:比如”检查这个用户名有没有被注册过”。这必然要发请求,原生 API 完全不管这块。
- 复杂的展示逻辑:一次性展示所有错误、按分组展示、做进度式的表单向导——原生提示一次只弹一个气泡,不适合这种场景。
- 多语言文案的统一管理:原生错误提示的默认语言跟随浏览器设置,虽然能用
setCustomValidity覆盖,但要接入你自己的 i18n 系统还是得手写胶水代码。 - 可视化设计的高度定制:如果设计师给的稿子里,错误提示是带图标、带动画、跟随输入框位置精确浮动的组件,原生气泡基本满足不了,还是得自己实现 UI。
这些恰恰是 React Hook Form、Zod、Vee-Validate 这类库真正解决的问题——它们从来不是要取代 required,而是在原生能力不够的地方补位。
所以正确的用法是什么
比较务实的做法,是把原生验证当成”第一道免费的防线”,而不是全部家当:
- 能用原生属性表达的规则,就写在 HTML 里。
required、type、min、max、pattern、minlength、maxlength这些覆盖了大部分基础校验,浏览器会自动处理键盘焦点跳转、屏幕阅读器提示等无障碍细节,这些是自己用 JS 重新实现时很容易漏掉的。 novalidate和 JS 校验库不是对立关系,是分工关系。 复杂逻辑(跨字段、异步、自定义 UI)交给 JS 层,但底层数据类型这类基础校验依然可以保留原生属性作为兜底,尤其是在 JS 因为某些原因没加载成功的情况下,原生属性还能兜底基本的数据格式。- 服务端校验永远不能省。 不管前端用的是原生 API 还是某个校验库,浏览器端的东西用户都可以绕过(改 DevTools、直接发请求),所以真正决定数据是否合法的,必须是后端再校验一遍。这条规则跟原生验证进不进步没有任何关系,十年前是这样,2026 年也一样。
一个简单的判断标准
写表单之前,可以按这个顺序过一遍:
- 这个字段的规则,是不是”必填/格式/长度/数值范围”这种简单规则?→ 直接用 HTML 属性。
- 需要跟别的字段做比较,或者要问服务器要数据?→ 上 JS,用校验库也行,手写也行。
- 设计上对错误提示的样式、位置、动画有特殊要求?→ 用
novalidate关掉浏览器默认提示,自己接管 UI,但校验逻辑依然可以复用checkValidity()这类 API,不用把判断逻辑重写一遍。
小结
原生 HTML 表单验证在 2026 年不仅没有过时,反而因为 :user-valid/:user-invalid 这类新伪类的普及,变得比几年前更好用了。
它解决不了跨字段校验、异步校验和高度定制的 UI,这些场景该用库还是得用库。但把简单的基础校验都甩给 JS 库去做,既增加了包体积,也丢掉了浏览器原生自带的无障碍支持——这笔账,不划算。
一句话总结:能让浏览器干的活,先让浏览器干;浏览器干不了的,再麻烦 JS。