Menu
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Đang tải...

Bài 7 — Color Theory cho UI: HSL, Contrast, A11y

UI Design and Design Handoff Bài 7/60

Mở đầu — vì sao bài này quan trọng

Nếu bạn đã từng ngồi hàng giờ trong Figma, chỉnh tay từng mã màu HEX kiểu #3B82F6 rồi tự hỏi "làm sao để có một màu đậm hơn một chút, cùng tông này?" — thì bài học này chính là câu trả lời bạn đang tìm.

Ở các bài trước, chúng ta đã nói về nguyên tắc thị giác và color theory nền tảng. Bài 7 đi sâu vào một góc rất cụ thể nhưng cực kỳ thực dụng cho UI: cách nghĩ về màu bằng hệ HSL thay vì HEX, và cách đảm bảo màu bạn chọn không chỉ đẹp mà còn đọc được với mọi người — kể cả người khiếm thị màu, người lớn tuổi, hay người đang dùng điện thoại ngoài nắng.

Đây không phải kiến thức "nice to have". Trong nghề UI Designer thực thụ, ba thứ tách một designer nghiệp dư khỏi một designer chuyên nghiệp về màu là: (1) họ chọn màu có hệ thống, không cảm tính; (2) họ luôn kiểm tra contrast trước khi giao cho dev; (3) họ hiểu accessibility (a11y) là ràng buộc pháp lý và đạo đức, không phải trang trí. Xong bài này, bạn sẽ có đủ ba thứ đó.

Khái niệm cốt lõi

HSL — tư duy màu theo cách con người nghĩ

HSL là viết tắt của Hue, Saturation, Lightness — Sắc độ, Độ bão hòa, Độ sáng. Đây là cách mô tả màu gần với trực giác con người hơn nhiều so với HEX hay RGB.

  • H — Hue (0–360°): vị trí màu trên vòng tròn màu. 0° là đỏ, 120° là xanh lá, 240° là xanh dương, rồi quay về 360° lại là đỏ. Hue trả lời câu hỏi "đây là màu gì?".
  • S — Saturation (0–100%): độ tinh khiết, hay độ "rực" của màu. 0% là màu xám hoàn toàn (không còn màu sắc), 100% là màu rực rỡ nhất. Saturation trả lời "màu này đậm chất màu hay nhạt nhòa?".
  • L — Lightness (0–100%): độ sáng. 0% là đen tuyền, 100% là trắng tinh, 50% là màu "chuẩn" nhất của hue đó. Lightness trả lời "màu này sáng hay tối?".
Hãy so sánh trực tiếp. Trong HEX, #3B82F6 là một màu xanh dương. Muốn có phiên bản đậm hơn cùng tông, bạn phải... đoán, hoặc mở công cụ ngoài. Nhưng cùng màu đó trong HSL là hsl(217, 91%, 60%). Muốn đậm hơn? Chỉ cần giảm L xuống 45%: hsl(217, 91%, 45%). Giữ nguyên H và S, bạn có ngay một sắc thái đậm hơn mà vẫn cùng gia đình màu. Muốn màu nhạt cho background? Tăng L lên 95%. Đây chính là lý do câu ghi chú gốc của bài viết "Tăng L".

Vì sao HSL > HEX khi làm UI

HEX phù hợp để lưu trữ và truyền màu giữa máy tính, nhưng nó là "ngôn ngữ máy". Ba con số trong HEX (3B, 82, F6 tương ứng R, G, B) không cho bạn biết gì về mối quan hệ giữa các màu. Bạn không thể nhìn hai mã HEX và nói ngay chúng có cùng hue hay không.

HSL thì khác — nó là "ngôn ngữ con người". Khi build một hệ màu (color scale) cho UI, bạn thường cần 9–11 sắc thái của cùng một màu, đánh số từ 50 (nhạt nhất) đến 900 (đậm nhất), giống bảng màu của Tailwind CSS hay Material Design. Với HSL, việc này trở thành công thức:

  • Giữ Hue cố định (ví dụ 217° cho xanh dương thương hiệu).
  • Cho Lightness giảm dần theo từng bậc: 97%, 93%, 85%, 74%, 60%, 50%, 42%, 35%, 27%.
  • Điều chỉnh nhẹ Saturation để các bậc tối bớt bị "xám" (thường tăng nhẹ S ở đầu và cuối scale).
Chỉ với logic đó, bạn tạo được một dải màu nhất quán trong vài phút. Đây là nền tảng cho các bài sau về design tokens và building color system trong Figma.

Contrast — trái tim của accessibility màu

