{
"title": "让 Tailwind 内置颜色支持暗黑模式",
"tags": [
"post"
],
"sources": [
"xlog"
],
"external_urls": [
"https://innei-4525.xlog.app/posts/tailwind-built-in-colors-dark-mode"
],
"date_published": "2023-04-14T13:30:08.997Z",
"content": "最近给 [xLog](https://xlog.app) 增加了黑暗模式的支持,但由于 xLog 在开发时候就没对黑暗模式留个口子,比如颜色值不固定写死,或者是使用 CSS 变量的颜色值。而 xLog 真巧使用了 Tailwind,基本上所有的颜色应用场景都用了 Tailwind 自带的色值,但由于 Tailwind 本身自带的色值都是一个[固定的值](https://tailwindcss.com/docs/customizing-colors),并不支持根据 Dark Mode 切换色值。\n\n于是我萌生了一个想法,让自带的颜色能根据是否是暗黑模式去切换就行了。\n\n首先第一是,要重新配置 Tailwind,覆写原来内置的所有的颜色,把固定的值全部改写成 CSS 变量。变量的前缀可以自定义,不要冲突就行了,这边就暂定为 `tw-colors-i`。比如我们需要类似这样的色值配置:\n\n```js\n// tailwind.config.ts\nmodule.exports = {\n theme: {\n colors: {\n slate: {\n 50: 'rgb(var(--tw-colors-i-slate-50))',\n 100: 'rgb(var(--tw-colors-i-slate-100))',\n // ...\n },\n },\n },\n}\n\n```\n\n但是以上的写法并不支持 Tailwind 的让颜色获得 Opacity 的能力,所以还需要修改一下。\n\n```js\n// tailwind.config.ts\nmodule.exports = {\n theme: {\n colors: {\n slate: {\n 50: 'rgba(var(--tw-colors-i-slate-50), <alpha-value>)',\n 100: 'rgba(var(--tw-colors-i-slate-100), <alpha-value>)',\n // ...\n },\n },\n },\n}\n\n```\n\n加上 ` <alpha-value>` 用于被 Tailwind 替换相应的 Opacity。注意这里我们使用了 `rgba` 所以后续在定义颜色变量的时候要注意,不能使用 Hex 类型的颜色值,而要使用类似这样的格式 `--tw-colors-i-slate-50: 248, 250, 252;`。\n\n接下来就是如何取反色的问题,在 Tailwind 3.3 以上的版本,内置颜色都是从 `50` 开始到 `950` 结束(3.2 版本没有 `950`)。一个亮色对应一个暗色,并且相加等于 `1000`。比如,`slate-50` 对应的暗色就是 `slate-950`, `90+950=1000`。按照这个思路,就可以写一个脚本去生成一个暗色的所有的色值。\n\n代码这里就不贴了,可以去 [Crossbell-Box/xLog]( https://github.com/Crossbell-Box/xLog/blob/27385ee5a6f09b714e2570e501313d41b6bbb1b9/scripts/tw/generate-css-var.js) 这里看,如果需要使用的话只需要修改下,生成的 CSS 路径和 Tailwind 色值配置的路径即可(Tailwind >= 3.3)。\n\n生成的 CSS 颜色变量定义大概长这样(截取部分,完整可以通过 [Crossbell-Box/xLog](https://github.com/Crossbell-Box/xLog/blob/27385ee5a6f09b714e2570e501313d41b6bbb1b9/src/css/css-var.css) 查看):\n\n```css\n:root {\n --tw-colors-i-slate-50: 248, 250, 252;\n --tw-colors-i-slate-100: 241, 245, 249;\n --tw-colors-i-slate-200: 226, 232, 240;\n --tw-colors-i-slate-300: 203, 213, 225;\n --tw-colors-i-slate-400: 148, 163, 184;\n --tw-colors-i-slate-500: 100, 116, 139;\n}\n\nhtml.dark {\n --tw-colors-i-slate-50: 2, 6, 23;\n --tw-colors-i-slate-100: 15, 23, 42;\n --tw-colors-i-slate-200: 30, 41, 59;\n --tw-colors-i-slate-300: 51, 65, 85;\n --tw-colors-i-slate-400: 71, 85, 105;\n --tw-colors-i-slate-500: 100, 116, 139;\n}\n\nhtml.light {\n --tw-colors-i-slate-50: 248, 250, 252;\n --tw-colors-i-slate-100: 241, 245, 249;\n --tw-colors-i-slate-200: 226, 232, 240;\n --tw-colors-i-slate-300: 203, 213, 225;\n --tw-colors-i-slate-400: 148, 163, 184;\n --tw-colors-i-slate-500: 100, 116, 139;\n}\n\n@media (prefers-color-scheme: dark) {\n html:not(.light) {\n --tw-colors-i-slate-50: 2, 6, 23;\n --tw-colors-i-slate-100: 15, 23, 42;\n --tw-colors-i-slate-200: 30, 41, 59;\n --tw-colors-i-slate-300: 51, 65, 85;\n --tw-colors-i-slate-400: 71, 85, 105;\n --tw-colors-i-slate-500: 100, 116, 139;\n }\n}\n\n@media (prefers-color-scheme: light) {\n html:not(.dark) {\n --tw-colors-i-slate-50: 248, 250, 252;\n --tw-colors-i-slate-100: 241, 245, 249;\n --tw-colors-i-slate-200: 226, 232, 240;\n --tw-colors-i-slate-300: 203, 213, 225;\n --tw-colors-i-slate-400: 148, 163, 184;\n --tw-colors-i-slate-500: 100, 116, 139;\n }\n}\n\n```\n\n这个 CSS 中定义了在系统在暗色还是亮色以及根据 `html.light` `html.dark` 确定亮暗色。\n\n同时生成 Tailwind 颜色配置:\n\n```js\n// tw-colors.js\nexport default {\n slate: {\n 50: \"rgba(var(--tw-colors-i-slate-50), <alpha-value>)\",\n 100: \"rgba(var(--tw-colors-i-slate-100), <alpha-value>)\",\n 200: \"rgba(var(--tw-colors-i-slate-200), <alpha-value>)\",\n 300: \"rgba(var(--tw-colors-i-slate-300), <alpha-value>)\",\n 400: \"rgba(var(--tw-colors-i-slate-400), <alpha-value>)\",\n 500: \"rgba(var(--tw-colors-i-slate-500), <alpha-value>)\",\n 600: \"rgba(var(--tw-colors-i-slate-600), <alpha-value>)\",\n 700: \"rgba(var(--tw-colors-i-slate-700), <alpha-value>)\",\n 800: \"rgba(var(--tw-colors-i-slate-800), <alpha-value>)\",\n 900: \"rgba(var(--tw-colors-i-slate-900), <alpha-value>)\",\n 950: \"rgba(var(--tw-colors-i-slate-950), <alpha-value>)\",\n },\n // ...\n inherit: \"inherit\",\n current: \"currentColor\",\n transparent: \"transparent\",\n black: \"rgba(var(--tw-colors-i-black), <alpha-value>)\",\n white: \"rgba(var(--tw-colors-i-white), <alpha-value>)\",\n}\n\n```\n\n在项目中,引入生成的 CSS,并在 Tailwind 配置覆写颜色。\n\n```js\n// tailwind.config.js\nconst twColors = require(\"./tw-colors\")\nconst alwaysColor = require(\"tailwindcss/colors\")\n\n/** @type {import('tailwindcss').Config} */\nmodule.exports = {\n content: [\"./src/**/*.tsx\"],\n darkMode: ['class', 'html.dark'],\n theme: {\n colors: twColors,\n extend: {\n \tcolors: {\n \t always: alwaysColor\n \t\t}\n \t}\n\t}\n}\n```\n\n这里可选配置定义下 `always`,always 的颜色仍是自带的固定颜色值。如有需要可以取用。\n\n至于 `darkMode` 是否需要定义,现在变成了可选项,如果需要用 Tailwind 提供的 `dark-mode: ` 那是需要开启的。不过既然都是动态色值了,开不开就按使用需求了。\n\n---\n\n说说缺点:\n\n- 引入了大量 CSS 变量,并且无用变量无法在编译时消除。\n\n\n\n更好的解决方式?\n\n给 Tailwind 提 PR。支持颜色多重定义,如下所示:\n\n```js\n/** @type {import('tailwindcss').Config} */\nmodule.exports = {\n content: ['./src/**/*.tsx'],\n theme: {\n colors: {\n blue: {\n 50: {\n light: '#123',\n dark: '#321',\n },\n },\n },\n },\n}\n```\n\n希望有 Tailwind 的小伙伴可以看到这个提案。\n\n最后安利一下 [xLog](https://xlog.app),这么好用的博客平台,还不赶紧试试?\n\n对了,我自创的 Mix Space 现也支持同步到 xLog 了,赶紧来试试吧。\n\n<https://twitter.com/__oQuery/status/1643604671133540355>\n\n<span style=\"text-align: right;font-size: 0.8em; float: right\">此文由 [Mix Space](https://github.com/mx-space) 同步更新至 xLog\n原始链接为 <https://innei.ren/posts/programming/tailwind-built-in-colors-dark-mode></span>",
"attributes": [
{
"value": "tailwind-built-in-colors-dark-mode",
"trait_type": "xlog_slug"
}
]
}