
本文介绍使用 CSS :focus-within 伪类解决“点击密码显示按钮导致输入框失焦、按钮消失”的典型交互问题,并提供兼容性处理与 React 实战代码。
本文介绍使用 css `:focus-within` 伪类解决“点击密码显示按钮导致输入框失焦、按钮消失”的典型交互问题,并提供兼容性处理与 react 实战代码。
在构建带“显示/隐藏密码”功能的表单时,一个常见痛点是:当用户聚焦密码输入框后,右侧按钮(如眼睛图标)显示;但一旦鼠标移向该按钮准备点击,输入框立即 blur,按钮随之隐藏——造成操作中断、体验割裂。
根本原因在于传统 onFocus/onBlur 控制逻辑将按钮置于输入框之外,其点击事件会触发输入框失去焦点,而 onBlur 又无差别地隐藏按钮,形成竞态。
✅ 推荐方案:CSS :focus-within
:focus-within 是现代 CSS 提供的语义化解决方案:当元素自身或其任意后代获得焦点时,该选择器即匹配。这意味着只要 <input> 或 <button> 在 .input-wrapper 内处于聚焦状态,按钮就能持续显示。
.input-wrapper button {
display: none;
}
.input-wrapper:focus-within button {
display: inline-block;
}该方案无需 JS 监听焦点事件,避免了 onBlur 的误触发,天然支持键盘导航(如 Tab 切换到按钮),且样式与逻辑解耦,更轻量、更健壮。
?️ React 完整实现(含焦点保持)
为确保按钮点击后输入框不丢失焦点(尤其在需要保留光标位置的场景),可配合 React Ref 与 useLayoutEffect 主动恢复焦点:
import { useState, useRef, useLayoutEffect, useCallback } from 'react';
const PasswordInput = () => {
const [showPassword, setShowPassword] = useState(false);
const inputRef = useRef<HTMLInputElement>(null);
const wrapperRef = useRef<HTMLDivElement>(null);
const togglePassword = useCallback(() => {
setShowPassword(prev => !prev);
}, []);
// 确保焦点始终落在 input 上(即使点击按钮后)
useLayoutEffect(() => {
if (wrapperRef.current && inputRef.current) {
const activeEl = document.activeElement;
if (wrapperRef.current.contains(activeEl) && activeEl !== inputRef.current) {
inputRef.current.focus();
// 可选:恢复光标位置(若需精确控制)
// inputRef.current.setSelectionRange(
// inputRef.current.selectionStart,
// inputRef.current.selectionEnd
// );
}
}
}, [showPassword]);
return (
<div className="input-wrapper" ref={wrapperRef}>
<input
ref={inputRef}
type={showPassword ? 'text' : 'password'}
placeholder="Enter password"
defaultValue="secret123"
/>
<button
type="button"
onClick={togglePassword}
aria-label={showPassword ? "Hide password" : "Show password"}
>
{showPassword ? '? Hide' : '?️ Show'}
</button>
</div>
);
};
export default PasswordInput;⚠️ 兼容性与注意事项
-
浏览器支持::focus-within 全球支持率约 96%(caniuse 数据)。对于 IE 或老旧 Android 浏览器,建议引入 focus-within-polyfill:
npm install focus-within-polyfill
并在入口文件中初始化:
import 'focus-within-polyfill';
无障碍访问:为按钮添加 aria-label,明确传达功能;确保 type="button" 防止意外表单提交。
不要用 :not(:focus-within):部分方案尝试用 .input-wrapper:not(:focus-within) button { display: none },但 :not() 的兼容性更低(约 4% 缺失),应优先使用正向匹配。
移动端适配:iOS Safari 对 :focus-within 支持良好,但需注意 input 默认聚焦行为可能受 user-scalable=no 等 meta 标签影响,建议测试真机表现。
通过 :focus-within + 轻量 JS 辅助,你既能获得声明式、可维护的样式逻辑,又能保障严谨的交互体验——这才是现代前端密码控件的最佳实践。

















