返回
UI · №13 · SPECIMEN

输入框最佳实践Input Field Best Practices

Input文本输入邮箱输入密码输入数字输入
LIVE SPECIMEN真的能玩,点点看

离开输入框时自动校验

先看结论

输入框是用户在应用中最常用的组件之一。从「类型选择」(text vs email vs password)到「状态反馈」(焦点、错误、禁用),每个细节都影响用户体验和数据质量。这个词条不讲"怎么做"而讲"为什么这样做"。

适合
任何需要用户输入的地方——登陆、注册、搜索、表单、编辑。关键是要根据输入内容的性质选择合适的 type,而不是所有输入都用 `type="text"`。
不适合
不要用输入框让用户在有限选项中选择(用 Select 或 Radio);不要用输入框做可以通过其他方式收集的信息。

直接发给 agent

Create a complete form input field for [type of input]. Requirements: - Input type: [text/email/password/number] (based on data type) - Label: [field name] with required indicator (*) - Placeholder: [example value or hint] - Helper text: [explain what this field is for] - Height: 44px (mobile touch target) - States: normal, focus, filled, error, disabled - Validation: * Timing: [real-time on blur / on submit] * Error message position: below input * Error color: #A02C2C (error from design system) - Accessibility: * <label for="[id]"> correctly associated * aria-invalid="true" + aria-describedby when error * Visible focus ring Output: - HTML structure with semantic markup - CSS for all states (including focus-visible) - Validation logic (show/hide error message) - Mobile responsive considerations

已于 2026-07-20 实测通过

技术标注5 项
输入框高度
移动端 ≥44px(触达区标准);桌面 40px 可接受
焦点态
必须有可见 focus ring(用户按 Tab 时能看到)
标签关联
<label for='email'> 与 <input id='email'> 关联(屏幕阅读器需要)
占位文案
placeholder 不能替代 label;必须同时有 label
验证时机
实时验证(Blur)vs 提交时验证,根据情景选择

详细说明

输入框最佳实践

它是什么

输入框是用户和应用通话的地方。点击它、敲键盘、收到反馈——这个循环中的每一步都能提升或破坏用户体验。

一个看似“简单”的输入框其实包含 10+ 个设计决策:

  • 用什么 type(text vs email vs password)
  • 高度多少(44px 触达区 vs 其他)
  • 焦点时怎么突出
  • 验证错误怎么显示
  • 移动端键盘怎么处理
  • 可访问性标记怎么加

这个词条不是“怎么写 HTML”,而是“为什么这样设计”。

技术标注

考虑点 决策 原因
高度 移动端 ≥44px iOS/Android 标准触达区,防止误触
焦点环 2px Cinnabar outline 用户按 Tab 时能清晰看到焦点位置
标签 <label for="id"> 屏幕阅读器可以读出字段含义;点标签也能激活输入框
验证时机 Blur 触发 用户离开字段时检查,不会在打字时骚扰
错误位置 输入框下方 不遮挡用户的输入内容;目光流自然(输入框→下方错误信息)
对比度 正文 ≥4.5:1 WCAG AA,保证色弱用户能看清

第 1 部分:输入框类型选择(何时用哪个 type)

text(通用文本输入)

<input type="text" placeholder="输入用户名" />

什么时候用

  • 用户名、昵称
  • 搜索框
  • 任何不特定数据类型的输入

移动键盘:默认字母键盘

优点

  • 没有特殊格式要求
  • 用户可以输入任何内容

缺点

  • 需要手动验证内容

email(邮箱输入)

<input type="email" placeholder="user@example.com" />

为什么用 type="email" 而不是 type="text"

  1. 移动键盘优化

    • 浏览器自动弹出 @ 符号
    • 省去用户长按找 @ 的麻烦
    • 桌面浏览器的行为一样,不会有坏影响
  2. 原生浏览器验证

    <input type="email" required />
    • 不需要写正则表达式
    • 浏览器会在提交时自动检查格式
    • 用户体验:提交时如果格式错,浏览器自动阻止 + 显示错误
  3. 语义化

    • 代码的意图清晰(这是邮箱输入,不是普通文本)
    • 屏幕阅读器会说“邮箱输入框”而不是“文本输入框”

什么时候不用

  • 不要用 type="text" 然后自己写邮箱正则
  • 不要以为 type="email" 的验证够完美(不能检查邮箱是否真实存在)

对应案例场景:博客登陆表单中的邮箱字段


