Build a Color System for Web Design in 2026 (OKLCH + Tokens)
您的品牌更新在上午9點推出。行銷在Figma中更新三個十六進位代碼。到了中午,您的按鈕在深色模式中變得不可見,您的對比度比率未能通過WCAG 2.2,您的CSS自訂屬性級聯成一個47變數的混亂,沒有人敢觸摸。我至少重建過這個失敗一打次——拼湊HSL數學,硬編碼亮度調整,祈禱下一個設計令牌外掛程式會拯救我。每個系統在深色模式出現、無障礙稽核進行或客戶要求「只要再加一種品牌顏色」時都會崩潰。修復不是另一個React鉤子或Sass mixin。這是一個顏色架構,可以在與您實際設計流程的接觸中倖存——一個從第一次提交就將感知均勻性、語義命名和模式切換視為限制的架構,而不是發佈後的補丁。
不過2026年不同。不是因為問題變得更容易,而是因為工具終於趕上了複雜性。OKLCH色彩空間在每個主要瀏覽器中登陸、Material Design 3的動態色彩架構成熟,以及設計令牌規範達到W3C候選推薦狀態之間,我們現在實際上有了一個連貫的堆棧。本文介紹瞭如何構建處理亮色模式、深色模式、高對比度、無障礙合規性和主題設定的色彩系統——而不會讓您想要翻轉一張桌子。
為什麼大多數色彩系統會崩潰
大多數色彩系統失敗是因為它們從錯誤的地方開始。設計師選擇一個主要品牌顏色,在Figma中生成調色盤,並將十六進位值交給開發人員。這些十六進位值灑在元件檔案中。然後有人要求深色模式,一切都崩潰了。
問題不在於顏色本身——在於架構。或者更確切地說,缺少架構。色彩調色盤不是色彩系統。調色盤給你一堆色板。系統給你規則,說明這些色板如何應用於不同的上下文、主題和無障礙要求。
生產色彩系統實際上需要什麼:
- 基本令牌——原始色彩值(您的調色盤)
- 語義令牌——顏色表示什麼(背景、表面、文字、錯誤等)
- 元件令牌——顏色如何應用於特定的UI元素
- 主題變體——所有上述內容如何在亮色/深色/高對比度模式之間移動
- 無障礙保證——對比度內置於令牌關係中,而不是在事實之後檢查
如果這些層中的任何一個都缺失,您最終都會遇到瓶頸。相信我的話。
Web的色彩理論基礎
在我們進入實現之前,讓我們將自己置於對UI工作真正重要的色彩理論中。我不會重述整個色輪——您可以在任何地方找到它。相反,讓我們關注讓人們困惑的事情。
感知均勻性
這裡有一件事在我理解之前困擾我多年:為什麼用均勻間隔的HSL色調值生成的調色盤看起來……不均勻?答案是感知均勻性。HSL將色彩空間視為數學上統一的,但我們的眼睛並不能這樣感知。在藍色中從50%到40%的10%亮度偏移看起來與黃色中的相同偏移大不相同。
這就是OKLCH如此重要的原因。在OKLCH中,亮度的10單位變化看起來像是相同數量的變化,無論色調如何。這是以程式方式生成一致調色盤的基礎。
60-30-10規則仍然成立
即使在2026年,經典室內設計原則也完美地轉化為UI:
- 60%——主導表面/背景顏色
- 30%——次要顏色(卡片、邊欄、部分)
- 10%——強調顏色(按鈕、連結、亮點)
您的色彩系統應該使這個比例預設易於實現。
溫暖與涼爽和感知重量
溫暖的顏色(紅色、橙色、黃色)感覺更沉重且更接近。涼爽的顏色(藍色、綠色、紫色)後退。這影響使用者對資訊層次結構的感知。您的主要動作顏色通常應該比您的背景更溫暖,以創建自然的視覺權重。
從Material Design 3的色彩架構學習
Material Design 3(M3)引入了他們稱之為「動態顏色」的東西——無論您是否使用Material Design本身,該架構都值得研究。Google的團隊本質上以規模方式解決了色彩系統問題,我們可以竊取他們最好的想法。
色調調色盤概念
M3為每個關鍵顏色生成色調調色盤——從0(黑色)到100(白色)的13個色調。每個色調有特定的角色:
| 色調 | 典型用法 | 亮色模式 | 深色模式 |
|---|---|---|---|
| 0 | 黑色 | — | — |
| 10 | 深色表面 | — | 表面變體 |
| 20 | 較暗元素 | — | 表面 |
| 30 | 深色強調 | 在主要容器上 | — |
| 40 | 主要 | 主要 | — |
| 50 | 中等範圍 | — | — |
| 60 | 較淡強調 | — | — |
| 70 | 淡色元素 | — | 主要容器 |
| 80 | 淡色強調 | 主要容器 | 主要 |
| 90 | 較淡表面 | 主要容器變體 | — |
| 95 | 非常淡 | 表面變體 | — |
| 99 | 接近白色 | 表面 | — |
| 100 | 白色 | 背景 | — |
這種方法的天才之處:亮色模式和深色模式使用相同的色調調色盤,只是映射不同。色調40在亮色模式下可能是您的主要顏色,而色調80是您在深色模式下的主要顏色。相同的色調,相同的調色盤,完全不同的應用。
關鍵顏色角色
M3定義了五個關鍵顏色角色,即使在Material Design之外,我也發現這些角色效果很好:
- 主要——您的主要品牌/動作顏色
- 次要——用於較不突出元素的支援顏色
- 三級——用於視覺興趣的附加強調
- 錯誤——錯誤和破壞性動作的語義顏色
- 中性——表面、背景和文字的骨幹
每個角色都有其自己的色調調色盤。這聽起來很多——五個調色盤×13個色調= 65個基本顏色。但您只將這些中的一部分公開為語義令牌。
選擇正確的色彩空間:OKLCH vs HSL vs HEX
這個決定有實際的後果。以下是誠實的比較:
| 功能 | HEX/RGB | HSL | OKLCH |
|---|---|---|---|
| 瀏覽器支援(2026年) | 100% | 100% | ~96% |
| 感知均勻性 | 否 | 否 | 是 |
| 直觀閱讀 | 否 | 有點 | 是 |
| 輕鬆調色盤生成 | 否 | 是 | 是 |
| 色域對映 | 僅sRGB | 僅sRGB | P3 +sRGB |
CSS color-mix()相容性 |
是 | 是 | 是 |
| 設計工具支援 | 通用 | 通用 | 增長中 |
我對2026年的建議:以OKLCH撰寫,作為OKLCH和後備十六進位進行分配。原因如下。
OKLCH為您提供三個直觀的軸:
- L(亮度):0%至100%
- C(色度):0至~0.4(飽和度強度)
- H(色調):0至360度
/* OKLCH真的很愉快 */
:root {
--color-primary: oklch(55% 0.2 250); /* 一種生動的藍色 */
--color-primary-light: oklch(75% 0.15 250); /* 相同色調,較淡,色度稍微降低 */
--color-primary-dark: oklch(35% 0.18 250); /* 較暗變體 */
}
注意到您如何獨立調整亮度和色度,同時保持色調恆定。嘗試在十六進位中做到這一點。我會等著。
對於2026年不支援OKLCH的約4%的瀏覽器,使用@supports與十六進位後備。數字持續縮小。
設計令牌:真理的來源
設計令牌是命名的、與平臺無關的設計決策表示。它們是您的設計工具和程式碼庫之間的合約。在2026年,W3C設計令牌社群群組規範已經穩定到足以讓人放心地構建。
令牌架構:三層
我將色彩令牌結構化為三層。這不是武斷的——這是處理主題設定而不變得無法管理的最小可行架構。
第1層:基本令牌(調色盤)
{
"color": {
"blue": {
"10": { "$value": "oklch(15% 0.05 250)", "$type": "color" },
"20": { "$value": "oklch(25% 0.08 250)", "$type": "color" },
"30": { "$value": "oklch(35% 0.12 250)", "$type": "color" },
"40": { "$value": "oklch(45% 0.18 250)", "$type": "color" },
"50": { "$value": "oklch(55% 0.20 250)", "$type": "color" },
"60": { "$value": "oklch(65% 0.18 250)", "$type": "color" },
"70": { "$value": "oklch(75% 0.15 250)", "$type": "color" },
"80": { "$value": "oklch(85% 0.10 250)", "$type": "color" },
"90": { "$value": "oklch(92% 0.06 250)", "$type": "color" },
"95": { "$value": "oklch(96% 0.03 250)", "$type": "color" },
"99": { "$value": "oklch(99% 0.01 250)", "$type": "color" }
}
}
}
第2層:語義令牌(意義)
{
"color": {
"primary": { "$value": "{color.blue.50}", "$type": "color" },
"on-primary": { "$value": "{color.blue.99}", "$type": "color" },
"primary-container": { "$value": "{color.blue.90}", "$type": "color" },
"on-primary-container": { "$value": "{color.blue.10}", "$type": "color" },
"surface": { "$value": "{color.neutral.99}", "$type": "color" },
"on-surface": { "$value": "{color.neutral.10}", "$type": "color" },
"error": { "$value": "{color.red.50}", "$type": "color" },
"on-error": { "$value": "{color.red.99}", "$type": "color" }
}
}
第3層:元件令牌(應用)
{
"button": {
"primary": {
"background": { "$value": "{color.primary}", "$type": "color" },
"text": { "$value": "{color.on-primary}", "$type": "color" },
"hover-background": { "$value": "{color.primary-container}", "$type": "color" }
}
}
}
在2026年工作的工具
對於令牌管理和轉換,這些工具已經過實戰測試:
- Style Dictionary 4.x——將令牌轉換為任何平臺格式的標準
- Tokens Studio for Figma——在Figma和程式碼庫之間同步令牌
- Cobalt UI——一個較新的符合W3C規範的令牌編譯器,正在獲得關注
我在使用Tokens Studio在Figma中管理令牌、將其匯出為W3C格式JSON、使用Style Dictionary將其編譯為CSS自訂屬性、Tailwind config和Swift/Kotlin值方面取得了良好成果。
使用CSS自訂屬性實現
這是理論與現實相遇的地方。CSS自訂屬性(CSS變數)是您色彩系統的執行時層。它們是在沒有額外JavaScript的情況下進行主題設定、深色模式和動態更新的原因。
基本設定
/* 基本層——很少直接在元件中引用 */
:root {
--palette-blue-50: oklch(55% 0.20 250);
--palette-blue-80: oklch(85% 0.10 250);
--palette-blue-90: oklch(92% 0.06 250);
--palette-blue-10: oklch(15% 0.05 250);
--palette-neutral-10: oklch(15% 0.01 250);
--palette-neutral-90: oklch(92% 0.01 250);
--palette-neutral-95: oklch(96% 0.005 250);
--palette-neutral-99: oklch(99% 0.002 250);
--palette-red-50: oklch(55% 0.22 25);
--palette-red-80: oklch(85% 0.10 25);
--palette-red-90: oklch(92% 0.06 25);
}
/* 語義層——這是元件引用的內容 */
:root,
[data-theme="light"] {
--color-primary: var(--palette-blue-50);
--color-on-primary: var(--palette-blue-99, #ffffff);
--color-primary-container: var(--palette-blue-90);
--color-on-primary-container: var(--palette-blue-10);
--color-surface: var(--palette-neutral-99);
--color-on-surface: var(--palette-neutral-10);
--color-surface-variant: var(--palette-neutral-95);
--color-error: var(--palette-red-50);
--color-on-error: white;
}
使用 `color-mix()` 進行動態變體
CSS最近在色彩系統中的最佳補充之一是 color-mix()。您可以派生它們,而不是為每個懸停狀態和不透明度變體定義單獨的令牌:
.button-primary {
background: var(--color-primary);
color: var(--color-on-primary);
}
.button-primary:hover {
/* 將主要混合與白色以獲得較淡的懸停狀態 */
background: color-mix(in oklch, var(--color-primary) 85%, white);
}
.button-primary:active {
/* 與黑色混合以獲得按下狀態 */
background: color-mix(in oklch, var(--color-primary) 90%, black);
}
/* 半透明表面疊加層 */
.overlay {
background: color-mix(in oklch, var(--color-on-surface) 8%, transparent);
}
這非常有用。您可以計算執行時,而不是定義每種顏色的15個不透明度變體。更少的令牌,更多的靈活性。
構建實際有效的深色模式
深色模式不僅僅是「用黑色交換白色」。我已經看到這種方法多次產生令人眼花繚亂、無法通過無障礙性失敗的結果。以下是如何正確執行此操作。
重新對映策略
記得Material Design部分中的色調調色盤嗎?深色模式將語義令牌重新對映到相同調色盤中的不同色調:
[data-theme="dark"] {
--color-primary: var(--palette-blue-80); /* 曾經是50,現在80(較淡) */
--color-on-primary: var(--palette-blue-20); /* 曾經是99,現在20(較暗) */
--color-primary-container: var(--palette-blue-30); /* 曾經是90,現在30 */
--color-on-primary-container: var(--palette-blue-90); /* 曾經是10,現在90 */
--color-surface: var(--palette-neutral-10); /* 曾經是99,現在10 */
--color-on-surface: var(--palette-neutral-90); /* 曾經是10,現在90 */
--color-surface-variant: var(--palette-neutral-20);
--color-error: var(--palette-red-80);
--color-on-error: var(--palette-red-20);
}
看到發生了什麼?關係翻轉了。主要顏色在深色模式中變得較淡(因此在深色表面上可見)。背景顏色翻轉到中性調色盤的深色端。每個前景/背景對都保持其對比度關係。
尊重系統偏好
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
/* 與上面相同的深色模式值 */
--color-primary: var(--palette-blue-80);
--color-surface: var(--palette-neutral-10);
/* ... */
}
}
:not([data-theme="light"]) 選擇器是關鍵技巧。除非使用者通過切換明確選擇亮色模式,否則它會尊重系統偏好。以下是切換邏輯:
function setTheme(theme) {
if (theme === 'system') {
document.documentElement.removeAttribute('data-theme');
localStorage.removeItem('theme');
} else {
document.documentElement.setAttribute('data-theme', theme);
localStorage.setItem('theme', theme);
}
}
// 頁面載入時,應用保存的偏好
const saved = localStorage.getItem('theme');
if (saved) document.documentElement.setAttribute('data-theme', saved);
對於Next.js等框架,您需要在 <head> 中注入此指令碼以防止主題閃爍。Astro也用其 is:inline 指令碼指令處理得很好。
深色模式表面高度
Material Design正確的一件事:在深色模式中,升高的表面應該稍微淡一些,而不是更暗。這模仿了陰影的工作方式——在黑暗的房間中,更近的表面捕捉更多環境光。
[data-theme="dark"] {
--color-surface-dim: oklch(12% 0.01 250);
--color-surface: oklch(15% 0.01 250);
--color-surface-bright: oklch(20% 0.01 250);
--color-surface-container-low: oklch(17% 0.01 250);
--color-surface-container: oklch(19% 0.01 250);
--color-surface-container-high: oklch(22% 0.01 250);
}
這些微小的亮度增量(OKLCH中的2-3%)創建了微妙但有效的高度層次。
無障礙和WCAG對比度合規性
這是許多色彩系統事後補丁問題而不是預防問題的部分。讓我們預防它們。
WCAG 2.2和3.0對比度要求
WCAG 2.2(目前的標準)使用對比度比公式:
| 級別 | 正常文字(<24px) | 大型文字(≥24px / 18.66px粗體) | UI元件 |
|---|---|---|---|
| AA | 4.5:1 | 3:1 | 3:1 |
| AAA | 7:1 | 4.5:1 | N/A |
WCAG 3.0(仍在草稿中,但與未來相關)使用APCA(進階感知對比算法),這在感知上更準確。APCA測量對比度的方式不同——它考慮極性(深色上的淺色文字與淡色上的深色文字)和文字大小更精細。
對於2026年,我建議至少目標WCAG 2.2 AA,以APCA作為補充檢查。
將對比度烘烤到您的令牌對中
最有效的方法:每個語義前景令牌應該在與其背景令牌的關係中定義,並具有保證的對比度。
以下是我在建置步驟中驗證這一點的方式:
// contrast-check.mjs——在您的CI/CD管道中執行此操作
import { wcagContrast } from 'culori';
const pairs = [
{ fg: 'on-primary', bg: 'primary', minRatio: 4.5 },
{ fg: 'on-primary-container', bg: 'primary-container', minRatio: 4.5 },
{ fg: 'on-surface', bg: 'surface', minRatio: 4.5 },
{ fg: 'on-surface', bg: 'surface-variant', minRatio: 3.0 },
{ fg: 'on-error', bg: 'error', minRatio: 4.5 },
];
function validateContrast(tokens, theme) {
const failures = [];
for (const pair of pairs) {
const ratio = wcagContrast(tokens[pair.fg], tokens[pair.bg]);
if (ratio < pair.minRatio) {
failures.push(
`[${theme}] ${pair.fg}/${pair.bg}: ${ratio.toFixed(2)} (need ${pair.minRatio})`
);
}
}
return failures;
}
對您的亮色和深色主題令牌執行此操作。如果任何對失敗,建置失敗。沒有例外。我見過團隊「暫時」跳過此檢查,最終導致運送無法通過無障礙的介面長達數月。
`forced-colors`媒體查詢
別忘了Windows高對比度模式。它會完全覆寫您的顏色,您需要確保您的UI仍然可用:
@media (forced-colors: active) {
.button {
border: 2px solid ButtonText;
}
.icon {
forced-color-adjust: auto; /* 讓系統處理它 */
}
}
將其全部組合在一起:完整的系統
以下是我在迭代許多專案後確定的實用工作流程:
從1-3個品牌顏色開始。 定義您的主要、次要和可選的三級色調。
在OKLCH中生成色調調色盤。 使用 Huetone 等工具或構建一個簡單的指令碼,每個色調生成11個亮度步驟。
定義語義令牌對映。 將色調對映到語義角色,用於亮色和深色主題。使用M3色調對映表作為起點。
驗證所有對比度對。 每個
on-*令牌必須針對其相應的表面滿足WCAG 2.2 AA。匯出為W3C設計令牌JSON。 這是您的單一真理來源。
編譯為CSS自訂屬性,使用Style Dictionary或Cobalt UI。
實現主題切換,使用
data-theme屬性和prefers-color-scheme。將對比度驗證新增到CI。 永遠不要在沒有自動對比度檢查的情況下運送顏色變更。
示例Tailwind v4整合
如果您使用Tailwind CSS v4(使用CSS優先設定),以下是設計令牌的轉化方式:
/* tokens.css——由您的Tailwind設定匯入 */
@theme {
--color-primary: var(--color-primary);
--color-on-primary: var(--color-on-primary);
--color-surface: var(--color-surface);
--color-on-surface: var(--color-on-surface);
--color-error: var(--color-error);
}
然後在您的標記中:
<button class="bg-primary text-on-primary hover:bg-primary/85">
開始使用
</button>
乾淨、語義化,並自動主題感知。
常見問題
網頁設計色彩系統應該有多少種顏色? 結構良好的系統通常有3-5個關鍵顏色角色(主要、次要、三級、錯誤、中性),每個都有11-13個色調變體。這大約是40-65個基本令牌。但您只會公開元件實際引用的15-25個語義令牌。更多並不一定更好——清晰性最重要。
設計令牌和CSS變數之間有什麼區別? 設計令牌是平臺無關的設計決策,以資料形式存儲(通常為JSON)。CSS自訂屬性是這些令牌的一種輸出格式。相同的令牌檔案也可能生成Swift色彩常數、Kotlin值或Figma樣式。令牌是真理的來源;CSS變數是該真理的一種表現。
OKLCH在2026年是否為生產就緒?
是的。截至2026年初,全球瀏覽器支援大約96%,涵蓋所有常綠瀏覽器。對於剩餘的約4%(主要是較舊的嵌入式瀏覽器),使用 @supports 提供十六進位後備或像 postcss-oklab-function 這樣的PostCSS外掛程式。風險最小。
我如何確保我的色彩系統符合WCAG無障礙標準? 將對比度檢查內置於設計令牌管道中。每個前景/背景對都應根據WCAG 2.2 AA最小值(正常文字4.5:1、大型文字和UI元件3:1)進行驗證。Culori JavaScript庫、Figma的Stark或axe瀏覽器擴展等工具可以提供幫助。關鍵是自動化這些檢查,以便它們在每次變更時執行。
我應該直接使用Material Design 3的色彩系統嗎? 您不必採用Material Design作為您的設計系統來受益於其色彩架構。色調調色盤概念、語義令牌結構和亮色/深色重新對映策略無論您的視覺風格如何都很優秀。挑選架構;應用您自己的品牌美學。
如何使用CSS自訂屬性處理深色模式?
在根元素上使用 data-theme 屬性,結合 prefers-color-scheme 媒體查詢。語義CSS自訂屬性針對每個主題重新對映到不同的基本值。元件永遠不會改變——只有令牌值移動。這是最乾淨的方法,避免複製元件樣式。
我應該在2026年使用什麼工具來管理設計令牌? Tokens Studio for Figma處理設計方面。Style Dictionary 4.x或Cobalt UI處理構建/轉換步驟。將您的令牌JSON存儲在版本控制中,與您的程式碼一起。對於較大的團隊,考慮專用設計令牌管理平臺,如Specify或Supernova,但開源堆棧對大多數團隊都很有效。
WCAG 2.2和APCA對比方法之間有什麼區別? WCAG 2.2使用簡單的亮度對比度比率(例如4.5:1)。APCA(為WCAG 3.0提議)在感知上更準確——它考慮亮色文字在深色背景上的對比度與深色文字在亮色背景上的對比度不同,並按字型大小和粗細更精細地調整需求。在2026年,為合規性目標WCAG 2.2 AA,並使用APCA作為額外的品質檢查。