联系表单必须用语义化<form>包裹,含method和action属性;每项输入须配显式<label for>绑定;多选组用<fieldset><legend>;置于<main>内且错误消息需aria-live播报。

用 <form> 包裹整个联系区域,别用 <div> 伪装
表单必须是独立的语义容器,<form> 不只是样式分组工具,它自带提交逻辑、隐式 ARIA role(role="form"),且浏览器会据此管理焦点和键盘行为。用 <div class="contact-form"> 替代 <form>,会导致屏幕阅读器完全忽略“这是一个可交互表单”的事实。
- 必须有
method和action(哪怕 JS 拦截提交,也要写合法值,如action="#") - 避免把
<form>套在<table>或<aside>里——某些旧版 Safari 会丢弃子节点 - 如果页面有多个表单(比如页脚也有订阅框),每个都应是独立
<form>,不能共用一个
<label> 必须显式绑定每个输入项,for/id 是底线
只靠视觉对齐或 CSS 位置关系,无法让屏幕阅读器把“邮箱”和后面的 <input type="email"> 关联起来。显式绑定是唯一可靠方式。
-
<label for="email">邮箱地址</label><input id="email" type="email">—— 这是最稳妥写法 - 嵌套写法(
<label>邮箱地址<input></label>)虽可行,但容易在动态渲染时破坏结构,尤其配合 React/Vue 的条件渲染 - 绝对不要出现
<input>没配<label>的情况;纯图标按钮(如“发送”)需加aria-label="发送联系表单"
用 <fieldset> + <legend> 分组逻辑相关字段
联系表单里常有“您的偏好”这类多选区域(如勾选接收邮件/短信),这类成组控件必须用 <fieldset> 包裹,否则屏幕阅读器用户无法得知这些复选框属于同一语义组。
-
<legend>必须是<fieldset>的第一个子元素,且不可用aria-label替代 - 不要用
<div role="group">模拟,JAWS 2021 及更早版本可能跳过该 group - 单个输入项(如姓名、电话)不需要
<fieldset>,滥用反而增加冗余节点
<main> 里放联系表单,但别让它“被吞掉”
联系表单通常是页面核心内容,理应落在 <main> 内部。但常见错误是:把 <main> 套进 <section> 或 <div>,或把它放在 <footer> 里——这会让可访问性树断裂,用户按快捷键“跳转到主要内容”时直接跳过表单。
立即学习“前端免费学习笔记(深入)”;
-
<main>必须是<body>的直接子元素,且全页只能有一个 - 允许
<main>内嵌<header>(比如表单标题)、<section>(比如说明文字),但别用<section>替代<main> - 如果联系表单是模态弹窗,仍需确保其 DOM 在
<main>内(而非<body>底层),并用aria-modal="true"和aria-labelledby补充上下文
最容易被忽略的是:表单验证失败后,错误消息没通过 aria-live 或 role="alert" 主动播报,用户卡在输入框却不知道哪里错了。这不属于语义标签本身,但它是语义结构生效后的必接环节——没有它,前面所有标签都白搭。



