password(密码输入)

<input type="password" placeholder="••••••••" />

关键特点

  • 用户敲的内容显示为 ●●●●●(隐藏内容)
  • 拷贝/粘贴功能仍然可用(用户可以从密码管理器粘贴)

为什么要隐藏密码

  • 安全性:防止背后看屏幕的人看到密码
  • 但用户也需要确认“我输入的是什么”

解决方案:显示/隐藏切换

<div class="password-input-wrapper">
  <input id="pwd" type="password" placeholder="••••••••" />
  <button id="toggle-pwd" type="button" aria-label="显示密码">
    <svg><!-- 眼睛 Icon --></svg>
  </button>
</div>

<script>
  const input = document.getElementById('pwd');
  const btn = document.getElementById('toggle-pwd');
  
  btn.addEventListener('click', () => {
    const isPassword = input.type === 'password';
    input.type = isPassword ? 'text' : 'password';
    btn.setAttribute('aria-label', isPassword ? '隐藏密码' : '显示密码');
  });
</script>

这样做的好处

  • 用户可以临时看到密码(验证输入正确)
  • 仍然默认隐藏(安全默认)
  • 按钮明确标注“显示密码”(用户主动选择)

对应案例场景:博客登陆表单中的密码字段


number(数字输入)

<input type="number" min="1" max="100" step="1" />

什么时候用

  • 年龄、数量、价格
  • 任何明确是“数字”的输入

移动键盘:数字键盘(9 个数字 + 0 + 操作键)

原生特性

  • 浏览器内置上/下箭头(+1/-1)
  • 自动检查 min/max 范围
  • 非数字的输入会被拒绝

缺点

  • 某些场景(电话号码、邮编)用 number 会失去前导 0
  • 例如邮编 01234 会变成 1234(丢失信息)

何时不用 number

<!-- ❌ 不要 -->
<input type="number" placeholder="13812345678" />

<!-- ✅ 要 -->
<input type="tel" placeholder="138-1234-5678" />

tel(电话号码)

<input type="tel" placeholder="138-1234-5678" />

为什么用 type="tel" 而不是 type="number"

  • 保留所有字符(包括 - + 空格)
  • 移动端弹电话键盘
  • 不会自动去掉前导 0 或特殊符号

对应案例场景:如果博客有“我的联系方式”编辑,电话字段用 type="tel"


第 2 部分:输入框状态与视觉反馈

Normal 状态(未交互)

┌─────────────────────────┐
│ 邮箱地址                 │ ← label
│ ┌───────────────────────┤
│ │ user@example.com      │ ← 占位符或之前的值
│ └───────────────────────┤
│ 输入你的账户邮箱         │ ← helper text
└─────────────────────────┘