Contrast là tỷ lệ tương phản độ sáng giữa hai màu đặt cạnh nhau, điển hình là chữ trên nền. Nó được tính bằng công thức dựa trên độ sáng tương đối (relative luminance), cho ra một tỷ số từ 1:1 (hai màu giống hệt) đến 21:1 (đen tuyền trên trắng tinh).

Tiêu chuẩn quốc tế WCAG (Web Content Accessibility Guidelines) đặt ra các ngưỡng bắt buộc:

  • AA cho chữ thường (dưới ~18px hoặc dưới 14px in đậm): tối thiểu 4.5:1.
  • AA cho chữ lớn (từ ~24px, hoặc 18.66px in đậm): tối thiểu 3:1.
  • AAA (mức nghiêm ngặt): 7:1 cho chữ thường, 4.5:1 cho chữ lớn.
  • Thành phần giao diện & đồ họa (viền input, icon, biểu đồ): tối thiểu 3:1.
Điểm mấu chốt cần khắc cốt ghi tâm: contrast phụ thuộc gần như hoàn toàn vào Lightness, không phải Hue. Hai màu có hue rất khác nhau (đỏ và xanh lá) nhưng cùng L=50% sẽ có contrast rất thấp với nhau — đó là lý do chữ đỏ trên nền xanh lá luôn khó đọc. Khi cần tăng contrast, thứ bạn kéo là chỉ số L.

A11y — không chỉ là contrast

Accessibility màu (viết tắt a11y — "a", 11 chữ, "y") rộng hơn contrast. Hai vấn đề lớn khác:

  • Color blindness (mù màu): khoảng 8% nam giới và 0.5% nữ giới gặp một dạng khiếm khuyết màu, phổ biến nhất là deuteranopia (khó phân biệt đỏ–xanh lá). Nếu app của bạn báo "trạng thái tốt = xanh lá, lỗi = đỏ" mà chỉ dùng màu, một phần đáng kể người dùng sẽ không phân biệt được.
  • Không dùng màu làm kênh thông tin duy nhất: đây là tiêu chí WCAG 1.4.1. Màu phải luôn đi kèm một tín hiệu khác — icon, text label, gạch chân, hình dạng.

Tình huống thực tế

Ví dụ 1 — Ngân hàng số Việt và cái bẫy "nút xanh nhạt xinh xắn"

Một team thiết kế app ngân hàng số tại TP.HCM (giả định, kiểu như Timo hay Cake) chọn màu thương hiệu chủ đạo là xanh mint tươi hsl(160, 70%, 75%) vì nó "trẻ trung, nhẹ nhàng". Họ dùng chính màu đó cho nút "Xác nhận chuyển tiền" với chữ trắng bên trên.

Khi đo contrast: chữ trắng (L=100%) trên nền mint L=75% cho tỷ số chỉ 1.6:1 — thất bại thảm hại so với ngưỡng 4.5:1. Trên màn hình văn phòng thì tạm ổn, nhưng khi khách hàng đứng ngoài trời giữa trưa Sài Gòn, chữ "Xác nhận" gần như biến mất. Tỷ lệ khách bấm nhầm hoặc chần chừ ở bước cuối tăng, và bộ phận CSKH nhận phàn nàn.

Cách xử lý: giữ nguyên Hue 160° và Saturation để không mất bản sắc thương hiệu, nhưng kéo Lightness của nút xuống 38% — thành hsl(160, 70%, 38%). Contrast với chữ trắng vọt lên 5.2:1, đạt AA. Màu mint nhạt vẫn được giữ lại nhưng chuyển sang vai trò khác: làm background nhẹ cho card, nơi nó chỉ cần contrast 3:1 với viền.

Bài học: một màu thương hiệu có thể sống ở nhiều mức Lightness khác nhau cho các mục đích khác nhau. Đừng ép một sắc thái duy nhất làm mọi việc.

Ví dụ 2 — Dashboard bán hàng và 8% người dùng không thấy "đỏ"

Một startup SaaS ở Hà Nội làm dashboard quản lý đơn hàng cho các shop online. Họ hiển thị trạng thái đơn bằng chấm tròn màu: xanh lá = đã giao, đỏ = hoàn/hủy, vàng = đang xử lý. Đẹp, gọn, "dev làm nhanh".

Vấn đề nổ ra khi một khách hàng lớn — chủ chuỗi cửa hàng bị deuteranopia — phàn nàn rằng anh không tài nào phân biệt được đơn "đã giao" với đơn "đã hủy", vì với mắt anh, xanh lá và đỏ ở cùng độ sáng trông gần như một màu nâu xám giống nhau. Anh suýt gửi nhầm hàng bù cho một đơn thực ra đã giao thành công.

