CSS 已足以处理状态切换、显隐和简单动画,关键在于正确使用选择器与结构::checked + label 实现无 JS 勾选交互,:has() 解决子变父变问题,[data-state] 替代 class 控制状态,提升可维护性与可访问性。

能不用 JS 就别用——只要交互只涉及状态切换、显隐、简单动画,CSS 本身已足够。关键不是“能不能”,而是选对选择器、写对结构、避开浏览器不响应的坑。
:checked + label 是最稳的勾选类交互
用户点一下就切换样式,不需要监听 click、不操作 class、不写 toggle。前提是结构得对:
-
<label>必须包裹<input type="checkbox">和文字/图标,点击任意部分都触发状态变更 -
<input>不能用display: none隐藏,要用position: absolute; opacity: 0; pointer-events: none;保可访问性 - 想控制后续元素,必须用
+(相邻兄弟)或~(后续兄弟),比如input:checked + .icon或input:checked ~ .content -
setAttribute('checked', 'checked')无效——它只影响初始渲染,不更新 DOM property,:checked不会响应
:has() 能解决“子变父变”的老难题
过去必须 JS 监听子元素状态再给父元素加 class,现在一行 CSS 就行:
-
.form-item:has(input:invalid)—— 输入框校验失败,父容器自动变红边框 -
.card:has(.badge--new)—— 卡片里有新标,就加个角标动画 - 注意兼容性:Chrome 105+、Firefox 103+、Safari 15.4+ 支持,IE 完全不支持
-
:has()无法响应 JS 触发的状态变更(比如el.classList.add()后不会重算),只认真实 DOM 状态
用 data- 属性代替 class 控制状态样式
把 JS 和 CSS 的耦合点从 class 名转移到 data 属性,逻辑更清晰、调试更容易:
立即学习“前端免费学习笔记(深入)”;
- HTML 写
<button data-state="loading">,CSS 写[data-state="loading"] { opacity: 0.6; } - JS 只负责更新
el.dataset.state = "success",不碰 class 列表,避免 class 堆砌和顺序冲突 - IE11 支持基础属性选择器
[data-state],但不支持[data-state^="lo"]这类,若需兼容就别用前缀匹配 - 避免泛滥:
data-id、data-index这类无样式意义的属性别当样式钩子用
真正难的不是写对一行选择器,而是让团队所有人接受:状态变更不等于“必须加 class”。:checked、:has()、[data-state] 这些不是炫技工具,是把交互契约从 JS 搬进 HTML 和 CSS 的交接点——那里才该是状态的唯一信源。


