设计决策

  • 边框:Hairline 色 #E5E1D8(浅,暗示可交互但不突出)
  • 背景:白色(#FFFFFF)
  • 文字:Ink 色 #1C1B18
  • 高度:44px(含 padding)
  • 圆角:6px

Focus 状态(用户点击或 Tab 进入)

┌─────────────────────────┐
│ 邮箱地址                 │
│ ┌───────────────────────┤ ← 边框变朱砂
│ │ user@                 │ ← 光标在这里
│ └───────────────────────┤
│ 输入你的账户邮箱         │
└─────────────────────────┘
    (圆形 focus ring)      ← 外层 2px 朱砂边框

决策

  • 边框变色:Hairline → Cinnabar #C13E23
  • 外层 outline:2px Cinnabar(focus ring)
  • 背景保持白色(不需要变)
  • 时间:200ms ease-out 过渡

为什么要 focus ring

  • 键盘用户(按 Tab)需要看到焦点在哪
  • 屏幕阅读器用户也会听到“焦点进入邮箱字段”
  • 对比度要足够(Cinnabar 5.28 on white,达 AA)

Filled 状态(用户已输入)

┌─────────────────────────┐
│ 邮箱地址 *              │ ← required 星号
│ ┌───────────────────────┤
│ │ user@example.com      │ ← 用户的输入
│ └───────────────────────┤
│ ✓ 邮箱格式正确          │ ← 可选:success 反馈
└─────────────────────────┘

决策

  • 如果验证通过,可以显示绿✓ Icon(可选)
  • 如果字段是 required,用 * 标记
  • 边框颜色可以保持 Hairline(已经表达出“有内容”的视觉了)

Error 状态(验证失败)

┌─────────────────────────┐
│ 邮箱地址 *              │
│ ┌───────────────────────┤ ← 边框变错误色
│ │ xiaoming              │ ❌ ← Icon + 错误提示
│ └───────────────────────┤
│ ✗ 邮箱格式不正确,请输入完整邮箱
└─────────────────────────┘

决策

① 边框颜色

border-color: #A02C2C; /* Error 色(DESIGN.md)*/

② 错误位置:输入框下方

  • 不要在输入框右侧挤 Icon(会遮挡内容)
  • 错误信息完整可读很重要

③ 错误文案

  • 清晰说明什么错了:「邮箱格式不正确」而不是「❌ 错误」
  • 告诉用户怎么修复:「请输入完整邮箱(user@example.com)」

④ 时机

情景 1:实时验证(推荐)
用户输入 xiaoming → Blur(离开字段)

系统检查格式

显示错误「邮箱格式不正确」

用户修改 → Blur 再检查 → 错误消失

情景 2:提交时验证
用户输入 xiaoming
用户点「登陆」

系统检查所有字段

显示所有错误

用户修改后重新提交

哪个更好

  • 实时验证:用户体验更好(及时反馈)
  • 提交时验证:用户可能会填一半的表单然后放弃

建议:邮箱字段用实时验证(字段少、可立即反馈)


Disabled 状态(用户无法交互)

┌─────────────────────────┐
│ 邮箱地址                 │
│ ┌───────────────────────┤
│ │ user@example.com      │ ← 文字变灰、光标不可用
│ └───────────────────────┤
│ 此字段已被管理员锁定    │ ← 解释为什么禁用
└─────────────────────────┘

决策

  • 背景:Paper 色(变浅,表达“不可用”)
  • 文字:Ink-2 色(变灰)
  • 光标:cursor: not-allowed
  • Opacity:0.5(如果上述不够明显)

HTML

<input type="email" disabled aria-disabled="true" />

什么时候禁用

  • 加载中(提交表单时输入框临时禁用)
  • 权限不足(管理员限制某些用户编辑)
  • 条件不满足(例如“有其他选择时,此字段不需要”)

第 3 部分:移动端优化

键盘处理

问题:移动端虚拟键盘会遮挡表单下半部分

解决方案

<input type="email" 
       inputmode="email"           <!-- 主动指定键盘类型 -->
       autocomplete="email"        <!-- 让浏览器自动填充 -->
       spellcheck="false"          <!-- 邮箱不需要拼写检查 -->
/>

inputmode 类型

  • email:邮箱键盘(@)
  • tel:电话键盘(0-9 + *)
  • numeric:纯数字
  • decimal:数字 + 小数点
  • url:URL 键盘(/ : . )

autocomplete 值

  • email:浏览器从已保存邮箱自动填充
  • off:禁用自动填充(密码字段通常不用 off,让密码管理器工作)
  • current-password:密码字段用这个(让 1Password、LastPass 识别)

字号不要低于 16px

问题:iOS Safari 会自动放大字号 ≤ 16px 的输入框

/* ❌ 不要 */
input {
  font-size: 14px;  /* iOS 会放大到 16px,然后页面跟着缩放 */
}

/* ✅ 要 */
input {
  font-size: 16px;  /* iOS 不会动放大 */
}

后果(如果字号 < 16px):

  1. 用户点输入框
  2. iOS 自动放大输入框
  3. 页面跟着放大
  4. 用户需要缩小页面才能看到全貌
  5. 体验糟糕

高度确保 ≥ 44px

iOS/Android 触达区标准:44x44px

/* ✅ 要 */
input {
  height: 44px;        /* 最小触达区 */
  padding: 10px 12px;  /* 内间距 */
}

好处

  • 不会误触邻近元素
  • 用户不用放大页面也能点中

第 4 部分:可访问性

Label 关联

<!-- ❌ 不要 -->
<label>邮箱</label>
<input type="email" />

<!-- ✅ 要 -->
<label for="email">邮箱</label>
<input id="email" type="email" />

为什么

  • 屏幕阅读器可以说“邮箱输入框”而不是“输入框”
  • 用户点 label 文字,焦点自动进入输入框(大幅提升移动端体验)

错误关联

<label for="email">邮箱 *</label>
<input 
  id="email" 
  type="email" 
  required
  aria-invalid="false"  <!-- 初始:无错 -->
  aria-describedby="email-error"
/>
<p id="email-error" role="alert"></p>

<script>
  input.addEventListener('blur', () => {
    if (!input.validity.valid) {
      input.setAttribute('aria-invalid', 'true');
      errorMsg.textContent = '邮箱格式不正确';
    } else {
      input.setAttribute('aria-invalid', 'false');
      errorMsg.textContent = '';
    }
  });
</script>

关键属性

  • aria-invalid="true":告诉屏幕阅读器这个字段有错
  • aria-describedby="email-error":错误信息在 id=“email-error” 的元素里
  • role="alert":错误信息变化时立即读出(而不是用户要自己查询)

Focus Ring 可见

input:focus-visible {
  outline: 2px solid #C13E23;  /* Cinnabar */
  outline-offset: 2px;
}

/* 不要移除 outline(会破坏键盘导航) */
input:focus {
  outline: none;  /* ❌ 禁止 */
}

为什么很重要

  • 键盘用户(包括屏幕阅读器用户)需要看到焦点
  • 移除 outline 的网站对残障人士来说是“无法使用”的

常见错误

错误 1:只用 placeholder,没有 label

<!-- ❌ 不要 -->
<input type="email" placeholder="Enter your email" />

问题

  • placeholder 在用户开始输入时会消失
  • 用户会忘记这个字段是干什么的
  • 屏幕阅读器不知道这是什么字段

正确做法

<!-- ✅ 要 -->
<label for="email">邮箱</label>
<input id="email" type="email" placeholder="user@example.com" />

错误 2:密码字段没有显示/隐藏切换

<!-- ❌ 不要 -->
<input type="password" placeholder="••••••••" />

问题

  • 用户无法验证自己输入的是什么
  • 容易输错密码

正确做法:加显示/隐藏按钮(见上面 password 部分)

错误 3:邮箱输入用 type="text" + 正则表达式

<!-- ❌ 不要 -->
<input type="text" placeholder="邮箱地址" />
<script>
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;  // 自己写正则
  // ...验证逻辑
</script>

问题

  • 移动端没有 @ 快捷键
  • 重复造轮子(浏览器已有原生验证)
  • 代码复杂、容易出 bug

正确做法

<!-- ✅ 要 -->
<input type="email" />

错误 4:输入框字号 < 16px

见上面“移动端优化”部分。


相关词条

  • №03「Form 表单结构与校验」 — 多字段表单的整体设计
  • №03「Button 怎么选」 — 输入框旁边通常有提交按钮
  • №04「表单验证提示」 — 错误信息的设计
  • №01「无障碍设计」 — 更多可访问性最佳实践

完整可复制的 Prompt

设计一个登陆表单,包含邮箱和密码两个输入框。

需求:
1. 邮箱字段
   - type: email(移动端弹 @ 键盘)
   - 实时验证(Blur 时检查格式)
   - 错误时显示「邮箱格式不正确」

2. 密码字段
   - type: password(内容隐藏)
   - 显示/隐藏切换按钮(眼睛 Icon)
   - 最小长度 8 字符
   - Helper text:「至少 8 个字符」

3. 设计约束
   - 配色:遵循 TellYourAgent 设计系统
     * Normal border: Hairline #E5E1D8
     * Focus border: Cinnabar #C13E23
     * Error border: Error #A02C2C
   - 高度:44px(移动端触达区)
   - 字号:≥16px(防止 iOS 自动放大)
   - 圆角:6px
   - Focus ring:2px Cinnabar outline

4. 可访问性
   - <label for="id"> 与 <input id="id"> 关联
   - aria-invalid + aria-describedby(错误时)
   - 键盘用户可以 Tab 遍历
   - 屏幕阅读器可理解所有字段

5. 响应式
   - 桌面:输入框自适应宽度,最大 400px
   - 移动:full-width,padding 16px

输出:
- HTML(语义化标记)
- CSS(所有状态、焦点环、过渡动效)
- JavaScript(实时验证、显示/隐藏切换、错误管理)
- 使用说明

为什么这个决策很重要

输入框是用户“告诉”应用“我要什么”的唯一方式。

  • type="email" 而不是 type="text",移动用户少敲 5 次按键
  • 44px 高而不是 32px,老年用户、手指大的用户就能按中
  • 有 focus ring,盲人用键盘也能正常使用
  • 实时验证而不是提交时验证,用户少走一次弯路

一句话:输入框的每个细节都在为“最广泛的用户群体”服务。