Skip to content

form 表单标签

<form> 是 HTML 表单的核心容器元素,用于收集用户输入并将其提交到服务器。几乎所有用户交互数据的收集——从登录、注册到搜索、评论——都离不开 <form> 元素。本节将全面介绍 <form> 元素的基础概念、核心属性以及表单嵌套规则。

前置知识

阅读本节前,建议先了解:无障碍表格设计

基础概念

什么是表单

表单(Form)是 Web 应用中用户与应用程序进行数据交互的核心机制。<form> 元素作为一个容器,将各种表单控件(如输入框、按钮、下拉框等)组织在一起,形成一个完整的数据提交单元。

表单的基本工作流程:

  1. 用户在表单控件中填写或选择数据
  2. 用户点击提交按钮(或通过其他方式触发表单提交)
  3. 浏览器收集所有表单控件的数据
  4. 浏览器按照指定的编码方式和请求方法将数据发送到目标 URL
  5. 服务器处理数据并返回响应
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>最简单的表单</title>
</head>
<body>
  <!-- 一个最基础的表单 -->
  <form action="/submit" method="post">
    <label for="username">用户名:</label>
    <input type="text" id="username" name="username">

    <label for="pwd">密码:</label>
    <input type="password" id="pwd" name="pwd">

    <button type="submit">登录</button>
  </form>
</body>
</html>

form 元素在 DOM 中的角色

<form> 元素在 DOM 中属于 流式内容(Flow Content)可感知内容(Palpable Content),可以出现在 <body> 内部大多数允许流式内容的位置。同时,它也是一个 表单相关元素(Form-associated element) 的上下文,内部包含的表单控件会自动归属于该表单。

规范定义

在 HTML 规范中,<form> 的内容模型允许包含流式内容,但不能包含另一个 <form> 元素(即表单不能嵌套)。

语法

基本语法结构

html
<form
  action="提交地址"
  method="请求方法"
  enctype="编码类型"
  name="表单名称"
  id="表单ID"
  target="目标窗口"
  autocomplete="自动补全"
  novalidate
  rel="链接关系"
>
  <!-- 表单控件 -->
</form>

核心属性一览

属性说明
actionURL 字符串表单提交的目标地址
methodget / post / dialog提交使用的 HTTP 方法
enctype编码类型字符串提交数据的编码方式
name字符串表单的名称,用于 JS 和旧版 DOM 访问
id字符串表单的唯一标识符
target_self / _blank / _parent / _top提交后的响应显示位置
autocompleteon / off是否启用浏览器的自动补全
novalidate布尔属性提交时是否跳过浏览器内置验证
rel空格分隔的链接类型提交链接的关系说明

详细说明

name 属性

name 属性为表单命名,主要用于以下场景:

  1. JavaScript 中通过名称访问表单document.forms['myForm'] 可以直接获取表单元素
  2. 旧版 DOM 接口document.forms 是一个 HTMLCollection,通过 name 建立索引
  3. 表单标识:在包含多个表单的页面中区分不同表单
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>form name 属性</title>
</head>
<body>
  <form name="loginForm" id="loginForm">
    <input type="text" name="username">
    <button type="submit">提交</button>
  </form>

  <form name="searchForm" id="searchForm">
    <input type="text" name="keyword">
    <button type="submit">搜索</button>
  </form>

  <script>
    // 通过 name 访问表单
    const login = document.forms['loginForm'];
    console.log(login.name); // 输出:loginForm

    // 通过 id 访问表单(更推荐)
    const search = document.getElementById('searchForm');
    console.log(search.name); // 输出:searchForm
  </script>
</body>
</html>

建议

在现代开发中,推荐使用 id 而非 name 来标识表单元素。name 主要用于向后兼容和特定场景(如 document.forms 集合)。

id 属性

id 属性为表单提供全局唯一的标识符,主要用途:

  1. CSS 选择器:通过 #myForm 为表单添加样式
  2. JavaScript 获取元素document.getElementById('myForm')
  3. label 的 for 属性关联:虽然 label 通常关联具体控件,但在某些框架中也可能引用表单
  4. 表单控件的 form 属性:控件可以通过 form="formId" 关联到不在其父级中的表单
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>form id 属性</title>
  <style>
    /* 通过 id 选择器设置表单样式 */
    #registerForm {
      max-width: 400px;
      margin: 20px auto;
      padding: 20px;
      border: 1px solid #ddd;
      border-radius: 8px;
    }

    #registerForm label {
      display: block;
      margin-top: 10px;
      font-weight: bold;
    }
  </style>
