Mở đầu — vì sao bài này quan trọng
Nếu bạn từng mở một file Figma của designer khác và thấy bảng màu chỉ có vài ô rời rạc kiểu "Primary Blue", "Light Blue", "Dark Blue" — không đánh số, không quy luật — thì bạn đã hiểu vì sao bài này tồn tại. Cái bảng màu đó trông thì ổn cho một màn hình duy nhất, nhưng đến màn thứ mười, khi cần một sắc xanh nhạt hơn "Light Blue" một chút để làm nền hover, designer sẽ bốc đại một mã hex mới. Sau ba tháng, file có 47 sắc xanh khác nhau, không ai biết cái nào dùng ở đâu, và dev thì phát điên vì mỗi nút bấm lại có một màu viền hơi lệch.
Một color system (hệ thống màu) đúng nghĩa giải quyết chính xác vấn đề đó. Thay vì chọn màu theo cảm tính từng lần, bạn xây một bộ màu có cấu trúc — nơi mọi sắc độ đều được sinh ra theo quy luật, được đặt tên nhất quán, và có thể tái sử dụng hàng trăm lần mà không bao giờ lệch. Đây là nền móng. Nếu color system của bạn lỏng lẻo, thì button system, form system, dark mode — mọi thứ xây lên trên đều rung lắc.
Trong bài này, chúng ta sẽ dựng một color system hoàn chỉnh ngay trong Figma, đi từ primitive scale (bảng màu gốc, mỗi hue 11 stop) đến semantic layer (lớp ý nghĩa), và cuối cùng nối vào Figma Variables để dùng thật. Đây thuần là kỹ thuật xây bảng màu trong Figma — chúng ta không đi sâu lại vào lý thuyết HSL hay contrast (Bài 7 đã lo), cũng không bàn theming đa mode (Bài 15) hay dark mode (Bài 30). Ở đây, mục tiêu là: bạn ngồi xuống và ra được một color library dùng được cho một sản phẩm thật.
Khái niệm cốt lõi
Ba tầng của một color system
Một color system chững chạc luôn có ba tầng tách bạch:
- Primitive (màu gốc): Đây là kho màu thô.
blue.500,gray.100,red.700... Chúng chỉ là những mã hex có tên, hoàn toàn không mang ý nghĩa về công dụng.blue.500không "biết" nó là màu thương hiệu hay màu link — nó chỉ là một sắc xanh cụ thể.
- Semantic (màu ngữ nghĩa): Đây là lớp gán ý nghĩa.
color.brand.primarytrỏ tớiblue.500.color.text.bodytrỏ tớigray.900.color.border.defaulttrỏ tớigray.200. Component không bao giờ dùng trực tiếp primitive — chúng dùng semantic. Nhờ vậy, khi rebrand từ xanh sang tím, bạn chỉ đổi một chỗ:color.brand.primarytừblue.500sangviolet.500. Cả hệ thống đổi theo.
- Component (tùy chọn): Tầng chi tiết nhất, ví dụ
button.primary.background. Với sản phẩm nhỏ và vừa, bạn thường bỏ qua tầng này để tránh phình. Với design system lớn (kiểu của một ngân hàng), nó đáng có.
Primitive scale: vì sao 11 stop?
Với mỗi màu (hue), ta không tạo một mã duy nhất mà tạo cả một scale — một thang từ rất nhạt đến rất đậm. Chuẩn phổ biến nhất hiện nay (Tailwind CSS đã phổ cập nó) là 11 stop, đánh số:
blue.50 = #EFF6FF (nhạt nhất — dùng làm nền)
blue.100 = #DBEAFE
blue.200 = #BFDBFE
blue.300 = #93C5FD
blue.400 = #60A5FA
blue.500 = #3B82F6 (màu "chuẩn" của hue này)
blue.600 = #2563EB
blue.700 = #1D4ED8
blue.800 = #1E40AF
blue.900 = #1E3A8A
blue.950 = #172554 (đậm nhất — dùng cho text trên nền sáng)
Vì sao đánh số kiểu này thay vì 1–11? Vì con số phản ánh độ sáng (lightness): số càng nhỏ càng sáng. 50 gần trắng, 950 gần đen. Điều này giúp bạn đoán được công dụng ngay khi nhìn tên: các stop 50–200 dùng cho nền và vùng nhạt, 300–500 cho trạng thái và chi tiết, 600–950 cho text và viền đậm. Có thêm 50 và 950 ở hai đầu vì thực tế cần một sắc cực nhạt cho background và một sắc cực đậm cho text mà thang 100–900 không phủ tới.
Một quy tắc vàng để nhớ: trong hầu hết hệ thống, stop 500 hoặc 600 là màu "đại diện" của hue — cái bạn nghĩ tới khi nghe "màu xanh dương của brand".
Cần bao nhiêu hue?
Một product bình thường cần khoảng 5–7 họ màu:
- 1 màu brand (ví dụ blue) — nhận diện, nút chính, link.
- 1 màu trung tính (neutral/gray) — dùng nhiều nhất, cho text, nền, viền.
- 4 màu trạng thái: success (green), warning (amber/yellow), error (red), info (thường trùng blue hoặc dùng cyan).
- Đôi khi thêm 1–2 màu phụ (accent) cho biểu đồ, tag, minh họa.
Neutral không bao giờ là màu xám "chết"
Sai lầm tinh vi: nhiều người tạo dải gray bằng cách nội suy giữa trắng và đen thuần (chỉ giảm lightness). Kết quả là dải xám lạnh lẽo, vô hồn. Các hệ thống tốt đều pha một chút hue vào neutral — thường là hơi ngả xanh (cool gray) hoặc hơi ngả ấm (warm gray). Ví dụ gray của Tailwind ngả xanh nhẹ, còn "stone" ngả nâu ấm. Chọn tông neutral phù hợp với brand: brand xanh dương thì cool gray hợp hơn, brand cam/đỏ thì warm gray ấm áp hơn.
Tình huống thực tế
Ví dụ 1 — Startup fintech ở TP.HCM và cái bảng màu 47 sắc xanh
Một startup fintech ở Quận 1 (gọi là "PayNhanh") có 3 designer làm song song trên cùng một app ví điện tử. Ban đầu họ không có primitive scale — mỗi người tự chọn màu xanh theo mắt. Khi tôi được mời vào audit, tôi export toàn bộ style và đếm được 47 mã hex xanh khác nhau, trong đó có những cặp lệch nhau đúng 1–2 đơn vị (kiểu #3B82F6 và #3C81F5) mà mắt thường không phân biệt nổi.
Hậu quả cụ thể: đội dev đã hardcode 47 giá trị đó vào code. Khi ban lãnh đạo quyết định làm nhạt tông xanh đi cho "thân thiện hơn", việc lẽ ra sửa một biến đã biến thành rà soát 47 chỗ trong hai codebase (web + app), mất gần một sprint.
Cách sửa: chúng tôi dựng một primitive scale blue 11 stop duy nhất, ánh xạ 47 màu cũ về stop gần nhất (đa số gom về blue.500, blue.600, blue.50). Sau đó dựng semantic layer. Lần rebrand tiếp theo — đổi từ xanh sang xanh ngọc — chỉ mất 20 phút đổi giá trị trong Figma Variables và dev đổi đúng một token.
Bài học: Không phải cứ nhiều màu là linh hoạt. Ngược lại — một scale có kỷ luật mới cho bạn khả năng đổi cả hệ thống trong vài phút.
Ví dụ 2 — Team e-commerce và màu trạng thái thiếu stop
Một team làm sàn thương mại điện tử (gọi là "ChợViệt") có brand color đầy đủ, nhưng màu error chỉ có đúng một sắc red.500. Khi làm form thanh toán, họ cần: viền input lỗi (đỏ vừa), nền cảnh báo dưới input (đỏ rất nhạt), và chữ báo lỗi (đỏ đậm để đủ contrast trên nền trắng). Chỉ có một sắc đỏ thì không cách nào làm cả ba.
Designer đành lấy red.500 rồi chỉnh opacity xuống 10% cho nền — nhưng khi nền phía sau không phải màu trắng thuần (ví dụ input nằm trên card xám nhạt), màu đỏ trong suốt đó ngả sang hồng bẩn, trông rất thiếu chuyên nghiệp.
Cách sửa: dựng đủ scale red 11 stop. Từ đó: nền dùng red.50, viền dùng red.500, chữ dùng red.700. Tất cả đều là màu đặc (không opacity), nên trông sạch trên mọi nền.
Bài học: Mọi hue trạng thái đều cần đủ scale, không chỉ một sắc. Đặc biệt tránh dùng opacity để "chế" sắc nhạt — hãy dùng stop có sẵn.
Ví dụ 3 — Freelancer và cái bẫy "eyedropper từ Dribbble"
Một bạn designer freelance kể với tôi rằng bạn hay dùng công cụ eyedropper hút màu trực tiếp từ các shot đẹp trên Dribbble để "học màu". Vấn đề là bạn hút lẻ tẻ từng màu, ghép vào project mà không dựng scale. Kết quả: project có những màu rất đẹp nhưng rời rạc, và khi cần một sắc trung gian giữa hai màu đã hút, bạn bí.
Chúng tôi đổi cách làm: thay vì hút màu lẻ, bạn chỉ hút một màu neo (thường là stop 500 mong muốn), rồi dùng plugin để sinh cả scale 11 stop quanh nó. Giờ bạn có cả họ màu chứ không phải một điểm cô lập.
Bài học: Cảm hứng màu có thể đến từ bất kỳ đâu, nhưng phải "hệ thống hóa" nó thành scale trước khi đưa vào sản phẩm.
Hướng dẫn từng bước
Dưới đây là quy trình dựng color system trong Figma, từ trắng tay đến library dùng được.
Bước 1 — Chọn hue và xác định màu neo
Liệt kê 5–7 hue bạn cần (brand, neutral, success, warning, error, info). Với mỗi hue, xác định màu neo — thường là stop 500 hoặc 600. Đây là màu bạn đã có sẵn từ brand guideline, hoặc màu bạn ưng nhất từ cảm hứng. Ví dụ brand của bạn là #3B82F6 → đó là blue.500.
Bước 2 — Sinh 11 stop cho mỗi hue
Bạn có ba lựa chọn, từ nhanh đến kiểm soát cao:
- Nhanh nhất: Dùng bảng màu có sẵn của Tailwind CSS (tailwindcss.com/docs/colors). Chúng đã được cân chỉnh rất kỹ về mặt thị giác, contrast tốt, và dev cực quen. Với đa số dự án, đây là lựa chọn thông minh — đừng phát minh lại bánh xe.
- Cân bằng: Dùng plugin trong Figma. Các plugin phổ biến là "Color Shades", "Tailwind CSS Color Generator", hoặc trang UI Colors (uicolors.app) — bạn dán màu neo, nó sinh cả 11 stop. Copy vào Figma.
- Kiểm soát cao nhất: Tự sinh bằng không gian màu OKLCH (qua trang oklch.com) để giữ độ sáng cảm nhận đều nhau giữa các hue — kỹ thuật nâng cao, chỉ cần khi bạn muốn các hue "cân" nhau tuyệt đối.
Bước 3 — Tạo Variable Collection cho primitive
Trong Figma, mở panel Variables (menu bên phải, tab Variables, hoặc icon trong Local variables). Tạo một collection tên "Primitives". Trong đó tạo các variable kiểu Color, đặt tên theo cấu trúc phân cấp bằng dấu /:
blue/50, blue/100, ... blue/950
gray/50, gray/100, ... gray/950
red/50 ... red/950
Dấu / trong tên variable sẽ tự động tạo nhóm (group) trong Figma, giúp panel gọn gàng. Gán đúng mã hex cho từng variable. Đây là 66 màu gốc của bạn.
Mẹo: nếu bạn còn dùng Figma bản không có Variables (hoặc muốn tương thích cũ), có thể tạo bằng Color Styles với tên blue/500. Nhưng Variables là hướng đi hiện tại và mạnh hơn nhiều — ưu tiên nó.
Bước 4 — Tạo Variable Collection cho semantic
Tạo collection thứ hai tên "Semantic". Trong đó tạo các variable ý nghĩa, và trỏ chúng vào primitive (Figma gọi là aliasing — bấm vào ô giá trị, chọn một variable primitive thay vì nhập hex):
color/text/body → gray/900
color/text/muted → gray/500
color/text/inverse → gray/50
color/bg/canvas → gray/50
color/bg/surface → white (hoặc gray/50)
color/border/default → gray/200
color/brand/primary → blue/500
color/brand/hover → blue/600
color/feedback/success → green/500
color/feedback/error → red/500
color/feedback/warning → amber/500
Đây là bước quan trọng nhất. Từ giờ, khi thiết kế, bạn chỉ dùng semantic variable, không bao giờ chạm trực tiếp vào primitive.
Bước 5 — Kiểm tra contrast
Với mỗi cặp text/nền quan trọng (ví dụ color/text/body trên color/bg/canvas), kiểm tra tỷ lệ tương phản đạt tối thiểu 4.5:1 cho chữ thường. Dùng plugin Contrast hoặc Stark trong Figma. Nếu chưa đạt, chỉnh semantic trỏ sang stop đậm hơn (ví dụ text/body từ gray/700 sang gray/900). Chi tiết về ngưỡng và cách tính, xem lại Bài 7.
Bước 6 — Publish thành library
Nếu bạn dùng Figma bản team/organization, publish file này thành một library để mọi file khác trong dự án đều dùng chung. Mọi thay đổi color được đẩy đi qua library update — đó là cách bạn giữ một nguồn chân lý duy nhất (single source of truth).
Lỗi thường gặp & mẹo
Lỗi 1 — Dùng primitive trực tiếp trong thiết kế. Đây là lỗi phổ biến nhất. Bạn tiện tay gán blue/500 thẳng vào một nút bấm. Về mặt hình thức trông giống hệt semantic, nhưng bạn đã phá vỡ tầng ngữ nghĩa. Lần rebrand sau, bạn lại phải đi tìm từng chỗ gán primitive. Kỷ luật: component chỉ ăn semantic.
Lỗi 2 — Đặt tên semantic theo màu, không theo ý nghĩa. Đặt color/blue-button là sai — vì hôm nào nút thành màu tím thì tên hóa vô nghĩa. Đặt color/brand/primary mới đúng. Tên phải mô tả vai trò, không mô tả sắc.
Lỗi 3 — Quên 50 và 950. Nhiều người chỉ làm 100–900 rồi bí khi cần nền cực nhạt hoặc text cực đậm. Luôn làm đủ 11 stop.
Lỗi 4 — Lạm dụng opacity thay cho stop. Như ví dụ ChợViệt: dùng red.500 opacity 10% cho nền sẽ ngả bẩn trên nền không trắng. Dùng red.50 đặc thay thế.
Lỗi 5 — Neutral xám chết. Nội suy trắng–đen thuần cho ra dải xám vô hồn. Pha nhẹ hue vào neutral.
Mẹo — Số stop lẻ dễ nhớ. Quy ước ngầm: 50–100 là background, 200–300 là viền/divider, 400–600 là màu tương tác chính, 700–950 là text. Ghi quy ước này vào documentation để cả team đọc màu theo cùng một ngôn ngữ.
Mẹo — Đặt tên khớp với dev. Nếu team dev dùng Tailwind, đặt tên primitive khớp y hệt (blue-500 ↔ blue/500). Handoff mượt hẳn, không phải dịch qua lại.
Mẹo — Bắt đầu nhỏ. Đừng dựng 12 hue ngày đầu. Bắt đầu 5–6 hue, thêm khi thực sự cần. Color system phình vô tội vạ cũng tệ như color system rời rạc.
Bài tập thực hành
- Dựng primitive scale. Chọn một brand color bạn thích (một mã hex bất kỳ). Dùng UI Colors (uicolors.app) hoặc plugin trong Figma để sinh 11 stop. Tạo collection "Primitives" trong Figma Variables và nhập đủ. Làm cho ít nhất 3 hue: brand, gray (neutral), và red (error).
- Dựng semantic layer. Tạo collection "Semantic" với tối thiểu 8 variable:
text/body,text/muted,bg/canvas,bg/surface,border/default,brand/primary,brand/hover,feedback/error. Trỏ (alias) mỗi cái vào primitive phù hợp.
- Áp dụng thật. Vẽ một card đơn giản (tiêu đề + đoạn text + một nút bấm + một dòng báo lỗi giả) và tô màu hoàn toàn bằng semantic variable. Tự kiểm: nếu bạn còn phải nhập hex thủ công ở bất kỳ đâu, tức là scale của bạn còn thiếu stop — bổ sung.
- Test đổi brand. Đổi giá trị
brand/primarysang một hue khác (ví dụ từ blue sang violet — nhớ sinh scale violet trước). Quan sát cả card đổi màu chỉ với một thao tác. Đây là khoảnh khắc bạn "cảm" được sức mạnh của color system.
Tóm tắt
- Color system có ba tầng: primitive (màu gốc,
blue/500), semantic (ý nghĩa,brand/primary), và component (tùy chọn). Component chỉ dùng semantic. - Mỗi hue nên có 11 stop (50 → 950), đánh số theo độ sáng: nhỏ = sáng, lớn = tối. Có
50và950ở hai đầu cho nền cực nhạt và text cực đậm. - Một product cần khoảng 5–7 hue: brand, neutral, và bốn màu trạng thái. Neutral nên pha nhẹ hue, đừng để xám chết.
- Trong Figma, dựng hai Variable Collection: "Primitives" và "Semantic", dùng aliasing để nối semantic vào primitive. Đặt tên theo vai trò, không theo màu.
- Cách nhanh nhất để bắt đầu: mượn Tailwind palette, tùy chỉnh stop 500 cho khớp brand, rồi phủ semantic lên trên.
- Lợi ích lớn nhất: khi rebrand hay chỉnh sửa, bạn đổi một chỗ thay vì hàng chục chỗ — như bài học từ PayNhanh (47 màu → 1 token) đã cho thấy.