Cách xử lý: team giữ nguyên màu nhưng bổ sung kênh thông tin thứ hai theo WCAG 1.4.1: mỗi trạng thái nay có thêm icon riêng (dấu tích cho đã giao, dấu X cho hủy, đồng hồ cho đang xử lý) và một nhãn chữ ngắn. Họ cũng đẩy độ chênh Lightness giữa xanh (L=45%) và đỏ (L=52%) ra xa hơn để ngay cả khi hue bị "trộn", độ sáng vẫn giúp phân biệt.

Bài học: màu không bao giờ được là kênh thông tin duy nhất. Với 8% nam giới mù màu, "dùng màu để phân biệt trạng thái" là một quyết định loại trừ người dùng.

Ví dụ 3 — Sàn TMĐT Đông Nam Á và bảng màu HSL cứu deadline

Một designer làm cho một sàn thương mại điện tử khu vực (kiểu Shopee/Lazada) được giao build lại toàn bộ hệ màu cam thương hiệu, cần 10 sắc thái từ nhạt đến đậm cho buttons, badges, banner sale. Ban đầu cô làm thủ công bằng HEX, mỗi màu chọn bằng mắt — mất gần hai ngày và các bậc màu nhìn "gợn", chỗ nhảy nhiều chỗ nhảy ít.

Cách xử lý: cô chuyển sang tư duy HSL. Cố định Hue = 25° (cam đặc trưng), rồi cho Lightness chạy đều theo bậc — 96%, 90%, 80%, 68%, 56%, 48%, 40%, 32%, 25%, 18% — và tinh chỉnh Saturation cao ở giữa (95%), giảm nhẹ ở hai đầu để tránh màu bị chói hoặc bị đục. Toàn bộ scale hoàn thành trong buổi sáng, và mỗi bậc đều đã được cô đối chiếu contrast: bậc 500 trở lên đạt 4.5:1 với chữ trắng, các bậc nhạt đạt 4.5:1 với chữ đậm.

Bài học: HSL không chỉ dễ nghĩ hơn — nó biến việc dựng color scale từ nghệ thuật cảm tính thành quy trình lặp lại được, nhanh và nhất quán.

Hướng dẫn từng bước

Đây là quy trình bạn có thể áp dụng ngay để tạo và kiểm tra một màu chủ đạo cho UI:

  • Chọn Hue thương hiệu. Xác định một góc màu duy nhất, ví dụ 217° cho xanh dương. Đây là mỏ neo của toàn bộ hệ màu.
  • Đặt màu "base" ở Lightness ~50%. Ví dụ hsl(217, 91%, 50%). Đây là sắc thái đậm nhất, rực nhất — thường dùng cho nút chính (primary button).
  • Sinh các sắc thái bằng cách chỉnh Lightness. Giữ Hue, giữ (hoặc tinh chỉnh nhẹ) Saturation, thay đổi L: tăng L cho phiên bản nhạt (background, hover state nhạt), giảm L cho phiên bản đậm (text, border, pressed state).
  • Đo contrast cho từng cặp sử dụng thật. Với mỗi màu bạn định đặt chữ lên, kiểm tra tỷ số. Chữ nội dung cần đạt tối thiểu 4.5:1. Đừng đo chung chung — đo đúng cặp màu chữ/nền thực tế.
  • Kiểm tra với giả lập mù màu. Bật chế độ giả lập color blindness để xem hệ màu có còn phân biệt được không, đặc biệt với các cặp mang nghĩa (thành công/lỗi).
  • Bổ sung kênh phi màu. Với mọi thông tin đang truyền bằng màu, thêm icon, text hoặc hình dạng đi kèm.
  • Lưu thành biến (variable), không hardcode. Đặt tên có hệ thống (blue-500, blue-600...) để khi giao cho dev, họ dùng token chứ không copy mã HEX rời rạc.
Về công cụ: trong Figma, bạn có thể nhập màu trực tiếp ở chế độ HSL trong color picker (bấm vào ký hiệu để chuyển giữa HEX/RGB/HSL). Để đo contrast, dùng plugin miễn phí như Stark, Contrast, hoặc A11y - Color Contrast Checker. Ngoài Figma, trang WebAIM Contrast Checker là tiêu chuẩn vàng để kiểm tra nhanh.

Lỗi thường gặp & mẹo