</head>
<body>
  <form id="registerForm" action="/register" method="post">
    <label for="email">邮箱:</label>
    <input type="email" id="email" name="email" required>

    <label for="password">密码:</label>
    <input type="password" id="password" name="password" required>

    <button type="submit">注册</button>
  </form>

  <!-- 表单控件可以在 form 外部,通过 form 属性关联 -->
  <input type="hidden" name="source" value="landing-page" form="registerForm">

  <script>
    const form = document.getElementById('registerForm');
    console.log(form.id); // 输出:registerForm
  </script>
</body>
</html>

表单控件的 form 属性关联

HTML5 引入了 form 属性,允许表单控件在 <form> 元素外部定义,但通过 form 属性关联到指定表单。这在布局上有更大的灵活性。

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>控件外部关联表单</title>
</head>
<body>
  <!-- 表单容器 -->
  <form id="orderForm" action="/order" method="post">
    <h2>订单信息</h2>
    <label for="product">商品名称:</label>
    <input type="text" id="product" name="product">
  </form>

  <!-- 提交按钮在表单外部,通过 form 属性关联 -->
  <div class="actions" style="margin-top: 10px;">
    <!-- 这个按钮提交 orderForm -->
    <button type="submit" form="orderForm">提交订单</button>
  </div>

  <!-- 这个输入框也属于 orderForm -->
  <input type="hidden" name="token" value="abc123" form="orderForm">
</body>
</html>

注意

form 属性的值必须与目标 <form>id 完全匹配。如果引用的 id 不存在,该控件不会参与任何表单的提交。

表单嵌套规则

HTML 规范明确禁止表单嵌套。一个 <form> 元素内部不能包含另一个 <form> 元素。这是由于表单嵌套会导致数据提交时的歧义——浏览器无法判断内部表单的提交行为应该归属于哪个外层表单。

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>表单嵌套规则</title>
</head>
<body>

  <!-- 正确:两个独立的表单 -->
  <form id="searchForm" action="/search" method="get">
    <label for="keyword">搜索:</label>
    <input type="search" id="keyword" name="q">
    <button type="submit">搜索</button>
  </form>

  <form id="loginForm" action="/login" method="post">
    <label for="user">用户名:</label>
    <input type="text" id="user" name="user">
    <button type="submit">登录</button>
  </form>

  <!-- 错误!表单不能嵌套 -->
  <!--
  <form action="/outer">
    <form action="/inner">
      ...
    </form>
  </form>
  -->
</body>
</html>

如果确实需要在一个页面中实现类似"表单内嵌表单"的效果,可以使用以下替代方案:

方案一:使用多个独立表单

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>多表单方案</title>
</head>
<body>
  <h2>用户中心</h2>

  <!-- 修改个人信息的表单 -->
  <section>
    <h3>修改个人信息</h3>
    <form id="profileForm" action="/profile" method="post">
      <label for="nickname">昵称:</label>
      <input type="text" id="nickname" name="nickname">
      <button type="submit">保存资料</button>
    </form>
  </section>

  <!-- 修改密码的表单 -->
  <section>
    <h3>修改密码</h3>
    <form id="passwordForm" action="/change-password" method="post">
      <label for="oldPwd">旧密码:</label>
      <input type="password" id="oldPwd" name="old_password">

      <label for="newPwd">新密码:</label>
      <input type="password" id="newPwd" name="new_password">

      <button type="submit">修改密码</button>
    </form>
  </section>
</body>
</html>

方案二:使用 form 属性将控件关联到不同表单

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>form 属性替代嵌套</title>
</head>
<body>
  <!-- 两个表单,控件通过 form 属性分别关联 -->
  <form id="mainForm" action="/main" method="post"></form>
  <form id="subForm" action="/sub" method="post"></form>

  <div>
    <h3>主表单控件</h3>
    <input type="text" name="field1" form="mainForm">
    <button type="submit" form="mainForm">提交主表单</button>
  </div>

  <div>
    <h3>子表单控件</h3>
    <input type="text" name="field2" form="subForm">
    <button type="submit" form="subForm">提交子表单</button>
  </div>
</body>
</html>

表单中的事件

<form> 元素支持多种事件,常用于在提交前进行验证或拦截提交行为:

