必须显式关联label与input,推荐嵌套写法;密码框切换显示应双input同步value并用绝对定位隐藏;提交按钮禁用需js控制并失败后恢复;移动端需用dvh单位和scrollintoview避免键盘遮挡。

label 和 input 必须显式关联,否则屏幕阅读器和点击体验都出问题
很多人用 <div> 包着 <code><input> 就完事,但这样 label 点击无法聚焦输入框,辅助技术也识别不了语义。必须用 for + id 配对,或者把 <input> 直接嵌在 <label></label> 里。
推荐嵌套写法,简洁且无歧义:
<label> 用户名 <input type="text" name="username"></label>
如果 label 和 input 分开(比如要加图标或做复杂布局),就严格配对:
<label for="username">用户名</label><input id="username" type="text" name="username">
密码输入框要配“显示/隐藏”按钮,但不能只靠 CSS 切换 type
直接用 JavaScript 改 input.type 从 password 切到 text 是可行的,但有坑:iOS Safari 在 type 切换后会清空输入内容,且光标可能丢失。更稳的做法是用两个 input 共享 value,用 CSS 控制显隐:
- 一个
<input type="password">,一个<input type="text"> - 初始时 password 显示、text 隐藏;切换时互换 visibility 和 focus 状态
- 监听
input事件同步 value,避免双输入框不同步
注意:不要用 display: none 隐藏备用 input,它会导致 iOS 键盘收起;改用 position: absolute; left: -9999px 或 opacity: 0; width: 0; height: 0 更安全。
提交按钮禁用状态必须靠 JS 控制,不能只靠 HTML disabled
用户点一次登录后,按钮立刻 disabled 是基本操作,但仅设 disabled 属性不够:网络请求失败后,按钮得恢复可用,否则用户没法重试。
- 初始状态:
<button type="submit">登录</button>(不带 disabled) - 表单提交时,用 JS 设置
button.disabled = true,并可加 loading 文案:button.textContent = '登录中…' - 请求完成(无论成功或失败),立刻恢复:
button.disabled = false,并还原文案
漏掉失败后的恢复,是登录页最常被 QA 打回的问题之一。
移动端键盘弹起时,输入框被遮挡,CSS 要配合 viewport 和 scroll 行为
iOS 和 Android 对 <input> 获取焦点后的滚动逻辑不一致。单纯用 scrollIntoView() 容易触发两次滚动或失效。
- 给表单外层加
id="login-form",在focus事件里调用document.getElementById('login-form').scrollIntoView({ behavior: 'smooth', block: 'center' }) - 避免在
body上设height: 100vh—— iOS 键盘弹出会压缩 viewport 高度,导致页面错位 - 用
min-height: 100dvh替代100vh(dvh 是动态视口单位,兼容现代浏览器)
这个细节在真机测试前很难暴露,但上线后用户第一反应就是“输不了”,必须实机验证。











