CSS 教程 / CSS 表单状态:焦点、验证、disabled 与 readonly

CSS 可以为焦点、有效性以及禁用或只读的表单控件设置样式,但 HTML 决定它们的标签、约束和提交行为。:invalid 可以在交互前匹配,而 :user-invalid 则帮助延迟错误样式,直到浏览器认为适合为止。

将标签和说明与控件关联

如果 label 的 for 属性与输入控件的 id 完全相同,则项目名称和输入窗口会连接。一旦开始输入,占位符就会消失,因此它们不会取代名称或重要条件。对于额外说明,请保持句子可见,并在 aria-describedby 中指定该句子的 ID。

<label for="email">Email address (required)</label>
<input id="email" name="email" type="email" required
  placeholder="name@example.com" aria-describedby="email-help">
<p id="email-help">Enter name@example.com in the same format.</p>

上述部分代码简要表示了下面完整示例中的电子邮件部分。id 用于标识文档中的对象,而 name 是提交数据中的项目名称。如果没有 name,即使可以在屏幕上输入,也不会包含在正常提交中。有关标签和描述的组成,请参考 W3C 表单标签指南 和 表单输入指南。

针对窄屏和较大字体的尺寸控制

如果仅依赖输入窗口的固有宽度,当字体增大时可能会超出窄屏幕。请设置表单的最大宽度,并在输入窗口中同时使用width: 100%和box-sizing: border-box。这将确保边框和内边距也包含在指定宽度内。

*, *::before, *::after { box-sizing: border-box; }
form { max-width: 28rem; }
input { width: 100%; min-width: 0; padding: .65rem; font: inherit; }

此规则包含在整个示例的样式部分。font: inherit 允许输入窗口和按钮遵循文本的字体、大小和行高,min-width: 0 则可移除阻止收缩的自动最小宽度,使控件放在 Flexbox 或 Grid 中时也能适应可用空间。不使用固定高度,以便即使说明增加或文字放大,下方的项目也能自然向下移动。

分离焦点状态和验证错误

:focus 匹配当前具有焦点的元素,而 :focus-visible 匹配当浏览器判断需要显示焦点时的情况。它主要在键盘导航期间显示,但也可以通过在文本输入窗口中用鼠标单击来显示,因此不应理解为“仅适用于键盘”。MDN::focus-visible 的匹配规则 。要将焦点指示一致地应用于链接,也可以参考 CSS 链接状态和可见键盘焦点 。

焦点表示您当前正在操作的项目,错误表示未满足输入条件的项目。由于两种状态可以同时存在,因此整个示例显示带有蓝色轮廓的焦点和带有深红色边框及错误提示的错误。在轮廓周围留出一些空间以避免截断,并且不要移除轮廓而不提供其他焦点指示器。

:invalid 如果当前值无效则匹配,因此从一开始空的必填输入字段也会被匹配。:user-invalid 在浏览器将显示用户交互后的错误状态时匹配。何时显示内容,例如在你完成输入或尝试提交时,可能取决于你的浏览器行为。实验通过按提交按钮检查错误。MDN::user-invalid 。

如果需要支持旧版浏览器,必须检查选择器是否受支持,并设计一个单独的验证流程。即使由于选择器不受支持而没有出现错误装饰,也不意味着 HTML 的必填项和格式检查会消失。选择一个足够区别于实际背景的焦点颜色,并检查 W3C 非文本对比指南 的适用条件。

比较必填项、有效项、禁用项和只读项

情况 含义 焦点和一般提交
required 用户必须输入一个值 如果是活动项则可聚焦;空值在提交时会进行验证
:valid·:invalid HTML 验证条件是否达成 验证结果独立于边框形状
disabled 当前不可用 通常不包含在 Tab 导航和提交中
readonly 对于支持该属性的控件,用户不能修改其值 可聚焦且可选择;若设置 name,值仍会进入普通提交数据

readonly 由特定的类似文本的控件支持,不能作为让 select 元素只读的通用方法。受支持的只读控件不会进行约束验证,而具名控件仍然可以提交表单数据。应将这种行为与禁用控件区分开。参见 HTML 标准:构建条目列表, MDN:readonly。

此电子邮件字段展示了 name@example.com 这样的格式。通过其有效性检查并不能确认账户存在或可以接收邮件。HTML type="email" 验证并不接受所有国际化的地址格式;非 ASCII 本地部分在此并未作为保证有效的输入呈现。有关详细信息,请参见 HTML 标准电子邮件输入条件。

运行示例并检查提交的字段