事件触发时机常见用途
submit表单提交时(点击提交按钮或按回车)验证数据、拦截提交、发送 AJAX 请求
reset表单重置时(点击重置按钮)清理状态
formdata构建 FormData 对象时获取表单数据
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>表单事件</title>
</head>
<body>
  <form id="eventForm" action="/submit" method="post">
    <label for="name">姓名:</label>
    <input type="text" id="name" name="name" required>

    <label for="email">邮箱:</label>
    <input type="email" id="email" name="email" required>

    <button type="submit">提交</button>
    <button type="reset">重置</button>
  </form>

  <div id="message"></div>

  <script>
    const form = document.getElementById('eventForm');
    const message = document.getElementById('message');

    // 监听表单提交事件
    form.addEventListener('submit', function(event) {
      // 阻止默认的表单提交行为
      event.preventDefault();

      // 获取表单数据
      const formData = new FormData(form);
      const name = formData.get('name');
      const email = formData.get('email');

      // 验证逻辑
      if (name.length < 2) {
        message.textContent = '姓名至少需要2个字符';
        message.style.color = 'red';
        return;
      }

      message.textContent = '验证通过,准备提交...';
      message.style.color = 'green';

      // 可以在这里使用 fetch 发送 AJAX 请求
      // fetch('/submit', { method: 'POST', body: formData });
    });

    // 监听表单重置事件
    form.addEventListener('reset', function() {
      message.textContent = '表单已重置';
      message.style.color = 'gray';
    });
  </script>
</body>
</html>

表单的 DOM 接口

<form> 元素在 JavaScript 中对应 HTMLFormElement 接口,提供了丰富的属性和方法:

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>表单 DOM 接口</title>
</head>
<body>
  <form id="apiForm" name="myForm" action="/api" method="post">
    <input type="text" name="username" value="zhangsan">
    <input type="email" name="email" value="zhangsan@example.com">
    <button type="submit">提交</button>
  </form>

  <script>
    const form = document.getElementById('apiForm');

    // ---- 只读属性 ----
    console.log(form.elements);       // HTMLFormControlsCollection,所有表单控件
    console.log(form.length);         // 表单控件的数量
    console.log(form.action);         // 提交地址
    console.log(form.method);         // 请求方法
    console.log(form.encoding);       // 等同于 enctype
    console.log(form.enctype);        // 编码类型

    // ---- 可读写属性 ----
    form.action = '/new-api';         // 修改提交地址
    form.method = 'get';              // 修改请求方法
    form.target = '_blank';           // 在新窗口打开

    // ---- 方法 ----
    // 提交表单(不触发 submit 事件中的验证)
    // form.submit();

    // 重置表单(触发 reset 事件)
    // form.reset();

    // 检查表单是否通过验证
    console.log(form.checkValidity()); // true 或 false

    // ---- 访问表单控件 ----
    // 通过 name 或 id 访问
    console.log(form.elements['username'].value);  // 输出:zhangsan
    console.log(form.elements['email'].value);      // 输出:zhangsan@example.com

    // 通过索引访问
    console.log(form.elements[0].value);            // 输出:zhangsan

    // 通过点属性访问(name 属性作为属性名)
    console.log(form.username.value);               // 输出:zhangsan
  </script>
</body>
</html>

实战示例

完整的登录表单

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>登录表单</title>
  <style>
    body {
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
      display: flex;
      justify-content: center;
      align-items: center;
      min-height: 100vh;
      margin: 0;
      background-color: #f5f5f5;
    }

    .login-card {
      background: white;
      padding: 40px;
      border-radius: 12px;
      box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
      width: 360px;
    }

    .login-card h1 {
      font-size: 24px;
      margin-bottom: 24px;
      text-align: center;
    }

    .form-group {
      margin-bottom: 16px;
    }

    .form-group label {
      display: block;
      margin-bottom: 6px;
      font-weight: 500;
      color: #333;
    }

    .form-group input {
      width: 100%;
      padding: 10px 12px;
      border: 1px solid #d0d0d0;
      border-radius: 6px;
      font-size: 14px;
      box-sizing: border-box;
    }

    .form-group input:focus {
      outline: none;
      border-color: #4a90d9;
      box-shadow: 0 0 0 3px rgba(74, 144, 217, 0.15);
    }

    .btn-login {
      width: 100%;
      padding: 12px;
      background-color: #4a90d9;
      color: white;
      border: none;
      border-radius: 6px;
      font-size: 16px;
      cursor: pointer;
    }

    .btn-login:hover {
      background-color: #3a7bc8;
    }
  </style>
