打开任何一个看起来“很贵”的产品。
Linear、Stripe、Vercel、Raycast,都可以。
别急着看它的品牌色。先看背景、边框、弱化文字、阴影。
你会发现,真正拉开质感差距的,不是那个最亮眼的主色,而是藏在这些细节里的一个决定。
大多数设计师根本没有主动做过这个决定。
但正是它,把一个“像是被设计过的 UI/UX”,和一个“像是拼起来的 UI”区分开了。
这个决定就是:
它们从来不用纯中性色。
不是纯白 #FFFFFF。
不是纯灰 #F5F5F5。
也不是纯黑 #000000。
每一层界面表面,都带着一点品牌色的温度。
每一个阴影,都偏向同一种冷暖方向。
每一段弱化文字,也不是死灰,而是有轻微的色相倾向。
品牌色不是浮在设计上面。
它是流在整个界面里面。
这就是那个关键决定。
只要这一步做对了,后面的高级感,很多都会自然长出来。
为什么纯中性色会毁掉 UI?
真实世界里,几乎没有纯白。
纸张有暖度。包装有色偏。布料有底色。
你觉得“高级”的材料,往往都不是绝对中性的。它们的中性色里,都会藏着一点微妙的色相。
数字界面也是一样。
一张卡片放在 #FFFFFF 上,再塞进一个 #F8F8F8 的页面里,很容易像表格。
但同样一张卡片,如果放在 #FFFDF8 上,外层背景换成 #F7F4EF,它马上就会像是被认真打磨过。
这两张卡片之间的差距,可能只有 2% 到 3% 的暖色偏移。
但这就是秘密。
高级感不是突然出现的。
它通常就藏在这几个百分点里。
这个决定,藏在三个地方
1. 被染色的背景和表面
最常见的错误是什么?
品牌色是蓝色,但整套中性色还是默认灰。
结果就是,蓝色像是被硬贴上去的。
它不属于这个空间。
它只是一个客人,站在自己家里。

暖色倾向的中性色

冷色倾向的中性色

深色模式里的染色中性色
解决方法并不复杂:
把品牌色轻轻混进你的灰阶系统里。
不是明显到让用户看出“这里是蓝色背景”。
而是让整个背景朝着品牌色轻轻倾斜。
/* 死的:纯灰中性色,和品牌没有关系 */
:root {
--brand: #2563EB;
--bg: #F8F8F8;
--surface: #FFFFFF;
--border: #E5E5E5;
--text-muted: #6B6B6B;
}
/* 活的:每个中性色都带一点品牌色 */
:root {
--brand: #2563EB;
--bg: #F5F7FF; /* 白色 + 3% 品牌蓝 */
--surface: #FAFBFF; /* 白色 + 1.5% 品牌蓝 */
--border: #E4E8F5; /* 灰色 + 5% 品牌蓝 */
--text-muted: #6B7280; /* 本身就偏蓝的冷灰 */
}
背景不需要大声喊“我是蓝色”。
它只需要稍微偏向蓝色。
这样,当品牌色出现时,它就不会像被临时贴上去。
它会像本来就属于这里。
2. 带温度的阴影
过去十年,几乎每个 UI 教程里都能看到这一句:
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
它太常见了。
也几乎总是错的。

暖色阴影

冷色阴影

深色背景中的阴影
阴影是什么?
阴影不是一层黑色贴纸。
它是光线缺席后的结果。
而这种“缺席”的颜色,会被周围环境光影响。
在暖色表面上,阴影会偏琥珀色。
在冷色表面上,阴影会偏蓝紫色。
纯黑色没有这种关系。
它看起来像 Photoshop 里硬叠上去的效果,而不是一个真实表面自然产生的深度。
/* 普通:纯黑阴影,像贴上去的 */
.card {
box-shadow:
0 1px 3px rgba(0, 0, 0, 0.10),
0 4px 16px rgba(0, 0, 0, 0.08);
}
/* 高级:阴影带有品牌色温度 */
/* 蓝色品牌 */
.card {
box-shadow:
0 1px 3px rgba(37, 99, 235, 0.08),
0 4px 16px rgba(37, 99, 235, 0.06),
0 0 0 1px rgba(37, 99, 235, 0.04);
}
/* 暖色品牌 */
.card {
box-shadow:
0 1px 3px rgba(120, 80, 40, 0.08),
0 4px 16px rgba(120, 80, 40, 0.05),
0 0 0 1px rgba(120, 80, 40, 0.04);
}
这里的透明度故意很低。
目标不是让用户明显看出“阴影被染色了”。
目标是避免它变成一块没有温度的黑色贴片。
还有一层很多设计师会忽略:
0 0 0 1px
这层非常关键。
它用阴影属性制造了一条发丝级边框。
不需要额外写 border。
不会造成布局位移。
还能继承整个阴影系统的温度。
它很安静,但做了很多脏活。
3. 有温度的文字层级
纯黑文字配纯白背景,是可访问性的底线。
它是最低要求。
但它不是设计目标。