Lỗi 1 — Đo contrast bằng cảm giác. "Nhìn cũng đọc được mà" là câu nói nguy hiểm nhất. Mắt bạn đã quen với thiết kế của mình. Luôn đo bằng con số, không bằng cảm nhận.

Lỗi 2 — Chỉ test trên nền trắng, màn hình xịn. Người dùng thật xem app trên điện thoại cũ, độ sáng thấp, ngoài nắng. Một màu đạt 3:1 trên MacBook có thể "chết" trên điện thoại giá rẻ. Nhắm vượt ngưỡng một chút để có biên an toàn.

Lỗi 3 — Quên trạng thái disabled và placeholder. Chữ placeholder trong ô input thường bị làm quá nhạt (hsl(0,0%,80%)) và trượt xuống dưới 4.5:1. Placeholder vẫn là nội dung cần đọc — vẫn phải đạt contrast.

Lỗi 4 — Dùng màu làm tín hiệu duy nhất. Nhắc lại vì nó quan trọng: link chỉ khác text thường ở màu là chưa đủ; cần gạch chân hoặc dấu hiệu khác.

Mẹo 1 — Nhớ "contrast nằm ở chữ L". Khi một cặp màu fail, đừng loay hoay đổi hue. Kéo Lightness của một trong hai màu ra xa hơn.

Mẹo 2 — Text đậm được "giảm giá" ngưỡng. Vì chữ lớn/đậm chỉ cần 3:1, đôi khi tăng font-weight hoặc size là cách hợp lệ để "cứu" một màu đẹp mà bạn không muốn đổi.

Mẹo 3 — Đừng để Saturation quá cao ở vùng tối. Màu đậm (L thấp) với S=100% thường trông "bẩn" và chói. Giảm nhẹ Saturation ở các bậc tối cho sạch mắt.

Mẹo 4 — Cẩn thận với màu vàng và cam trên nền trắng. Vàng có Lightness tự nhiên rất cao, nên gần như luôn fail contrast với chữ/nền trắng. Đừng dùng vàng cho chữ nhỏ trên nền sáng.

Bài tập thực hành

  • Dựng một color scale bằng HSL. Chọn một Hue bất kỳ cho thương hiệu giả định của bạn. Tạo 9 sắc thái (đặt tên 100 đến 900) bằng cách chỉ thay đổi Lightness, giữ Hue cố định. Ghi lại giá trị HSL của từng bậc.
  • Audit contrast một màn hình thật. Chụp màn hình một app bạn hay dùng (ví dụ MoMo, ZaloPay, hay Shopee). Dùng WebAIM Contrast Checker đo ít nhất 5 cặp màu chữ/nền: nút chính, text phụ, placeholder, badge, link. Đánh dấu cặp nào fail AA và ghi cần giảm/tăng L bao nhiêu để đạt.
  • Sửa một nút fail. Lấy màu nút mint hsl(160, 70%, 75%) với chữ trắng trong Ví dụ 1. Tự tay tìm giá trị Lightness thấp nhất mà tại đó cặp màu đạt đúng 4.5:1. So sánh với con số 38% trong bài.
  • Test mù màu. Bật giả lập deuteranopia (qua plugin Figma hoặc công cụ online) trên một dashboard có trạng thái màu. Liệt kê những chỗ thông tin bị "mất" khi hue bị trộn, và đề xuất kênh phi màu để bù.

Tóm tắt

  • HSL là ngôn ngữ màu của con người: Hue (màu gì), Saturation (rực bao nhiêu), Lightness (sáng bao nhiêu). Nó vượt trội HEX khi làm UI vì cho phép tạo sắc thái và color scale bằng cách chỉ chỉnh Lightness — giữ Hue cố định, tăng L cho nhạt, giảm L cho đậm.
  • Contrast là bắt buộc, không phải tùy chọn: WCAG yêu cầu tối thiểu 4.5:1 cho chữ thường, 3:1 cho chữ lớn và thành phần giao diện. Contrast phụ thuộc chủ yếu vào Lightness.
  • A11y rộng hơn contrast: khoảng 8% nam giới mù màu; không bao giờ dùng màu làm kênh thông tin duy nhất — luôn kèm icon, chữ hoặc hình dạng.
  • Quy trình chuyên nghiệp: chọn Hue, dựng scale bằng L, đo contrast từng cặp thật, giả lập mù màu, bổ sung kênh phi màu, lưu thành biến có tên hệ thống.
Nắm chắc bài này, bạn không chỉ chọn màu đẹp hơn — bạn chọn màu mà mọi người dùng đều đọc được, và giao cho dev một hệ màu có logic thay vì một mớ mã HEX rời rạc.