
本文介绍通过 CSS :focus-within 伪类与 React 聚焦管理相结合的方式,解决密码输入框聚焦时关联按钮因失焦而消失的问题,确保用户可无缝点击按钮切换密码可见性。
本文介绍通过 css `:focus-within` 伪类与 react 聚焦管理相结合的方式,解决密码输入框聚焦时关联按钮因失焦而消失的问题,确保用户可无缝点击按钮切换密码可见性。
在构建带“显示/隐藏密码”功能的表单时,一个常见痛点是:当用户聚焦 <input type="password"> 后,按钮(如眼睛图标)动态显示;但一旦鼠标移向按钮准备点击,输入框立即触发 onBlur,按钮随之隐藏,导致点击失败——这本质上是焦点在输入框与按钮间快速切换引发的竞态问题。
✅ 推荐方案:使用 :focus-within + 受控聚焦管理
CSS 的 :focus-within 伪类是专为此类场景设计的:只要其任意子元素获得焦点,父容器即匹配该状态。这意味着将输入框和按钮共同包裹在一个容器内,并为该容器设置 :focus-within 样式,即可让按钮在输入框或按钮自身获得焦点时始终可见:
.input-wrapper button {
display: none;
/* 建议添加定位与尺寸控制 */
position: absolute;
right: 8px;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
cursor: pointer;
}
.input-wrapper:focus-within button {
display: inline-block;
}同时,在 React 中需避免手动依赖 onFocus/onBlur 控制显隐状态(易受事件时机干扰),转而交由 CSS 驱动,并辅以精准的焦点保持逻辑:
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 toggleVisibility = useCallback(() => {
setShowPassword(prev => !prev);
}, []);
// 确保切换后焦点仍停留在 input 上,且光标位置不丢失
useLayoutEffect(() => {
if (wrapperRef.current && inputRef.current) {
const activeEl = document.activeElement;
if (wrapperRef.current.contains(activeEl)) {
inputRef.current.focus();
// 可选:恢复光标位置(若需更精细控制)
// const { selectionStart, selectionEnd } = inputRef.current;
// setTimeout(() => {
// inputRef.current?.setSelectionRange(selectionStart, selectionEnd);
// }, 0);
}
}
}, [showPassword]);
return (
<div className="input-wrapper" ref={wrapperRef}>
<input
ref={inputRef}
type={showPassword ? 'text' : 'password'}
placeholder="Enter password"
defaultValue="mySecret123"
/>
<button
type="button"
onClick={toggleVisibility}
aria-label={showPassword ? "Hide password" : "Show password"}
>
{showPassword ? '?️?️ Hide' : '?️ Show'}
</button>
</div>
);
};
export default PasswordInput;⚠️ 注意事项与兼容性建议
-
浏览器支持::focus-within 全局支持率约 96%(截至 2024),主流现代浏览器均支持。如需兼容旧版 Safari(<15.4)或 IE,应引入 focus-within-polyfill,并在入口文件中注册:
import 'focus-within-polyfill';
- 无障碍友好:为按钮添加 aria-label,并确保其可通过键盘 Tab 导航(默认 button 支持),提升可访问性。
- 样式健壮性:避免使用 .input-wrapper:not(:focus-within) button 这类写法——:not() 对复杂选择器的支持度更低(全球约 4% 缺失),直接正向声明更可靠。
- 性能考量:useLayoutEffect 在渲染后同步执行,确保焦点恢复发生在浏览器绘制前,避免视觉闪烁;若仅需基础功能,也可用 useEffect,但可能偶现短暂失焦。
通过 :focus-within 解耦 DOM 状态与 JS 逻辑,配合轻量级 React 聚焦管理,既能彻底规避 onBlur 消失问题,又保持代码简洁、可维护性强,是现代表单交互的最佳实践之一。

