暖色文字层级

冷色文字层级

深色模式文字层级
你喜欢的那些高级产品,几乎都不会直接用纯黑。
它们会用接近黑色、但带有轻微色相偏移的文字。
弱化文字也不会直接走灰色,而是顺着品牌色的冷暖方向走。
这样,层级仍然清楚。
但整个界面会更统一。
/* 平:没有温度,也和品牌没有关系 */
:root {
--text-primary: #000000;
--text-secondary: #666666;
--text-muted: #999999;
}
/* 暖色品牌:每一层文字都偏一点琥珀 */
:root {
--text-primary: #1A1614;
--text-secondary: #4A4440;
--text-muted: #9A8E85;
}
/* 冷色品牌:每一层文字都偏蓝灰 */
:root {
--text-primary: #0F1623;
--text-secondary: #3D4A5C;
--text-muted: #7B8CA0;
}
这些值在正文尺寸下依然可以通过 WCAG AA。
所以这里并没有牺牲可读性。
只是把温度加进了已有的对比度预算里。
一套完整的 Token 系统
下面是两套完整品牌系统。
一套偏暖,适合 editorial、fintech、高端 SaaS。
一套偏冷,适合开发工具、数据分析、B2B 科技产品。
每一个中性色都被轻轻染过。
每一个阴影都有冷暖方向。
品牌色不是突然冒出来的按钮颜色,而是整个页面里早就存在的色彩强化。
/* 暖色品牌:editorial、fintech、高端 SaaS */
:root[data-theme="warm"] {
--color-bg: #F7F3EE;
--color-surface: #FDFAF7;
--color-surface-2: #F2EDE6;
--color-border: #E8DDD2;
--color-border-strong:#C8B8A8;
--color-text: #1A1410;
--color-text-2: #4A3E34;
--color-text-muted: #9A8878;
--color-brand: #C4502A;
--color-brand-subtle: #F5EAE4;
--shadow-sm: 0 1px 3px rgba(100,60,30,0.08), 0 0 0 1px rgba(100,60,30,0.04);
--shadow-md: 0 4px 16px rgba(100,60,30,0.10), 0 1px 4px rgba(100,60,30,0.06);
--shadow-lg: 0 12px 40px rgba(100,60,30,0.12), 0 2px 8px rgba(100,60,30,0.06);
}
/* 冷色品牌:开发工具、数据分析、B2B 科技 */
:root[data-theme="cool"] {
--color-bg: #F4F6FA;
--color-surface: #FAFBFD;
--color-surface-2: #EDF0F7;
--color-border: #DDE3EE;
--color-border-strong:#BCC6DA;
--color-text: #0F1520;
--color-text-2: #3A4560;
--color-text-muted: #7A8BA0;
--color-brand: #2563EB;
--color-brand-subtle: #EEF3FF;
--shadow-sm: 0 1px 3px rgba(30,50,100,0.08), 0 0 0 1px rgba(30,50,100,0.04);
--shadow-md: 0 4px 16px rgba(30,50,100,0.10), 0 1px 4px rgba(30,50,100,0.06);
--shadow-lg: 0 12px 40px rgba(30,50,100,0.12), 0 2px 8px rgba(30,50,100,0.06);
}
每个 token 都有色相。
但都不明显。
这就是重点。
只要它们不再像互不相干的默认灰,系统感就会出来。
一个真实卡片组件
下面是一个简单卡片组件。
它没有复杂动画,没有高级滤镜,也没有夸张视觉效果。
但只要前面的 token 系统是统一的,它就会自然显得更精致。
.card {
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: 8px;
box-shadow: var(--shadow-md);
padding: 24px;
}
.card-label {
font-size: 0.7rem;
font-weight: 500;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--color-text-muted);
margin-bottom: 8px;
}
.card-title {
font-size: 1.25rem;
font-weight: 600;
color: var(--color-text);
line-height: 1.3;
margin-bottom: 8px;
}
.card-body {
font-size: 0.9rem;
line-height: 1.65;
color: var(--color-text-2);
}
.card-badge {
display: inline-block;
background: var(--color-brand-subtle);
color: var(--color-brand);
font-size: 0.72rem;
font-weight: 500;
letter-spacing: 0.06em;
padding: 3px 10px;
border-radius: 4px;
margin-top: 16px;
}

