多步骤表单必须用 javascript 驱动,单靠 html 无法实现分步状态;应使用 fieldset+legend 封装各步以保障可访问性与批量控制,禁用非当前步 fieldset,动态管理 required 属性,保存 session 状态并正确处理焦点与按钮类型。

多步骤表单不能靠纯 HTML 实现,form 标签本身没有“下一步”状态,所有分步逻辑必须由 JavaScript 驱动;否则要么全表单一次性提交,要么数据断裂、校验失效、可访问性崩溃。
为什么用多个 form 标签一定会失败
常见错误是给每步写一个独立 form,比如:<form id="step1">...</form>
<form id="step2">...</form>。这会导致:
- 用户填完第 1 步后点“下一步”,第 1 步的字段值无法自动带到第 2 步——DOM 中两个
form完全隔离 - 最后点“提交”时,浏览器只序列化当前
form内字段,后端收不到完整数据 - Tab 键导航会跳过整步(因为
form是焦点边界),键盘用户操作断裂 - 屏幕阅读器无法感知“步骤”语义,
legend和fieldset的上下文丢失
fieldset + legend 是唯一靠谱的结构封装方式
不是为了样式好看,而是解决两个硬性需求:可访问性和批量控制。
- 每个步骤必须用
fieldset包裹,legend写标题(如<legend>第2步:联系方式</legend>),屏幕阅读器能自然读出步骤上下文 - 切换步骤时,对非当前
fieldset设置disabled属性(el.disabled = true),它会自动禁用所有子控件,防止误操作和键盘跳转 - 千万别用
div替代——它没有disabled批量能力,也没有语义归组,测试工具会直接报无障碍缺陷 -
fieldset必须是form的直系子元素,不能嵌套在其他容器里,否则部分旧浏览器渲染异常
动态开关 required 是绕不过去的坎
浏览器原生校验不认 CSS 显隐,只要 input 在 form 里且带 required,哪怕 display: none 或 aria-hidden="true",form.checkValidity() 仍会把它算进去。
- 切到第 2 步前,先清除所有非当前步字段的
required:document.querySelectorAll('input[required]').forEach(el => el.removeAttribute('required')) - 再给当前步字段加回:
document.querySelectorAll('[data-step="2"] input').forEach(el => el.setAttribute('required', '')) - 更稳妥的做法是彻底弃用
required,改用 JS 手动校验:validateStep(2)只查[data-step="2"] input, [data-step="2"] select - “下一步”按钮必须是
type="button",否则回车或点击会触发整个form默认提交
别忽略 sessionStorage 和焦点管理
用户刷新页面或点浏览器后退键,表单状态默认全丢——这不是体验问题,是功能缺失。
- 每次切步前存两样东西:
sessionStorage.setItem('formStep', String(nextIndex))和sessionStorage.setItem('formData', JSON.stringify(getFormData())) - 页面加载时,在 DOM ready 后立刻读取并还原:
const savedStep = sessionStorage.getItem('formStep'),存在就调showStep(Number(savedStep)) - 切换步骤后,必须把焦点移到当前
fieldset下第一个可聚焦元素:document.querySelector('[data-step="2"] input:not([type="hidden"])').focus() - 如果某步首个字段是
type="hidden"或disabled,得主动跳到下一个有效字段,否则键盘用户卡死
最易被忽略的是:所有中间按钮必须是 type="button",且必须调 event.preventDefault();否则哪怕结构、校验、存储都对了,第一步点“下一步”也会整页跳转或清空输入。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