将以下代码保存为 form-states.html(UTF-8 编码)并在浏览器中打开。HTML 和 CSS 决定表单的状态。结尾的简短 JavaScript 代码是辅助代码,用于拦截有效提交并在屏幕上显示数据列表,而不发送到服务器或执行任何额外的输入检查。如果关闭 JavaScript,则结果输出功能将无法工作。

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Comparing form states and submitted fields</title>
  <style>
*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; padding: 1rem; color: #183044; font-family: sans-serif; line-height: 1.6; }
main { max-width: 46rem; margin-inline: auto; }
h1 { font-size: 1.5rem; } h2 { font-size: 1.2rem; }
button, input { font: inherit; }
button { cursor: pointer; }
:focus-visible { outline: 3px solid #164f73; outline-offset: 3px; }
form { max-width: 28rem; }
.field { margin-block: 1.25rem; }
label { display: block; margin-bottom: .35rem; font-weight: 700; }
input { display: block; width: 100%; min-width: 0; padding: .65rem;
  border: 2px solid #6d7e8b; border-radius: .3rem; background: white; color: #183044; }
input:required { border-inline-start-width: 4px; }
input:user-invalid { border-color: #9d2632; }
.help, .error { margin-block: .4rem 0; }
.error { display: none; color: #9d2632; }
input:user-invalid + .error { display: block; }
input[readonly] { background: #eef6fa; }
input:disabled { background: #eceff2; color: #465766; cursor: not-allowed; }
button { padding: .65rem 1rem; border: 1px solid #164f73; border-radius: .3rem;
  background: #166b92; color: white; }
output { display: block; margin-block: 1rem; white-space: pre-wrap; overflow-wrap: anywhere; }
  </style>
</head>
<body>
<main>
<h1>Check input states and submitted data</h1>
<form id="demo-form">
  <div class="field">
    <label for="email">Email address (required)</label>
    <input id="email" name="email" type="email" autocomplete="email" required
      placeholder="name@example.com" aria-describedby="email-help email-error">
    <p id="email-error" class="error">Enter an email address in a format such as name@example.com; do not leave the field empty.</p>
    <p id="email-help" class="help">This sample input checks the address format. It does not check whether the example address receives mail.</p>
  </div>
  <div class="field">
    <label for="plan">Plan (read-only)</label>
    <input id="plan" name="plan" type="text" value="Basic" readonly>
  </div>
  <div class="field">
    <label for="coupon">Coupon (currently disabled)</label>
    <input id="coupon" name="coupon" type="text" value="WELCOME" disabled>
  </div>
  <button type="submit">Check submitted fields</button>
</form>
<output id="result" for="email plan coupon" aria-live="polite"></output>
<script>
  const form = document.getElementById("demo-form");
  form.addEventListener("submit", (event) => {
    event.preventDefault();
    const entries = Array.from(new FormData(form).entries());
    document.getElementById("result").textContent = JSON.stringify(entries, null, 2);
  });
</script>
</main>
</body>
</html>
  1. 将电子邮件留空并点击 Check submitted fields 按钮。检查浏览器中的必填输入说明和错误信息。
  2. 在电子邮件中输入 abc 并重新提交。由于格式错误,不应输出新的结果列表。
  3. 输入 name@example.com 并提交。结果应包含 email 和 plan 两项,plan 的值为 Basic;不含处于 disabled 状态的 coupon 字段。
  4. 使用 Tab 键依次移动到 Email 输入框、Plan 只读框和 Check submitted fields 按钮。非活动优惠券在正常的 Tab 顺序中会被跳过,只读方案可以获得焦点。
  5. 在整个示例中,只移除 coupon 输入框的 disabled 属性。您可以将焦点放在优惠券上,并为您的提交列表添加值。确认后,恢复禁用状态。
  6. 仅移除方案的 readonly 属性并尝试编辑。比较后,将其值恢复为“Basic”,并再次添加 readonly。
  7. 减少屏幕宽度并调整浏览器放大或字体大小设置。检查输入窗口、焦点轮廓和错误句子是否未被截断或重叠。

了解 CSS 和浏览器验证的限制

CSS 负责显示项目的状态。浏览器验证可以减少用户输入错误,但可以通过开发者工具或独立请求绕过,因此实际服务必须在服务器端重新检查允许的值、长度、权限等。错误句子会具体描述有问题的项目和解决方案,并且在服务器错误发生时,还应设计保留已填写信息的处理流程。

如果示例行为异常,首先检查标签的 for/id 配对、提交名称、必填项、输入类型,以及禁用或只读状态。如果焦点不可见,检查是否有其他轮廓规则覆盖它。立即显示的错误可能来自 :invalid。如果在提交尝试后信息仍然隐藏,请检查 :user-invalid 支持以及 CSS 组合器和伪类 。