注意这个 badge。
它用的是 --color-brand-subtle。
不是随手找一个浅蓝。
也不是随便洗淡一个橙色。
它是品牌色在低饱和度下的准确延伸。
这一个 token,会让 badge 看起来像是系统里长出来的,而不是从某个组件库里搬过来的。
深色模式:规则一样,方向相反
深色模式不是黑色背景。
深色模式应该是“被染色的深色背景”。
:root[data-theme="dark"] {
--color-bg: #0F1117;
--color-surface: #161B27;
--color-border: #1E2738;
--color-text: #E8EDF5;
--color-text-2: #8A9AB5;
--shadow-sm: 0 0 0 1px rgba(100,140,255,0.08);
--shadow-md: 0 4px 20px rgba(0,0,0,0.4), 0 0 0 1px rgba(100,140,255,0.06);
}
#18181B 是 zinc-900。
它没有色相。
没有温度。
它只是默认值。

而 #0F1117 会偏向蓝黑色系。
#161B27 这个表面色,也明显和品牌处在同一个色彩对话里。
#E8EDF5 则是偏冷的灰白,而不是临床感很重的纯白。


在深色背景上,阴影会变成光晕。
0 0 0 1px 那一层,也从深度边框变成了亮度边框。
原则没变。
只是方向反过来了。
上线前,做一个测试
把整个界面去饱和,变成灰度。
如果层级依然清楚,说明色温做得是对的。
它是隐形的,只是在提升质感。
但如果界面一去色就塌了,说明你的色相承担了太多结构职责。
这就不对。

色温应该建立在稳固的灰阶系统之上。
它不是灰阶系统的替代品。
三条规则,所有界面都适用
第一,永远不要使用纯中性色。
#FFFFFF、#000000、#808080 不属于任何品牌。
一套真正用于生产的 token 系统里,每一个中性色都应该带一点色相。
哪怕只有 2%。
第二,阴影颜色要匹配品牌温度。
蓝色品牌,就用带蓝色倾向的阴影。
暖色品牌,就用琥珀倾向的阴影。
纯黑阴影不属于任何系统。
所以它看起来也确实不属于那里。
第三,所有交互状态都要染色。
Hover、Focus、Active,都应该使用 --color-brand-subtle,或者使用比表面色更饱和一点的变体。
不要用一层平灰色 overlay 糊上去。
这个决定并不复杂。
它只是太容易在赶进度的时候被跳过。
你看过的所有高级 UI,底层都在做这件事。
现在你知道它到底怎么做了。
所有 CSS 都使用 custom properties。
不需要预处理器。
兼容所有现代浏览器。
高级 UI,没有例外
我们已经讲过那条核心规则:
每一个中性色,都要带一点品牌色的温度。
阴影不能是死黑,要和品牌的冷暖方向一致。
文字层级不能用扁平灰,而是要微微偏暖或偏冷。
品牌色不是盖在界面上面。
它应该流进整个设计系统里。
大多数设计师做到这里,就停了。
背景处理了。卡片处理了。文字处理了。
然后,问题来了。
蓝色品牌里突然出现一个红色错误提示。
渐变做到一半,另一端飘成了纯白。
图标库一接进来,怎么看都像从别的产品里复制过来的。
这时候,很多人会以为规则失效了。
其实没有。
只是这条规则还需要三个延伸。
而这三个延伸,几乎没人认真讲。
色温冲突问题
想象一个蓝色品牌产品。
它已经有了一套完整的染色中性色系统。
背景轻轻偏蓝。阴影带一点靛蓝。文字层级也一直保持冷色方向。整个界面原本很统一。
然后,表单校验触发了。
一个红色错误提示跳出来。

