/** @type {import('tailwindcss').Config} */ /** * 颜色走 CSS 变量而不是写死的十六进制。 * * # 为什么不逐处加 dark: 前缀 * * 全站约 700 处颜色用法散在 21 个组件里。逐个写 `bg-white dark:bg-gray-900` * 有两个致命问题:漏一处就是深色下的白底白字(而且只有肉眼能发现), * 以及此后每加一个组件都要记得写两遍 —— 那种约定活不过三次改动。 * * # 为什么改调色板就够了 * * 这套代码里灰阶**本身就是语义色阶**: * - `white` / `gray-50` / `gray-100` = 表面层次(卡片 / 页面底 / 悬停) * - `gray-200` / `gray-300` = 分隔线 * - `gray-900` → `gray-400` = 文字主次 * * 深色模式要做的正是把这条色阶**反转**:white 变近黑、gray-900 变近白。 * 于是零组件改动就能整体切换,新组件照常写 `bg-white text-gray-900` * 也自动适配 —— 不需要任何人记得任何约定。 * * # 为什么是 `rgb(var(--x) / )` 而不是直接存颜色串 * * 代码里有 `bg-blue-50/70`、`bg-gray-50/60` 这样的透明度修饰符。 * 变量若存 `#f9fafb`,Tailwind 生成的 `rgb(#f9fafb / 0.7)` 是无效 CSS, * 那些半透明高亮会静默失效(不报错,只是不透明)。存 RGB 三元组才行。 */ const withAlpha = (v) => `rgb(var(${v}) / )`; const grayScale = { 50: withAlpha('--c-gray-50'), 100: withAlpha('--c-gray-100'), 200: withAlpha('--c-gray-200'), 300: withAlpha('--c-gray-300'), 400: withAlpha('--c-gray-400'), 500: withAlpha('--c-gray-500'), 600: withAlpha('--c-gray-600'), 700: withAlpha('--c-gray-700'), 800: withAlpha('--c-gray-800'), 900: withAlpha('--c-gray-900'), 950: withAlpha('--c-gray-950') }; /** * 强调色(blue / red / green / amber / orange / yellow)。 * * 这条色阶在这套代码里也是**语义色阶**,与灰阶同理: * - `50` – `300` = 表面(chip 底、提示条底、徽标底、边框) * - `400` – `900` = 前景(文字、图标、实心按钮底) * * 深色模式下两段的走向**相反**:表面段要变暗(照搬浅色的近白值会在深色页面上 * 糊出一块刺眼亮斑),前景段要变亮(照搬浅色的 red-700 落在深色卡片上只有 * 2.67:1,读不动)。所以它必须走变量,不能写死 —— 见 index.css 的两组定义。 */ const accent = (name) => ({ 50: withAlpha(`--c-${name}-50`), 100: withAlpha(`--c-${name}-100`), 200: withAlpha(`--c-${name}-200`), 300: withAlpha(`--c-${name}-300`), 400: withAlpha(`--c-${name}-400`), 500: withAlpha(`--c-${name}-500`), 600: withAlpha(`--c-${name}-600`), 700: withAlpha(`--c-${name}-700`), 800: withAlpha(`--c-${name}-800`), 900: withAlpha(`--c-${name}-900`) }); /** * 实心按钮/徽标的底色 —— `--s-*`,两种模式下**同值**。 * * 为什么不能跟 accent 的前景段走:那一段在深色下被提亮成了浅色 * (red-600 → rgb(246,141,141)),而 `bg-red-600 text-white` 的白字落上去 * 只有 1.6:1。实心按钮的底色本来就该保持饱和 —— 深色模式下变的是页面, * 不是「危险操作按钮是红的」这件事。 * * 只覆盖 backgroundColor,`text-red-600` / `border-red-600` 仍走 accent()。 * Tailwind 的 backgroundColor 默认继承 colors,这里逐档覆写 400–900 * (50–300 是表面段,仍从 colors 继承 —— 它们在深色下就该变暗)。 */ const solid = (name) => ({ 400: withAlpha(`--s-${name}-400`), 500: withAlpha(`--s-${name}-500`), 600: withAlpha(`--s-${name}-600`), 700: withAlpha(`--s-${name}-700`), 800: withAlpha(`--s-${name}-800`), 900: withAlpha(`--s-${name}-900`) }); export default { content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], // class 而不是 media:主题要能被人显式选择。跟系统走是**默认值**, // 不是唯一选项 —— 白天开深色主题是常见偏好。 darkMode: 'class', theme: { extend: { /** * 层次(elevation)。 * * index.css 里一直定义着 `--shadow-1/2/3` 三个语义令牌,但**没有任何地方用过** * —— 全站只有 10 处阴影,而且都是 Tailwind 的系统默认档(shadow / shadow-sm / * shadow-2xl)。同时界面用 67 条 `border-*-gray-200` 硬线来分组。 * * 结果就是整个应用看起来是平的:弹窗、下拉、抽屉飘不起来。 * 这里把已有的令牌接出来,让层次成为可用且统一的表达手段( * 也是深色模式下唯一有效的分组方式 —— 深色里靠边框分组几乎看不见)。 */ boxShadow: { 1: 'var(--shadow-1)', 2: 'var(--shadow-2)', 3: 'var(--shadow-3)', panel: 'var(--shadow-panel)' }, /** * 字号体系。 * * # 为什么要重定而不是用默认值 * * 默认档位(xs=12px)在这套界面上被当**正文**用了 172 处, * 另有一批 text-[9px]/[10px]/[11px] 的魔法数字(共 171 处)—— * 最小只有 9px,在现在的显示器上基本读不动。整个界面看上去老旧, * 首先就是因为**字太小**。 * * 这里的做法是把比例尺整体抬一档,并给两个小档位起名字: * - `2xs` / `3xs` 取代 11px / 9-10px 的魔法数字(不再出现裸 px) * - `xs` 12px → 13px(原本拿它当正文的地方自动变舒适) * - `sm` 14px 保持不变(现在它承担小标题与主要正文) * * 行高一起给:字号变大而不放行高会把密排的列表顶得很难看。 * 小档位用 1.4(紧凑但不能挤),正文档 1.55。 */ fontSize: { '3xs': ['0.6875rem', { lineHeight: '1.2' }], // 11px —— 角标与元信息下限 '2xs': ['0.75rem', { lineHeight: '1.35' }], // 12px —— 元信息 xs: ['0.8125rem', { lineHeight: '1.45' }], // 13px —— 次要正文 sm: ['0.875rem', { lineHeight: '1.55' }], // 14px —— 正文 base: ['0.9375rem', { lineHeight: '1.55' }], // 15px lg: ['1.0625rem', { lineHeight: '1.5' }], xl: ['1.25rem', { lineHeight: '1.4' }], '2xl': ['1.5rem', { lineHeight: '1.35' }] }, /** * 圆角整体调大一档。 * * 原值(rounded 0.25rem / md 0.375rem)是好几年前 Tailwind 默认的 * 紧凑风格,在宽屏桌面应用上显得硬。这里只动默认比例尺,不改任何 * 组件的类名 —— 全站 200 处圆角一次性刷新,也不会出现「新组件用大圆角、 * 旧组件还是小圆角」的断层。 * * 不用 CSS 变量:Tailwind 的 borderRadius 不参与主题切换, * 两种模式下圆角本就相同,走变量只会多一层间接。 */ borderRadius: { DEFAULT: '0.5rem', md: '0.625rem', lg: '0.75rem', xl: '1rem', '2xl': '1.25rem', /* * 语义化令牌(定义在 index.css 的 :root)。 * * 数值档位是「大中小」;这两个名字回答的是「用在哪里」: * - card 卡片/面板(比控件更大,观感更轻) * - control 按钮/输入/滑杆(可点控件的统一形状) * 两者都是显式选择,不依赖作者记得选 md 还是 lg。 */ card: 'var(--radius-card)', control: 'var(--radius-control)' }, /** * textColor 单独覆盖 white。 * * `--c-white` 服务两种**互相冲突**的用途: * - `bg-white` = 卡片表面 → 深色模式必须变暗 * - `text-white` = 彩色按钮上的文字 → 深色模式必须**保持浅色** * * 只有一个变量时后者跟着变暗,白字落在 `bg-chrome-700` 的激活导航项上 * 只剩 1.34:1 —— 几乎不可见(实测发现)。按钮底色在深色模式下依然是 * blue-600 那样的彩色,上面的文字本来就该是白的。 * * Tailwind 的 textColor 默认继承 colors,这里只改 white 一项, * 其余(gray/blue/...)仍走反转的色阶。 */ textColor: { white: withAlpha('--c-on-accent') }, /** * backgroundColor 单独覆盖强调色的 400–900 档,让实心按钮底保持饱和。 * * 每个强调色的 400–900 在这套代码里承担**两个冲突用途**: * - `text-red-600` / `border-red-300` = 深色下必须**提亮**才读得动 * - `bg-red-600` + `text-white` = 深色下必须**保持饱和** * * 共用一档时后者必坏:深色下 red-600 被提亮到 rgb(246,141,141), * 白字落上去只有 1.6:1 —— 与 text-white/bg-white 那次是同一类错误 * (一个名字服务两种语义)。 * * 50–300 不覆盖:那是表面段,深色下就该跟着变暗。 */ backgroundColor: { blue: solid('blue'), red: solid('red'), green: solid('green'), amber: solid('amber'), orange: solid('orange'), yellow: solid('yellow') }, colors: { white: withAlpha('--c-white'), gray: grayScale, // slate 在这套代码里只用于登录页与少数深色块,与 gray 同源即可 —— // 保留两个名字是为了不改那些组件,但它们指向同一条色阶。 slate: grayScale, /** * chrome —— 应用框架(侧栏 / 底部导航)的专用色阶。 * * 为什么不能跟 gray 走:这两块**在浅色模式下本来就是深色的** * (深色侧栏配浅色内容区是这套 UI 的原本设计)。把它们并入反转的 * gray 之后,深色模式下 `bg-slate-900` 变成了近白色 —— 侧栏比内容区 * 还亮,整个层次翻了过来(实测 rgb(243,245,248),而内容区是 rgb(17,19,24))。 * * 独立成一条色阶后:浅色模式下它是深色框架,深色模式下**微调即可** * (比内容区略深一点,保持"框架比内容更沉"的关系),两种模式下 * 语义一致。 */ chrome: { 100: withAlpha('--c-chrome-100'), 200: withAlpha('--c-chrome-200'), 400: withAlpha('--c-chrome-400'), 600: withAlpha('--c-chrome-600'), 700: withAlpha('--c-chrome-700'), 800: withAlpha('--c-chrome-800'), 900: withAlpha('--c-chrome-900') }, /* * 强调色。表面段(50–300)在深色下变暗、前景段(400–900)变亮, * 两段走向相反 —— 详见 accent() 的注释与 index.css 的两组定义。 * * 实心按钮底另走 --s-*(见上面的 backgroundColor)。 * * 注意这里**只能有一份定义**:JS 对象字面量的重复键后者胜出, * 而那不会报错。上一版同时写了固定 hex 与 accent() 两份, * accent() 覆盖了 hex 那份,但 index.css 里当时没有对应的 --c-red-* 等 * 变量 —— `rgb(var(--c-red-600) / 1)` 里的变量未定义使整条声明失效, * 于是 bg-red-600 退回透明,白字落在白卡片上:按钮看不见但点得动。 */ blue: accent('blue'), red: accent('red'), green: accent('green'), amber: accent('amber'), orange: accent('orange'), yellow: accent('yellow') } } }, plugins: [] };