纯 css 主题切换必须用 配合 :checked 和 ~ 选择器实现全局样式控制,因 :checked 仅适用于原生表单控件;需将 checkbox 置于 body 开头并作用于后续兄弟元素,配合 css 变量与继承生效,但无法持久化状态或响应系统偏好。

纯 CSS 实现主题切换完全可行,但必须用 :checked 配合表单控件(如 <input type="checkbox">)+ 兄弟/后续兄弟选择器(+ 或 ~),不能靠 JS 触发。它适合静态页面或对 JS 加载有顾虑的场景,但有明确限制:状态无法持久化、无法响应系统偏好、不支持多级嵌套主题。
为什么必须用 <input type="checkbox"> 而不是 <button></button>
:checked 只作用于 <input type="checkbox"> 或 <input type="radio">,对 <button></button> 或普通 <div> 完全无效。你不能“伪造”一个可被 <code>:checked 捕获的状态——浏览器只认原生表单控件的 checked 属性。
- 用
<label></label>包裹<input>,点击 label 即触发勾选,视觉上可隐藏 input - 不要写
<input id="theme" checked>后又用 JS 改它——这会让 CSS 与 JS 状态脱节,:checked规则可能失效 - 若需默认深色,直接加
checked属性;若需默认浅色,就别加
+ 和 ~ 的选择直接影响样式生效范围
+ 只匹配紧邻的下一个兄弟元素,~ 匹配之后所有符合条件的兄弟元素。主题切换通常要影响整个 下的内容,所以必须把 <input> 放在 DOM 顶层(如 开头),再用 ~ 控制主体:
<input type="checkbox" id="theme-toggle"><header>...</header><main>...</main><footer>...</footer>
对应 CSS:
#theme-toggle:checked ~ header,
#theme-toggle:checked ~ main,
#theme-toggle:checked ~ footer {
--bg-color: #121212;
--text-color: #e0e0e0;
}
- 如果把
<input>塞进某个<div> 里,<code>~就只能影响那个<div> 内部的兄弟,切不了全局<li> <code>+在这里基本没用——你不可能让<input>后面只跟一个元素就覆盖整页样式
如何让颜色变量生效?必须搭配 :root 和后代继承
仅靠 #theme-toggle:checked ~ * 改 style 是无效的;CSS 中不能用选择器直接设置自定义属性到 :root。正确做法是:把 <input> 放在 或 下,然后用选择器控制 body 或其子元素的 style 属性(不行),或者——更可靠地——用选择器直接作用于需要变色的元素,并在其中使用 var():
body {
background-color: var(--bg-color, #fff);
color: var(--text-color, #333);
}
<h1>theme-toggle:checked ~ body {</h1><p>--bg-color: #121212;
--text-color: #e0e0e0;
}</p>
⚠️ 注意:#theme-toggle:checked ~ body 这种写法**不合法**——<input> 和 不是兄弟关系。实际应写成:
/* 把 input 放在 body 开头 */ <input type="checkbox" id="theme-toggle"><div class="app-root">...</div>
.app-root {
background-color: var(--bg-color, #fff);
color: var(--text-color, #333);
}
<h1>theme-toggle:checked ~ .app-root {</h1><p>--bg-color: #121212;
--text-color: #e0e0e0;
}</p>
- CSS 变量必须在被使用的元素自身或其祖先上声明才有效;
~选择器不能跨层级注入:root变量 - 过渡动画要手动加在用到变量的元素上,例如
transition: background-color 0.3s, color 0.3s
这个方案最常被忽略的硬伤:无法保存用户选择
:checked 纯 CSS 方案每次刷新页面都会重置为初始状态(即 HTML 中写的 checked 属性是否存在)。没有 JS,你就没法读写 localStorage 或 prefers-color-scheme。这意味着:
- 用户昨天选了深色,今天打开还是浅色
- 无法自动跟随系统设置(
@media (prefers-color-scheme: dark)与:checked无关) - 如果页面有 iframe 或动态加载内容,
~选择器不会自动作用于新插入的节点
真要上线用,至少得补一段极简 JS 来初始化 checkbox 状态并监听变化——否则体验断层非常明显。