暖色系统里的色温冲突

冷色系统里的色温冲突

深色模式里的色温冲突
这个红色看起来不属于这里。
不是因为错误状态不该用红色。红色当然可以表达错误。
问题在于,这个红色来自另一个宇宙。
它可能来自组件库。来自默认设计系统。来自某套假设“背景是中性灰”的色板。
当它被直接丢进一个冷色调系统时,就像图书馆里突然响起火警。
信号是对的。温度是错的。
解决办法不是把错误色改成绿色,也不是否定红色。
真正的办法,是把红色轻轻推向品牌色温。
只推一点点。
让它不再和整个界面打架。
/* 像从别处拿来的颜色,不属于任何系统 */
--color-error: #DC2626;
/* 冷色品牌:把错误红朝蓝色方向轻推一点 */
--color-error: #D42B3A;
/* 暖色品牌:把错误红朝琥珀红方向轻推一点 */
--color-error: #C8392B;
这个色相变化很小。
大概只有色轮上的 6 到 10 度。
它依然是红色。依然能清楚表达危险。
但它不再像一个外来物。
它和周围系统共享了一点点温度,眼睛就会接受它属于这里。
同样的逻辑,适用于所有语义色。
/* 冷色品牌:所有语义色都轻微偏蓝 */
:root[data-theme="cool"] {
--color-error: #D42B3A; /* 红色,但略微冷移 */
--color-error-subtle: #EEF0FF; /* 带品牌蓝,不是纯粉底 */
--color-warning: #C08A10; /* 琥珀色,向冷色方向降饱和 */
--color-warning-subtle: #F0F2FF; /* 几乎接近 brand-subtle */
--color-success: #1E7A4A; /* 绿色,略微蓝移 */
--color-success-subtle: #EDF1FF; /* 依然偏向品牌场 */
}
/* 暖色品牌:所有语义色都轻微偏琥珀 */
:root[data-theme="warm"] {
--color-error: #C8392B; /* 红色,但略微暖移 */
--color-error-subtle: #FFF0EC; /* 暖调浅底,接近 brand-subtle */
--color-warning: #B87820; /* 琥珀色,本来就适配暖系统 */
--color-warning-subtle: #FFF4E6; /* 暖奶油色,和背景同族 */
--color-success: #2E7A42; /* 绿色,略微暖移 */
--color-success-subtle: #F0F7EE; /* 很淡的暖绿 */
}
这里最容易被忽略的是 subtle token。
冷色品牌里的错误浅底,不应该是 #FEE2E2。
那种粉白色和蓝调表面没有任何关系。
它和 Part 1 里那个死灰中性色,本质上是同一个问题。
浅底色应该偏向品牌所在的环境场,而不是偏向某个“通用错误色”的默认理解。
错误输入框的边框,也一样。
/* 普通写法:红色边框和冷色表面没有关系 */
.input-error {
border-color: #DC2626;
background: #FEF2F2;
}
/* 冷色品牌:错误仍然是红色,但输入框底色偏向品牌场 */
.input-error {
border-color: #D42B3A;
background: #F0F0FF;
box-shadow: 0 0 0 3px rgba(212, 43, 58, 0.12),
0 0 0 1px rgba(37, 99, 235, 0.06);
}
这个输入框的 focus ring 同时带着两种温度。
外圈是错误红。内侧发丝线是品牌蓝。
用户不会分析这些细节。
但眼睛会知道:它们属于同一个系统。
因为它们下面的整个界面,本来就已经是冷色调的。
让渐变也属于系统
渐变是什么?
本质上,是两个中性色加一个方向。
Part 1 里讲过的所有规则,在渐变里要执行两次。

暖色渐变

冷色渐变

