:placeholder-shown本身不能实现浮动标签,它仅在input有placeholder属性且内容为空时生效,无法覆盖输入后失焦、粘贴、JS赋值等边界场景,必须结合:focus-within、:not(:placeholder-shown)及JS监听才能完整实现。

:placeholder-shown 本身不能实现浮动标签,它只是个“空状态探测器”——只在 input 有 placeholder 属性且内容为空(不含空格、零宽字符等)时为真。单靠它,标签一输入就上浮、一删光就掉回、失焦后还可能遮住文字。必须组合其他状态才能撑起完整交互。
为什么 :placeholder-shown 单独用会失效
常见错误是只写这两行:
input:placeholder-shown + label { top: 0.75em; }
input:not(:placeholder-shown) + label { top: -0.75em; }
这会导致:
- 用户输入一个空格,
:placeholder-shown立刻失效,标签上浮 → 行为正确 - 但用户全选删除后,标签立刻掉回原位 → 文字被遮挡
- 用户聚焦但没输任何内容,标签不动 → 违反 Material Design 聚焦即上浮原则
-
type="number"输入0时,部分浏览器不触发:placeholder-shown切换,标签卡死
:focus-within + :not(:placeholder-shown) 是更稳的组合
纯 CSS 场景下,:focus-within 比 :focus 更可靠,因为它作用于父容器,能捕获子元素聚焦(比如 input 被点中),且不会因 label 遮挡而中断。配合 :not(:placeholder-shown) 可覆盖「已填」语义:
立即学习“前端免费学习笔记(深入)”;
-
.field:has(input:focus)不推荐::has()兼容性差(Safari 15.4+,Chrome 105+),且无法 fallback - 正确写法是给
.field加position: relative,label 绝对定位,然后用:.field:focus-within label, .field input:not(:placeholder-shown) + label - 必须加
pointer-events: none到 label 上,否则点击 label 会失焦 input -
input:not(:placeholder-shown)比:valid更通用:不依赖required或type校验,也不怕"0"或" "的误判
HTML 结构和属性缺一不可
以下任意一项缺失,整个伪类链都会断掉:
-
input必须带placeholder属性,哪怕写成placeholder=""或placeholder=" "—— 没它,:placeholder-shown永远不匹配 -
label必须紧跟在input后面,且是**下一个兄弟元素**,用+才能选中;写成label > input或中间插span就失效 -
label必须有for属性,值等于input的id,否则可访问性崩塌,且部分屏幕阅读器不触发聚焦联动 -
type="hidden"、disabled、contenteditable="true"元素上,:placeholder-shown不生效
真正难处理的不是「怎么让标签上浮」,而是「什么时候该让它保持上浮」——:placeholder-shown 只回答了“现在空吗”,没回答“刚才填过吗”。粘贴、JS 清空、全角空格、右键删除……这些边界都得靠 JS 补位,CSS 只能管住最干净的那条主线。


















