13表单与微交互
表单本质是「对话」:用户说 → 表单听 → 系统应。HTML/CSS 负责给表单装上 「眼睛和耳朵」——状态机把每一步亮给用户看, 步进表单把长表单切成小步, details / summary让折叠区域不写 JS 也能展开。 这一课让表单从「静态收集器」变成「会反馈的小助手」。
一、表单状态机:把表单想象成对话
一个表单从来不是「一个输入框」。它是一台状态机—— 用户在每一个瞬间都让表单处于某种状态。视觉上要做的,是把这八种状态一一区分: 空闲(idle)、聚焦(focus)、填写中(dirty)、已填(filled)、 校验失败(invalid)、提交中(submitting)、提交成功(success)、提交失败(error)。
幸运的是,浏览器原生给我们准备好了状态钩子——
:focus、:placeholder-shown、
:user-invalid、:disabled、[aria-busy="true"]。
CSS 拿到这些钩子,就能给每一种状态画上不同的样子。
| 状态 | CSS 钩子 | 用户感知 | 典型视觉 |
|---|---|---|---|
| idle 空闲 | :placeholder-shown | 什么都没发生 | 灰色占位文字、淡边框 |
| focus 聚焦 | :focus | 正在被操作 | 橘色发光圈、边框加深 |
| dirty 填写中 | :not(:placeholder-shown) | 用户正在打字 | 边框由灰转金、底色变暖 |
| filled 已填 | :valid | 值合法 | 绿对勾、边框稳定 |
| invalid 无效 | :user-invalid | 必填未填或格式错 | 红框、抖动、文字提示 |
| submitting 提交中 | [aria-busy="true"] | 正在请求服务端 | 按钮转圈、不可再点 |
| success 成功 | [data-state="ok"] | 提交成功 | 绿色 ✓、成功提示 |
| error 失败 | [data-state="err"] | 网络或服务异常 | 红色 ⚠、错误详情 |
:invalid 是「当前值不合规」就触发,页面刚加载空表单时就红一片;
:user-invalid 是「用户操作过且仍不合规」才触发,体验更克制。
现代项目首选 :user-invalid。二、步进表单 Stepper:长表单拆小步
注册页有 12 个字段,一屏装不下、用户看怕了、退出率飙升—— Stepper(步进器)的解法是把长表单拆成「账号 → 个人信息 → 偏好 → 确认」4 步。 每步只露一组字段,进度条告诉用户「还差几步」,心理负担立减一半。
关键技术有三件:<progress> 做进度条、
<fieldset disabled> 一键冻结非当前步、
用一组隐藏 <input type="radio"> + :checked
+ 兄弟选择器切换当前面板——全程纯 CSS,零 JS。
| 角色 | HTML 元素 | 作用 |
|---|---|---|
| 进度条 | <progress> | 展示「还剩几步」 |
| 步骤面板 | <fieldset disabled> | 非当前步一键冻结(不能输入也不能提交) |
| 切换器 | radio + :checked | 用纯 CSS 在不同面板间切换,无需 JS |
| 步骤连线 | flex + 渐变背景 | 把进度条拆成 N 段,已完成段着色 |
disabled 比 JS 手动控制每个 input 可靠得多。三、details / summary:原生折叠区域
以前做折叠面板要写 JS 控制 display: none、再监听点击切换。
HTML5 给了一个零 JS 方案:<details> + <summary>。
点 summary 就展开 details,点回去就收起,浏览器原生搞定。
| 属性 / 用法 | 作用 | 典型场景 |
|---|---|---|
| open | 默认展开(布尔属性) | 首次访问就展示的内容 |
| summary | 折叠标题(必须为 details 的第一个子元素) | 始终可见的点击区 |
| 嵌套 details | 折叠里再嵌折叠(手风琴菜单) | 分类目录、设置项 |
| name 属性 | 同 name 的多个 details 形成「手风琴」(互斥展开) | FAQ 问答 |
| 表单条件字段 | 勾选「订阅推送」→ 展开通知偏好设置 | 渐进披露表单字段 |
四、表单的视觉反馈:聚焦环 + 错误震动
第 08 课讲了 outline:键盘聚焦时浏览器给一个蓝色发光圈。 但「会反馈的表单」要做得更多:聚焦时边框放大、 发光、微微抬升让用户知道「这里在响应我」; 输入错误时水平抖动一下,让用户「啊,我填错了」。
三件套实现:transition 让聚焦过渡丝滑,
box-shadow 做发光圈,@keyframes shake 配合
animation 跑一次水平抖动。
aria-live="polite" 区域放成功 / 失败提示,让读屏软件也能感知。
五、必填与可选的视觉化
表单字段有两种状态:必填(必填字段)和
可选(补充信息)。视觉上要明确告诉用户——
必填字段配红星(红色 *),可选字段标「(可选)」
灰色括号。一种约定俗成的视觉契约。
实现用 label::after 伪元素:给必填字段的 label 加类名
.req,CSS 自动追加红星;给可选字段的 label 加类名
.opt,自动追加灰色「(可选)」。无需为每个 label 单独写星号。
| 类型 | 视觉 | CSS 写法 |
|---|---|---|
| 必填字段 | 标签后跟红色 * | .req::after { content: " *"; color: red; } |
| 可选字段 | 标签后跟灰色「(可选)」 | .opt::after { content: " (可选)"; color: #999; } |
| placeholder | 输入框内灰色文字 | HTML 属性 placeholder="…" |
| 帮助文字 | 输入框下方小字提示 | <p class="hint">…</p> |
*,右侧一个可选字段标签带灰字「(可选)」:把 .req 改成 .opt 看红星变灰字#B3472E(站内主色)。
星号必须挂在 label 后,不能挂在 input 后:
读屏软件朗读 label 时才能听到「必填」字样,星号只是视觉提示。
想给读屏用户更明确的提示,可以在 label 里包一个
<span class="sr">必填</span> + CSS 隐藏视觉。六、表单提交的状态展示
提交按钮不是「按下就没了」那么简单。一个会说话的表单,
会把提交过程切成 空闲 → 提交中 → 成功 / 失败 四种状态,
每种给用户清晰提示:空闲态按钮显示「保存」并可点击;
提交中按钮 :disabled + aria-busy="true" + 旋转图标;
成功显示绿色 ✓ + 「保存成功」;失败显示红色 ⚠ + 错误详情。
这里用一组隐藏 radio + :checked 兄弟选择器实现状态切换器——
零 JS 也能演示四种状态,真实项目由 JS 在
fetch 生命周期里切换状态。
| 状态 | 视觉 | 关键属性 | 关键代码 |
|---|---|---|---|
| idle 空闲 | 橘色「保存」按钮 | type="submit" | 普通按钮 + 渐变背景 |
| submitting 提交中 | 灰色 + 旋转圈 | disabled + aria-busy="true" | 伪元素 ::before 旋转动画 |
| success 成功 | 绿色 ✓ + 「已保存」 | aria-live="polite" 区域提示 | 绿色背景 + ✓ 字符 |
| error 失败 | 红色 ⚠ + 错误详情 | role="alert" 立即通知 | 红色背景 + ⚠ 字符 |
①
:disabled:鼠标点不动、键盘 Tab 跳过;②
aria-busy="true":告诉读屏软件「正在忙,请等待」;③ 文字切换:从「保存」→「提交中…」→「已保存」或「重试」
少任何一件都是残缺——视觉用户看得到旋转圈, 但读屏用户只能靠 aria 属性判断状态。
七、综合实战:完整注册表单(纯 HTML + CSS)
把前几课所学全部拼装:一个三步骤注册表单,零 JavaScript, 仅靠 HTML5 + CSS3 实现:stepper 三步切换、details 折叠通知偏好、 必填字段红星、聚焦边框放大、错误抖动、状态展示一应俱全。
真实项目里,步骤切换由 JS 控制 radio 的 checked 属性,
提交由 JS 处理 fetch。本课先让你看清 HTML/CSS 的天花板——
离开 JS,HTML 也能完成大部分微交互。
八、小结
- 表单是状态机,八种状态用 CSS 钩子一一映射:
:focus/:user-invalid/:placeholder-shown/:disabled/[aria-busy]/[data-state]。 - 长表单用 Stepper 拆小步,四件套:
<progress>进度条 +fieldset disabled冻结 + radio + :checked 切换面板 + flex 进度条分段着色。 <details> / <summary>是零 JS 折叠,open默认展开、name同组互斥(手风琴)、可嵌套。- 聚焦反馈三件套:边框变粗 + box-shadow 发光 + transform 微抬;
错误反馈用
@keyframes shake+:user-invalid。 - 必填字段用
label.req::after { content: " *"; color: red }红星; 可选字段用label.opt::after { content: " (可选)" }灰字。 - 提交按钮四态:idle / submitting / success / error,
:disabled+aria-busy+ 文字切换,缺一不可。 - 完整注册可纯 HTML + CSS 实现——原型阶段足够,真实项目加 JS 接管交互。
*」,密码字段标「(可选)」因为已改成验证码;
失焦后邮箱空值触发错误抖动;
最后加一个「提交中…」「登录成功 ✓」「登录失败 ⚠」三状态指示器。
全程禁用 JS,只用这一课讲的 HTML/CSS 钩子。