表单结构与校验Form Structure & Validation
先看怎么选
用户填错了,什么时候告诉他、在哪告诉他?校验时机有三种落点——边填边说的、点提交才集中说的、服务器最后把关的。选错时机的代价:用户填完 10 个字段点提交,才发现第 1 格就错了,直接弃填。
朱批格式对不对,离开字段就说(实时);全表齐不齐,点提交集中说(提交时);数据真不真,服务器说了算(永远要有)。三层是组合,不是三选一。
试试看:
- 左边点进「昵称」敲 1 个字就点别处 → 立刻报错;右边同样操作 → 不吭声,点「注册」才集中报错并跳到第一个错误
- 两边都填对后提交:用
taken@example.com→ 客户端全过,服务器仍会打回(服务器端校验兜底,错误映射回邮箱字段)
实时校验
用户离开某个字段(Blur)时就地检查、就地报错,错误文案显示在字段正下方;用户回来修改时错误随输入消失。
- 适合
- 格式类规则——邮箱格式、密码长度、必填。字段较多的注册/设置表单尤其需要:早发现早修,不让错误堆到最后一刻。
- 不适合
- 不要在用户第一个字符还没敲完就报错(逐键校验太吵,用户还没填完就被骂);不要用于需要服务器才能判断的规则(邮箱是否已注册),那是服务器端校验的事。
实现细节与 Prompt
- 触发时机
- Blur 检查、Input 清错——「先宽后严」:出错前不打扰,出错后立刻响应修复
- 错误位置
- 字段正下方,不遮挡输入内容;role="alert" 让屏幕阅读器立即播报
- 可访问性
- aria-invalid="true" + aria-describedby 指向错误文案的 id
Add inline validation to this form.
Rules:
- Validate each field on blur (when the user leaves the field), not on every keystroke
- Show the error message directly below the field, in the error color (#A02C2C)
- Clear the error as soon as the user starts correcting the input
- Set aria-invalid="true" and link the message via aria-describedby
- Error messages must say what is wrong AND how to fix it
(e.g. "邮箱格式不正确,请输入完整邮箱 user@example.com")
提交时校验
用户点「提交」时一次性检查全部字段:逐字段就地标错,错误多于一条时在表单顶部给一条汇总,并把焦点移到第一个出错字段。
- 适合
- 字段很少的表单(登陆就两格);规则之间有联动(两次密码必须一致);以及作为实时校验之后的最后一道客户端闸门——提交前再整体查一遍。
- 不适合
- 长表单不要只依赖提交时校验——用户填完一大页才知道开头就错了,这是弃填率最高的设计。报错后绝对不能清空用户已填的内容。
实现细节与 Prompt
- 错误汇总
- 多条错误时表单顶部放 Alert 汇总,逐字段仍然就地标错(两处都要)
- 焦点管理
- 提交失败自动 focus 到第一个错误字段,键盘用户不用自己找
- 防重复提交
- 提交中按钮 Loading + disabled,防止网络慢时用户连点
Add on-submit validation to this form.
Rules:
- On submit, validate all fields at once; prevent submission if any fail
- Mark each invalid field inline (error border + message below the field)
- If more than one error, show a summary Alert at the top of the form
listing all errors as anchor links to the fields
- Move keyboard focus to the first invalid field
- Never clear what the user has already typed
- While submitting: disable the submit button and show a loading spinner
服务器端校验
数据到达服务器后的最终检查——唯一可信的一道。客户端校验都可以被绕过(禁用 JS、直接发请求),服务器端校验是安全底线,永远不能省。
- 适合
- 一切表单都要有,没有例外。尤其是只有服务器知道的规则:邮箱已被注册、库存不足、权限不够。返回的错误要映射回具体字段就地显示,让用户知道改哪里。
- 不适合
- 不要把它当成唯一的校验——格式错误也要等一次网络往返才知道,体验差;不要把服务器错误只用 Toast 一闪而过——会消失的提示承载不了「需要用户处理」的错误。
实现细节与 Prompt
- 定位
- 客户端校验是体验优化,服务器端校验是安全边界——前者可省心,后者不可省
- 错误映射
- 接口返回字段级错误(field + message),前端映射到对应 Input 的 Error 态
- 等待反馈
- 请求期间按钮 Loading + 禁用;失败后保留用户输入,就地显示服务器消息
Add server-side validation handling to this form.
Rules:
- The API returns errors as { field, message } pairs; map each one onto
the matching input's error state (error border + message below)
- Errors that belong to no single field (e.g. "rate limited") go into a
persistent Alert above the form — never a self-dismissing toast
- Keep all user input intact after a failed submission
- While the request is in flight: disable the submit button, show a
spinner, and set aria-busy="true" on the form
详细说明
表单结构与校验
它是什么
表单 = 多个输入框 + 一个提交按钮 + 一套「什么时候告诉用户错了」的契约。
输入框怎么选 type、按钮怎么分主次,分别见 №03「输入框最佳实践」和「按钮怎么选」。这个词条讲把它们组装成表单时的两件事:
- 结构——字段怎么排、标签放哪、按钮放哪;
- 校验——三层校验(实时 / 提交时 / 服务器端)各管什么、怎么组合。
新手最常见的误解是把三层校验当成“三选一”。正确的理解:实时校验管体验,提交时校验管兜底,服务器端校验管安全——短表单可以省第一层,任何表单都不能省最后一层。
快速判断
| 要检查什么 | 用哪层 | 什么时候说 |
|---|---|---|
| 格式对不对(邮箱、长度、必填) | 实时校验 | 离开字段(Blur)就说 |
| 全表齐不齐、字段间联动 | 提交时校验 | 点提交时集中说 |
| 数据真不真(已注册、没库存) | 服务器端校验 | 响应回来后映射到字段说 |
组合原则
- 三层是叠加关系:实时管体验、提交时管兜底、服务器端管安全
- 短表单(登陆)可以省实时校验,长表单(注册)三层都要
- 服务器端校验在任何表单里都不能省——它是唯一不可绕过的一层
表单结构五条基本法
1. Label 放字段上方,不放字段里面
placeholder 会在用户开始输入时消失——用 placeholder 当 label,用户填到一半就忘了这格是干什么的。label 独立放在字段上方,任何时刻都可见,移动端单列下目光流也最顺(自上而下扫一遍)。
2. 单列布局,不要双列
双列表单的目光流是 Z 字形,用户容易漏掉右列字段;单列自上而下,一格一格填完就是填完了。例外只有语义上强绑定的短字段(省份 / 城市,姓 / 名)。
3. 相关字段分组
超过 6-8 个字段就该分组(「账户信息」「收货地址」),组间留大间距或加分组标题。更长的表单考虑拆步——见 №02「Stepper」。
4. 必填标记要一致
要么全部必填字段标 *,要么反过来只标「(可选)」——混用两种标记用户会困惑。大多数字段必填时,标可选的那几个更省眼睛。
5. 主按钮放表单流末尾,Primary 只有一个
用户填完最后一格,视线落点就是提交按钮。「提交」用 Primary,「取消 / 上一步」用 Secondary 或 Ghost(见「按钮怎么选」)。绝不并排两个 Primary。
技术标注
| 考虑点 | 决策 |
|---|---|
| 校验分层 | 实时(Blur)→ 提交时(全表)→ 服务器端(最终);后一层永远兜住前一层 |
| 错误位置 | 字段级错误在字段正下方;表单级错误在顶部 Alert(不用会消失的 Toast) |
| 错误文案 | 说清「什么错了 + 怎么改」:「邮箱格式不正确,请输入完整邮箱」而不是「输入有误」 |
| 错误标记 | 颜色(#A02C2C 边框)+ 文字说明,双通道——只靠变红,色弱用户看不见 |
| 焦点管理 | 提交失败自动 focus 第一个错误字段 |
| 防重复提交 | 提交中按钮 disabled + Loading(Spinner + 「提交中…」) |
| 可访问性 | aria-invalid + aria-describedby 关联错误;错误容器 role="alert" |
什么时候用这个词
做设计决策时
场景:注册表单的校验策略
表单:昵称 + 邮箱 + 密码 + 确认密码,共 4 个字段
逐条决策:
昵称长度 2-20 → 格式规则 → 实时校验(Blur 时查)
邮箱格式 → 格式规则 → 实时校验
两次密码是否一致 → 字段联动 → 提交时校验(第二格填完前查不了)
邮箱是否已被注册 → 只有服务器知道 → 服务器端校验,
错误映射回邮箱字段:「该邮箱已被注册」
结果:三层全用上,每层只管自己该管的。
反面场景:登陆表单
表单:邮箱 + 密码,共 2 个字段
字段太少,实时校验的收益小 → 可以只做提交时校验
「邮箱或密码错误」→ 服务器端校验,显示在表单顶部
(安全考虑:不说具体哪个错,防止撞库探测)
给 agent 发指令时
不要只说「加个校验」——agent 不知道你要哪层、什么时机、错误放哪。说清三件事:
给这个注册表单加校验:
1. 昵称(2-20 字符)和邮箱(格式)在 Blur 时实时校验,错误显示在字段下方
2. 提交时整表复查 + 检查两次密码一致,有错则顶部 Alert 汇总并 focus 第一个错误字段
3. 服务器返回「邮箱已注册」时,映射回邮箱字段就地显示,保留用户已填内容
常见错误
错误 1:只做客户端校验
客户端校验(JS)可以被禁用、被绕过、被直接发请求跳过。
后果:脏数据入库、安全漏洞(越权提交)。
正确做法:客户端校验只是体验优化,服务器必须重新校验一切。
错误 2:长表单只在提交时报错
用户填完 10 个字段 → 点提交 → 「第 1 格昵称太短」
用户心理:早干嘛去了?
后果:弃填。这是转化率杀手。
正确做法:格式类规则用实时校验,错误当场暴露当场修。
错误 3:报错后清空表单
提交失败 → 页面刷新 → 用户填的全没了
这是表单设计里最不可原谅的错误。
正确做法:无论哪层校验失败,用户已填内容一个字符都不能丢。
错误 4:只用颜色标错
出错字段只是边框变红,没有文字说明。
问题 1:色弱用户看不出红色边框
问题 2:就算看见红了,也不知道错在哪、怎么改
正确做法:颜色 + 文字双通道,错误文案说清「什么错 + 怎么改」。
错误 5:用「禁用提交按钮」代替校验
表单没填对之前提交按钮一直是灰的,但不说为什么。
用户对着灰按钮干瞪眼,不知道还差哪格。
正确做法:按钮保持可点,点了之后用提交时校验告诉用户差什么。
(按钮 disabled 只用于「提交中」防重复点击。)
相关词条
- №03「输入框最佳实践」 — 单个字段的 type 选择与状态设计,是表单的原料
- №03「按钮怎么选」 — 提交用 Primary、取消用 Ghost,表单的动作出口
- №04「表单验证提示」 — 字段下方那行小字的三种身份(Helper / Error / Success)
- №02「Stepper」 — 表单长到一定程度,拆步比硬撑一页更好
- №04「提示(怎么选)」 — 表单级错误用 Alert(钉在内容里),不用会消失的 Toast
完整可复制的 Prompt
设计一个注册表单,包含昵称、邮箱、密码、确认密码四个字段。
结构要求:
- 单列布局,label 在字段上方(不用 placeholder 当 label)
- 必填字段标 *;提交按钮 Primary(#C13E23)放表单末尾,full-width
- 字段高度 44px、字号 ≥16px、圆角 6px(遵循 TellYourAgent 设计系统)
校验要求(三层组合):
1. 实时校验(Blur 触发):
- 昵称 2-20 字符;邮箱格式检查
- 错误显示在字段正下方,颜色 #A02C2C,文案说清怎么改
- 用户重新输入时错误即刻消失
2. 提交时校验:
- 整表复查 + 两次密码一致性
- 多条错误时表单顶部 Alert 汇总;focus 移到第一个错误字段
- 提交中按钮 disabled + Spinner
3. 服务器端校验(模拟接口):
- 接口返回 { field, message } 数组,映射回对应字段就地显示
- 「邮箱已被注册」显示在邮箱字段下方;保留用户全部已填内容
可访问性:
- label/for 关联;aria-invalid + aria-describedby;错误容器 role="alert"
- 全键盘可操作,focus ring 可见(2px #C13E23 outline)
输出:HTML(语义化 <form>)+ CSS(所有状态)+ JS(三层校验逻辑)+ 使用说明。
为什么这个决策很重要
表单是产品里用户投入成本最高的界面——别的页面看看就走,表单要用户一格一格地填。
- 校验时机选错,用户填到最后才发现开头错了 → 弃填,转化率直接损失
- 只做客户端校验 → 脏数据、安全漏洞,上线后才爆
- 错误提示不说人话(「输入有误」)→ 用户不知道怎么改,反复试错后放弃
一句话:校验的三层不是技术选择,是对用户的承诺——错误越早暴露、越好修复,用户越愿意把表单填完。