13表单与微交互

表单本质是「对话」:用户说 → 表单听 → 系统应。HTML/CSS 负责给表单装上 「眼睛和耳朵」——状态机把每一步亮给用户看, 步进表单把长表单切成小步, details / summary让折叠区域不写 JS 也能展开。 这一课让表单从「静态收集器」变成「会反馈的小助手」。

约 16 分钟 6 个动手实验 状态机 + 步进 + 折叠

一、表单状态机:把表单想象成对话

一个表单从来不是「一个输入框」。它是一台状态机—— 用户在每一个瞬间都让表单处于某种状态。视觉上要做的,是把这八种状态一一区分: 空闲(idle)、聚焦(focus)、填写中(dirty)、已填(filled)、 校验失败(invalid)、提交中(submitting)、提交成功(success)、提交失败(error)。

幸运的是,浏览器原生给我们准备好了状态钩子—— :focus、:placeholder-shown、 :user-invalid、:disabled、[aria-busy="true"]。 CSS 拿到这些钩子,就能给每一种状态画上不同的样子。

表单八状态 × 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 vs :user-invalid 怎么选—— :invalid 是「当前值不合规」就触发,页面刚加载空表单时就红一片; :user-invalid 是「用户操作过且仍不合规」才触发,体验更克制。 现代项目首选 :user-invalid。

二、步进表单 Stepper:长表单拆小步

注册页有 12 个字段,一屏装不下、用户看怕了、退出率飙升—— Stepper(步进器)的解法是把长表单拆成「账号 → 个人信息 → 偏好 → 确认」4 步。 每步只露一组字段,进度条告诉用户「还差几步」,心理负担立减一半。

关键技术有三件:<progress> 做进度条、 <fieldset disabled> 一键冻结非当前步、 用一组隐藏 <input type="radio"> + :checked + 兄弟选择器切换当前面板——全程纯 CSS,零 JS。

Stepper 三件套:进度条 + 冻结面板 + 切换器
角色HTML 元素作用
进度条<progress>展示「还剩几步」
步骤面板<fieldset disabled>非当前步一键冻结(不能输入也不能提交)
切换器radio + :checked用纯 CSS 在不同面板间切换,无需 JS
步骤连线flex + 渐变背景把进度条拆成 N 段,已完成段着色
四步注册步进器:点「下一步/上一步」切换步骤,看 fieldset disabled 冻结非当前步、进度条同步高亮
<fieldset disabled> 一键冻结组内所有表单控件: 不能输入、不能聚焦、不能提交。这正是 Stepper 想要的—— 用户没法跳步操作、也没法在步骤 2 提交步骤 1 的数据。 disabled 比 JS 手动控制每个 input 可靠得多。

三、details / summary:原生折叠区域

以前做折叠面板要写 JS 控制 display: none、再监听点击切换。 HTML5 给了一个零 JS 方案:<details> + <summary>。 点 summary 就展开 details,点回去就收起,浏览器原生搞定。

details / summary 关键属性与典型场景
属性 / 用法作用典型场景
open默认展开(布尔属性)首次访问就展示的内容
summary折叠标题(必须为 details 的第一个子元素)始终可见的点击区
嵌套 details折叠里再嵌折叠(手风琴菜单)分类目录、设置项
name 属性同 name 的多个 details 形成「手风琴」(互斥展开)FAQ 问答
表单条件字段勾选「订阅推送」→ 展开通知偏好设置渐进披露表单字段
表单条件字段:勾选「订阅推送」→ 自动展开通知偏好 details;勾掉 → 收起。零 JS,纯 HTML5

四、表单的视觉反馈:聚焦环 + 错误震动

第 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" 立即通知红色背景 + ⚠ 字符
点下方四个状态标签切换:看按钮颜色、指示器颜色、提示文字同步变化(纯 CSS 演示,零 JS)
提交中的无障碍三件套:
① :disabled:鼠标点不动、键盘 Tab 跳过;
② aria-busy="true":告诉读屏软件「正在忙,请等待」;
③ 文字切换:从「保存」→「提交中…」→「已保存」或「重试」
少任何一件都是残缺——视觉用户看得到旋转圈, 但读屏用户只能靠 aria 属性判断状态。

七、综合实战:完整注册表单(纯 HTML + CSS)

把前几课所学全部拼装:一个三步骤注册表单,零 JavaScript, 仅靠 HTML5 + CSS3 实现:stepper 三步切换、details 折叠通知偏好、 必填字段红星、聚焦边框放大、错误抖动、状态展示一应俱全。

真实项目里,步骤切换由 JS 控制 radio 的 checked 属性, 提交由 JS 处理 fetch。本课先让你看清 HTML/CSS 的天花板—— 离开 JS,HTML 也能完成大部分微交互。

完整三步骤注册:点底部「上一步/下一步」切换——看进度条、聚焦发光、必填红星、折叠通知偏好全联动(纯 HTML + CSS)

八、小结

  • 表单是状态机,八种状态用 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 钩子。