HTML5 语义与原生校验
前两节已经能让学员填写报名信息。现在需要让页面结构更清楚:导航、课程通知和报名区域分别承担什么作用?邮箱写错或年龄超出范围时,浏览器能否在提交前提醒填写者?
本节先用语义元素划分学员管理页面,再为报名字段选择准确的输入类型和校验规则,最后组合成一张页面。完成后,要能指出主要区域,并用错误邮箱和超出范围的年龄触发浏览器提示。
页面语义结构
div 可以包裹任何内容,但它本身不说明这部分内容是导航、正文还是页脚。HTML 提供了一组语义元素,用标签名直接表达区域用途。
| 元素 | 常见用途 |
|---|---|
header | 页面或区域的头部 |
nav | 主要导航链接 |
main | 页面独有的主要内容 |
section | 有明确主题的内容区域 |
article | 可以独立理解或复用的内容 |
aside | 与主要内容相关的补充信息 |
footer | 页面或区域的尾部 |
下面是页面区域示意图。图中的颜色和排列只用于说明结构,不是语义元素自带的样式;窄屏下两栏会自动换行。
这张图展示一种可能的区域关系,不与下一段代码逐项对应。实际页面通常用 main 包住主要内容;section 和 article 谁在外层,要由内容含义决定。
学员管理页面示例
复制第一节的 HTML 文档骨架,把下面的 <body>...</body> 放进去,保存为 semantic-page.html 后用浏览器打开。本例只组织内容,不处理颜色、间距和布局:
<body>
<header>
<h1>学员管理系统</h1>
<nav aria-label="主要导航">
<a href="#pending">待处理报名</a>
<a href="#notice">课程通知</a>
<a href="#help">使用提示</a>
</nav>
</header>
<main>
<section id="pending">
<h2>待处理报名</h2>
<p>当前有 3 条报名记录等待审核。</p>
</section>
<section id="notice">
<h2>课程通知</h2>
<article>
<h3>周末班开课提醒</h3>
<p>请在开课前完成环境检查。</p>
</article>
</section>
<aside id="help">
<h2>使用提示</h2>
<p>新增学员前,请先确认手机号没有重复。</p>
</aside>
</main>
<footer>
<small>© 2026 学员管理系统</small>
</footer>
</body>外观预览如下。这里的样式只用于课件展示,语义结构和导航行为以上方代码为准:
请在开课前完成环境检查。
打开页面后,点击三个导航链接,地址栏的片段标识会变化;页面足够长时,还会滚动到对应的 id。代码中“课程通知”是页面的一个 section,其中的“周末班开课提醒”离开当前页面也能独立理解,所以用 article 包裹。这与示意图中 article 包住 section 的结构不同,但都由实际内容决定。
一个页面通常只放一个代表主要内容的 main。header 和 footer 既可以描述整个页面,也可以描述某个独立区域。语义元素负责说明结构,不会自动完成视觉布局,页面样式仍由 CSS 处理。
section、article 与 div 的使用边界
选择容器时依次判断:
- 内容有明确主题,是页面中的一个区域:考虑
section。 - 内容离开当前页面仍能独立理解:考虑
article。 - 只是为了分组、布局,或没有更合适的语义:使用
div。
不要为了使用新标签而把每个 div 都替换成 section。语义来自内容用途,不来自标签的新旧。
表单输入类型
浏览器不仅会显示输入框,还会根据 type 提供合适的键盘、选择器和基础校验。先看报名页面中常见的类型:
<label for="email">邮箱</label>
<input id="email" name="email" type="email" autocomplete="email">
<label for="mobile">手机号</label>
<input id="mobile" name="mobile" type="tel" autocomplete="tel">
<label for="age">年龄</label>
<input id="age" name="age" type="number" min="16" max="80" step="1">
<label for="birthday">出生日期</label>
<input id="birthday" name="birthday" type="date">
<label for="homepage">个人主页</label>
<input id="homepage" name="homepage" type="url">
<label for="keyword">搜索学员</label>
<input id="keyword" name="keyword" type="search">预览效果如下:
| 类型 | 适合的数据 | 浏览器可能提供的帮助 |
|---|---|---|
email | 邮箱地址 | 邮箱键盘和基础格式校验 |
tel | 电话号码 | 电话键盘,不自动验证真实手机号规则 |
number | 可计算的数值 | 数值输入和范围、步长校验 |
date | 日期 | 日期选择器 |
url | 网址 | 网址键盘和基础格式校验 |
search | 搜索关键词 | 与搜索场景匹配的输入体验 |
不同浏览器和系统的控件外观可能不同,因此仍要使用 label 说明业务含义。手机号是标识信息,不用于数值计算,应使用 tel,不要使用 number。
数值与日期范围
<input name="age" type="number" min="16" max="80" step="1">
<input name="score" type="number" min="0" max="100" step="0.5">
<input name="start_date" type="date" min="2026-01-01" max="2026-12-31">预览效果如下:
min、max:允许的最小值和最大值。step:数值变化的步长;例如0.5表示可以按半分递增。
这些属性既会影响部分控件的操作方式,也会参与浏览器原生校验。
多文件选择
上一节已经使用 file 和 accept 选择文件。在需要提交多份学习资料时,可以再添加 multiple:
<label for="materials">学习资料</label>
<input
id="materials"
name="materials"
type="file"
accept=".pdf,image/png,image/jpeg"
multiple
>预览效果如下:
multiple:允许一次选择多个文件。accept:提示文件选择器优先显示允许的类型。
accept 不是安全校验,文件的真实类型和大小仍需由后端检查。
浏览器原生校验
浏览器可以根据输入类型和约束属性,在提交前检查必填、格式、长度和范围。复制第一节的 HTML 文档骨架,把下面的表单放入 <body>,保存为 validation-form.html:
<form action="/students" method="post">
<p>
<label for="register-email">邮箱</label>
<input id="register-email" name="email" type="email" required>
</p>
<p>
<label for="register-age">年龄</label>
<input
id="register-age"
name="age"
type="number"
min="16"
max="80"
required
>
</p>
<button type="submit">提交</button>
</form>下方仅预览表单外观,按钮不会提交数据;原生校验请在上面创建的独立 HTML 文件中观察:
用浏览器打开文件,按下面的顺序观察原生校验:
| 操作 | 预期现象 |
|---|---|
| 两项都留空,点击“提交” | 浏览器阻止提交,并提示先填写邮箱 |
邮箱填入 abc,年龄填入 20,点击“提交” | 浏览器提示邮箱格式不正确 |
邮箱改为 student@example.com,年龄填入 15,点击“提交” | 浏览器提示年龄低于允许范围 |
浏览器通常会定位到当前首先发现的无效字段。修正一个字段后再提交,才能继续观察下一个字段的提示;提示的文字和外观会因浏览器而异。
文本长度与格式约束
常见文本长度可以直接声明:
<input name="nickname" type="text" minlength="2" maxlength="20" required>格式简单且稳定时,可以使用 pattern:
<label for="student-code">学员编号</label>
<input
id="student-code"
name="student_code"
type="text"
pattern="S[0-9]{4}"
placeholder="例如:S1001"
title="请输入字母 S 和 4 位数字"
required
>这里要求学员编号由大写字母 S 和 4 位数字组成,例如 S1001。title 为填写规则提供补充说明。
两个文本字段的外观预览如下:
本课程不展开复杂正则表达式。手机号、姓名等真实业务规则经常变化,应由后端执行权威验证并返回清楚的错误信息。
不参与原生校验的情况
<input name="student_id" value="S1001" readonly>
<input name="source" value="import" disabled>预览效果如下:
readonly 控件不能修改,但仍可获得焦点并通常会提交;disabled 控件不能操作,不参与原生校验,也通常不会提交。
表单还可以使用 novalidate 关闭整体原生校验:
<form action="/students" method="post" novalidate>
<!-- 浏览器不执行表单级原生约束检查 -->
</form>基础课程案例保留原生校验,不主动添加 novalidate。这些浏览器规则用于改善填写体验,不能代替后端验证。
语义与校验综合示例
现在把语义结构、准确输入类型和原生校验合并到一张报名页面中:
<header>
<h1>Web 全栈课程报名</h1>
<nav aria-label="报名导航">
<a href="#intro">课程说明</a>
<a href="#register">填写资料</a>
</nav>
</header>
<main>
<section id="intro">
<h2>课程说明</h2>
<p>请使用真实联系方式,标记为“必填”的内容必须填写。</p>
</section>
<section id="register">
<h2>报名资料</h2>
<form action="/students" method="post">
<p>
<label for="name">姓名(必填)</label>
<input id="name" name="name" type="text" minlength="2" maxlength="20" required>
</p>
<p>
<label for="email">邮箱(必填)</label>
<input id="email" name="email" type="email" autocomplete="email" required>
</p>
<p>
<label for="age">年龄</label>
<input id="age" name="age" type="number" min="16" max="80">
</p>
<button type="submit">提交报名</button>
</form>
</section>
</main>
<footer>
<small>报名问题请发送邮件到 support@example.com</small>
</footer>下面是页面外观预览,报名按钮不会发送请求;可运行的语义结构和校验规则以上方代码为准:
仍以第一节的 HTML 文档骨架新建文件,把这段代码放入 <body>,保存为 register-page.html 并用浏览器打开。先检查导航链接能否定位到对应区域,再分别测试空值、错误邮箱和超出范围的年龄。通过校验后,页面可能进入不存在的 /students 地址,这是因为本节没有启动后端服务,不影响对 HTML 和浏览器校验的观察。
基础无障碍检查
语义标签不仅方便阅读,也为屏幕阅读器、键盘操作和辅助技术提供结构信息。完成页面后,可以按下面的顺序检查:
对话框标题关联
弹窗内容需要声明自己的角色,并让辅助技术知道弹窗标题是什么。p01 先学习静态 HTML 关系,打开、关闭和焦点管理会在后面的 DOM 与事件章节处理:
<div role="dialog" aria-labelledby="remove-dialog-title">
<h2 id="remove-dialog-title">确认删除学员</h2>
<p>删除后将无法从当前列表中恢复。</p>
<button type="button">取消</button>
<button type="button">确认删除</button>
</div>静态外观预览如下,按钮尚未连接打开、关闭或删除操作:
aria-labelledby 的值必须对应标题元素的 id。它补充的是对话框的语义关系,不负责实现弹窗显示、隐藏或按钮行为。
表单错误关联
下面模拟用户提交后邮箱格式不正确的状态。错误文字有独立的 id,输入框通过 aria-describedby 指向它,并用 aria-invalid="true" 表明当前值无效:
<main>
<h1>学员登记</h1>
<form>
<fieldset>
<legend>基本信息</legend>
<label for="student-name">姓名</label>
<input id="student-name" name="name" type="text" required>
<label for="student-email">邮箱</label>
<input
id="student-email"
name="email"
type="email"
value="abc"
aria-describedby="email-error"
aria-invalid="true"
required
>
<small id="email-error">邮箱格式不正确,请重新填写。</small>
</fieldset>
</form>
</main>错误状态的外观预览如下:
- 页面只保留一个清楚的
h1,后续标题按层级递进,不用标题标签只为了放大字体。 - 每个表单控件都要有对应的
label;一组相关控件使用fieldset和legend。 - 图片提供准确的
alt;装饰图片使用空alt="",不要把文件名当说明。 - 对话框使用
role="dialog",并通过aria-labelledby关联清楚的标题。 - 错误提示要有自己的
id,再用aria-describedby关联;aria-invalid="true"只在确实无效时设置。值恢复有效后,应移除错误状态或改为有效状态。 - 表格补充
caption、th和scope;导航区域使用nav,主内容使用main。
aria-* 是补充语义,不是用来替代原生 HTML 的第一选择。优先使用准确的原生元素,再在确实需要时添加 ARIA 状态或关系。
小结
- 语义元素用于说明页面区域的用途,不负责视觉布局。
section表示有明确主题的区域,article表示可以独立理解的内容。- 准确的输入类型可以改善键盘、选择器和基础校验体验。
required、长度、范围和简单的pattern可以声明输入约束。readonly与disabled对操作、校验和提交的影响不同。- 浏览器原生校验用于改善填写体验,关键业务规则仍需由后端再次验证。
- 语义结构、标签关联、替代文本和错误关系共同构成基础无障碍检查清单。