</head>
<body>
  <div class="login-card">
    <h1>用户登录</h1>
    <form id="loginForm" action="/api/login" method="post" name="login">
      <div class="form-group">
        <label for="username">用户名</label>
        <input
          type="text"
          id="username"
          name="username"
          placeholder="请输入用户名"
          required
          minlength="3"
          maxlength="20"
          autocomplete="username"
        >
      </div>

      <div class="form-group">
        <label for="password">密码</label>
        <input
          type="password"
          id="password"
          name="password"
          placeholder="请输入密码"
          required
          minlength="6"
          autocomplete="current-password"
        >
      </div>

      <button type="submit" class="btn-login">登录</button>
    </form>
  </div>
</body>
</html>

使用 JavaScript 拦截表单提交

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>AJAX 表单提交</title>
</head>
<body>
  <form id="ajaxForm" action="/api/contact" method="post">
    <label for="name">姓名:</label>
    <input type="text" id="name" name="name" required>

    <label for="message">留言:</label>
    <textarea id="message" name="message" rows="4" required></textarea>

    <button type="submit">发送留言</button>
  </form>

  <div id="result"></div>

  <script>
    const form = document.getElementById('ajaxForm');
    const result = document.getElementById('result');

    form.addEventListener('submit', async function(e) {
      e.preventDefault(); // 阻止默认提交

      // 检查浏览器验证
      if (!form.checkValidity()) {
        form.reportValidity(); // 显示验证提示
        return;
      }

      try {
        // 构建 FormData
        const formData = new FormData(form);

        // 发送 AJAX 请求
        const response = await fetch(form.action, {
          method: form.method,
          body: formData
        });

        const data = await response.json();

        if (response.ok) {
          result.textContent = '留言发送成功!';
          result.style.color = 'green';
          form.reset(); // 清空表单
        } else {
          result.textContent = '发送失败:' + data.message;
          result.style.color = 'red';
        }
      } catch (error) {
        result.textContent = '网络错误,请重试';
        result.style.color = 'red';
      }
    });
  </script>
</body>
</html>

注意事项

  1. name 属性不能省略:表单控件的 name 属性决定了提交时数据的键名。没有 name 的控件不会随表单提交。

  2. id 必须唯一:同一页面中,每个元素的 id 必须唯一,否则会导致 JavaScript 获取错误元素和 label 关联异常。

  3. 表单不能嵌套:浏览器会忽略嵌套的 <form> 标签或产生不可预期的行为。如果需要"表单中的表单",使用 form 属性关联外部表单。

  4. form 属性的兼容性form 属性在所有现代浏览器中都得到支持,但在非常旧的浏览器(如 IE11)中不支持。

  5. submit 事件的默认行为submit 事件触发时,如果不调用 event.preventDefault(),浏览器会执行默认的表单提交(页面跳转)。使用 AJAX 提交时务必阻止默认行为。

  6. form.submit() 不触发验证:通过 JavaScript 调用 form.submit() 方法会直接提交表单,不会触发 submit 事件,也不会执行浏览器内置验证。如果需要验证,应先调用 form.checkValidity()form.reportValidity()

最佳实践

  1. 始终为表单和控件提供 id:便于 CSS 样式和 JavaScript 操作,也便于 <label>for 属性关联。

  2. 为表单控件设置有意义的 namename 会出现在提交的数据中,应该使用符合后端接口约定的命名。

  3. 使用语义化的表单结构:结合 <fieldset><legend> 对表单控件进行分组,提高可读性和无障碍性。

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>语义化表单</title>
</head>
<body>
  <form action="/register" method="post">
    <!-- 用 fieldset 和 legend 对相关控件分组 -->
    <fieldset>
      <legend>基本信息</legend>

      <label for="realname">真实姓名:</label>
      <input type="text" id="realname" name="realname" required>

      <label for="email">电子邮箱:</label>
      <input type="email" id="email" name="email" required>
    </fieldset>

    <fieldset>
      <legend>安全设置</legend>

      <label for="pwd">设置密码:</label>
      <input type="password" id="pwd" name="password" required>

      <label for="pwdConfirm">确认密码:</label>
      <input type="password" id="pwdConfirm" name="password_confirm" required>
    </fieldset>

    <button type="submit">注册</button>
  </form>
</body>
</html>
  1. 使用 form 属性实现灵活布局:当提交按钮需要与表单控件分离时,使用 form 属性而非嵌套表单。

  2. 统一使用 idname 来标识表单:不要混用,保持代码风格一致。现代项目推荐使用 id

下一节

继续学习:action 与 method

参考链接