深色渐变
最常见的渐变错误,是其中一个色标飘向纯白。
比如一张卡片从 #EEF3FF 渐变到 #FFFFFF。
前一个色标是对的。它属于品牌系统。
但后一个纯白不属于任何东西。
于是,渐变走到一半,就穿过了一条色温边界。
整个表面马上变得没有完成感。
修复方法很简单:
两个色标,都不能是纯色。
/* 坏的:一个色标属于品牌,一个色标什么都不属于 */
.hero {
background: linear-gradient(160deg, #EEF3FF 0%, #FFFFFF 100%);
}
/* 好的:两个色标都轻微偏向品牌,渐变始终留在系统内 */
.hero {
background: linear-gradient(160deg, #EDF1FF 0%, #F8FAFF 100%);
}
第二个色标不是 #FFFFFF。
而是 #F8FAFF。
只有大约 2% 的蓝。单独看几乎看不出来。
但它足够让整个渐变留在同一个色温场里。
渐变方向也不是随便来的。
冷色品牌更适合从底部或角落的深蓝,向中心或顶部的浅蓝白过渡。
暖色品牌则更适合从底部的深琥珀,向顶部的淡奶油色过渡。
这更接近自然光照在真实材料上的方式。
所以它会显得高级。
用户不一定说得出来原因。但他会感觉到。
/* 冷色品牌:光源来自上方中心 */
.feature-surface {
background: linear-gradient(
175deg,
#F8FAFF 0%,
#EDF1FF 40%,
#E4EBFF 100%
);
}
/* 暖色品牌:光源来自上方,底部琥珀感加深 */
.feature-surface {
background: linear-gradient(
175deg,
#FDFAF7 0%,
#F5EFE8 40%,
#EDE4D8 100%
);
}
Mesh gradient 也是同一个规则,只是多了一个约束:
每一个颜色点,都要带品牌色温。
最容易犯的错,是把品牌色当作其中一个点,再用纯白作为环境底色。
结果还是一样:品牌色漂在一个不属于任何系统的中性场上。
/* Mesh gradient:冷色品牌。每个点都偏蓝 */
.mesh-surface {
background:
radial-gradient(ellipse at 20% 20%, #D6E4FF 0%, transparent 60%),
radial-gradient(ellipse at 80% 80%, #C8D9FF 0%, transparent 60%),
radial-gradient(ellipse at 60% 10%, #E8EEFF 0%, transparent 50%),
#F4F6FA; /* base 是染色后的 bg token,永远不要用 #FFFFFF */
}
底层是 --color-bg,不是白色。
每一个径向点,都是被调过的蓝。
你会发现,这个渐变里甚至不需要直接出现品牌色。
它不需要喊出来。
因为整个场已经朝品牌方向倾斜了。
图标和插画:缩小版问题
图标,其实是缩小版的阴影。
控制阴影的规则,也控制图标描边。

暖色图标与插画

冷色图标与插画

深色模式里的图标与插画
大多数图标库默认给你的描边,都是纯灰。
#374151。#6B7280。#9CA3AF。
把这些颜色放到染色表面上,Part 1 里的问题就会以更小比例再次出现。
图标坐在暖色或冷色场里,但它看起来像从别处拿来的。
不至于错到让人立刻发现。
但足以让 UI 有一种“拼装感”。
修复方式和文字颜色完全一样。
/* 普通:外来的灰色描边,不属于任何系统 */
.icon-primary { color: #374151; }
.icon-secondary { color: #6B7280; }
.icon-muted { color: #9CA3AF; }
/* 冷色品牌:图标颜色直接来自文字 token */
.icon-primary { color: var(--color-text); /* #0F1520 */ }
.icon-secondary { color: var(--color-text-2); /* #3A4560 */ }
.icon-muted { color: var(--color-text-muted); /* #7A8BA0 */ }
/* 暖色品牌:同样处理 */
.icon-primary { color: var(--color-text); /* #1A1410 */ }
.icon-secondary { color: var(--color-text-2); /* #4A3E34 */ }
.icon-muted { color: var(--color-text-muted); /* #9A8878 */ }
图标颜色,就是文字颜色。
不是近似值。
不是为了让图标重新发明一套颜色。
就是同一套 token。
这不是偷懒。这是正确答案。
一个图标如果出现在文字标签旁边,它就应该和文字拥有同样的温度。
直接复用同一个 token,可以避免你做多余判断。
填充图标和浅色容器,也沿用 Part 1 里的 badge 规则。
/* 冷色品牌:图标容器用 brand-subtle,图标描边用 brand */
.icon-container {
background: var(--color-brand-subtle); /* #EEF3FF */
color: var(--color-brand); /* #2563EB */
border-radius: 8px;
padding: 10px;
}
/* 不要这样:通用浅灰容器,不属于任何系统 */
.icon-container-dead {
background: #F3F4F6;
color: #374151;
}
插画会更难一点。
因为插画包含更多颜色,它和 token 系统的关系也没那么直接。
但原则完全一样。
插画里的阴影,应该带品牌色温。
皮肤、布料、建筑、纸张这些中间调,也应该根据品牌的冷暖方向微微偏移。
纯白和纯黑,最好不要出现。
如果是冷色品牌的产品插画,落在表面上的阴影不应该是:
rgba(0,0,0,0.15)
它应该更接近:
rgba(30,50,100,0.12)
也就是 CSS 阴影 token 里用到的同一组色相。
这样,插画和 UI 看起来像被同一个光源照亮。
如果是暖色品牌,插画高光应该偏暖奶油色,阴影偏琥珀棕。
存在于 --color-bg 里的那个色相,也应该出现在插画里的布料、纸张、木纹和阴影中。
Illustration shadow palette · Cool brand
Deep shadow: #1A2A4A (海军蓝暗部,和 color-text 同族)
Mid shadow: #3A4D6A (蓝灰中间调,和 color-text-2 同族)
Light shadow: #8AA0C0 (冷灰蓝,和 color-text-muted 同族)
Highlight: #F0F4FF (近白高光,和 color-surface 同族)
Illustration shadow palette · Warm brand
Deep shadow: #2A1A0E (深琥珀棕,和 color-text 同族)
Mid shadow: #5A3E2A (暖棕中间调,和 color-text-2 同族)
Light shadow: #A08070 (暖茶色,和 color-text-muted 同族)
Highlight: #FDF8F2 (暖奶油高光,和 color-surface 同族)
插画色板,本质上就是 token stack 的可视化版本。
每一层阴影,对应一层文字 token。
每一个高光,对应一个表面 token。
插画师和 UI 设计师,不应该各用各的颜色。
他们应该共享同一个色温源。
Live System



把这三个延伸放在一起看,系统感就会非常明显。
一个表单错误状态,使用了被色温校正过的红色,以及偏向品牌场的输入框背景。
一个渐变表面,两个色标都留在 token 系统里,没有任何一端跑去纯白。
切换主题时,每个 token 都会一起重新连线。
这才是系统。
不是几个组件长得像。
而是每一层颜色都服从同一个温度逻辑。
统一规则
Part 1 说过:
品牌色应该流进设计,而不是盖在设计上。
Part 2 其实还是这句话。
只是把它应用到 Part 1 没讲完的地方。

统一规则:暖色

统一规则:冷色

统一规则:深色
语义色没有例外。
把它们轻轻推向品牌温度。
它们的 subtle 背景,也要偏向品牌,而不是默认粉、默认黄、默认绿。
渐变没有例外。
两个色标都不能是纯白或纯灰。
渐变方向要符合自然光打在带有品牌环境温度的表面上的行为。
图标没有例外。
描边颜色来自文字 token stack。容器背景来自 brand-subtle。
一个用通用灰描边的图标,放在染色表面上,本质上和一个纯灰边框放在染色卡片上一样突兀。
插画也没有例外。
每一张插画的阴影色板,都要从 UI 阴影同一套 token 家族里推出来。
光源一致。环境温度一致。
Part 1 的测试方法依然有效:
把整个界面去饱和。
如果层级还成立,说明色温工作是隐形且正确的。
如果语义红色一变灰就埋进背景里,说明你推得还不够,或者对比关系没站稳。
如果插画像是从另一个产品里搬来的,说明它的阴影色板已经漂回纯灰了。
一个决定。
贯穿每一层。
没有例外。
在 云栈社区,和更多开发者一起探讨设计系统与前端工程化的最佳实践。