- Dùng hàm contrast-color() cho phép trình duyệt tự động chọn màu chữ đen hoặc trắng phù hợp với nhiều màu nền khác nhau như nút bấm
- Ngay cả trong các dự án quy mô lớn, việc duy trì khả năng đọc của văn bản trở nên dễ dàng hơn và nâng cao hiệu quả bảo trì
- Hiện tại trong Safari Technology Preview, thuật toán chính thức của WCAG 2 đang được sử dụng, nhưng có thể lệch với cách con người thực sự cảm nhận
- Việc đưa thuật toán APCA thế hệ tiếp theo vào đang được thảo luận trong quá trình phát triển WCAG 3, hứa hẹn đánh giá độ tương phản độ sáng tốt hơn
- Ngoài tương phản đen trắng đơn giản, trong tương lai dự kiến sẽ bổ sung nhiều tùy chọn màu sắc và tính năng cải thiện khả năng truy cập
Tổng quan và bối cảnh ra đời của contrast-color()
- Trong các thiết kế dùng nhiều màu nền khác nhau cho nút bấm hoặc thành phần giao diện, màu chữ (màu văn bản) có dễ đọc hay không là yếu tố quan trọng
- Trước đây, lập trình viên phải tự tay phối hợp màu nền và màu chữ cho từng trường hợp, nhưng ở các dự án lớn, điều này làm tăng độ phức tạp trong quản lý và nguy cơ phát sinh lỗi
- Với hàm CSS contrast-color(), nhà phát triển chỉ cần chỉ định màu nền, còn trình duyệt sẽ tự động chọn màu chữ đen hoặc trắng có độ tương phản cao hơn
- Cách làm này giúp nâng cao đáng kể hiệu quả bảo trì và công việc thiết kế
- Có thể khai báo đơn giản như
color: contrast-color(màu_sắc);
Ví dụ cách dùng contrast-color()
- Gán màu mong muốn cho biến màu nền của nút, rồi để
contrast-color() tự động chọn một trong hai màu đen hoặc trắng tương phản nhất cho màu chữ
- Vì chỉ cần quản lý một màu tại một thời điểm, việc thay đổi chính sách thiết kế hoặc hỗ trợ dark/light mode cũng trở nên dễ maintenance hơn
button {
background-color: var(--button-color);
color: contrast-color(var(--button-color));
}
- Khi tận dụng Relative Color Syntax, bạn cũng có thể quản lý nhất quán màu nền và màu chữ ở trạng thái hover
Các điểm cần lưu ý về khả năng truy cập và giải thích thuật toán
- Việc dùng
contrast-color() không tự động giải quyết mọi vấn đề khả năng truy cập (Accessibility)
- Với một số màu nền có độ sáng trung bình, cả đen lẫn trắng đều có thể không đạt ngưỡng yêu cầu
- Thuật toán WCAG 2 hiện được dùng trong Safari Technology Preview là tiêu chuẩn chính thức về khả năng truy cập web
- Thuật toán này chọn dựa trên tỷ lệ tương phản, nhưng đôi khi cho ra kết quả không khớp với độ tương phản độ sáng mà mắt người thực sự cảm nhận
- Ví dụ, với nền xanh dương #317CFF, hệ thống có thể tính toán máy móc rằng màu đen có độ tương phản cao hơn, nhưng trên thực tế màu trắng lại dễ đọc hơn
- Trước những phê bình và nhu cầu cải tiến đó, trong tiêu chuẩn khả năng truy cập thế hệ tiếp theo (WCAG 3) đang có thảo luận về việc đưa vào APCA (Accessible Perceptual Contrast Algorithm) tốt hơn
- APCA tính toán độ tương phản màu sắc có phản ánh đặc tính nhận thức của con người, nên bảo đảm khả năng đọc thực tế tốt hơn
Đảm bảo đủ độ tương phản trong môi trường thực tế
- Có thể dùng media query
@media (prefers-contrast: more) của CSS để áp dụng thêm các kiểu hiển thị tương phản cao theo tùy chọn khả năng truy cập của người dùng
@media (prefers-contrast: more) {
/* định nghĩa kiểu có độ tương phản cao hơn */
}
- Chẳng hạn, nếu màu chủ đạo của thương hiệu là xanh lá sáng như #2DAD4E, thì trong tương lai dù
contrast-color() chọn màu trắng, độ tương phản vẫn có thể chưa đủ với cỡ chữ nhỏ
- Khi áp dụng thuật toán APCA, bạn có thể tham chiếu chi tiết ngưỡng tương phản tối thiểu cần thiết theo cỡ chữ và độ đậm, từ đó hỗ trợ quyết định thiết kế trong thực tế
- Thực tế, với chữ 24px/độ đậm 400 thì dùng màu trắng là phù hợp, nhưng với FONT mảnh hơn hoặc chữ nhỏ hơn, nên dùng màu nền đậm hơn
- Đội ngũ thiết kế có thể dễ dàng quản lý bảng màu theo biến cho từng điều kiện như light/dark mode, tùy chọn prefers-contrast, v.v.
--button-color: #2DAD4E;
@media (prefers-contrast: more) {
@media (prefers-color-scheme: light) {
--button-color: #419543;
}
@media (prefers-color-scheme: dark) {
--button-color: #77CA8B;
}
}
button {
background-color: var(--button-color);
color: contrast-color(var(--button-color));
font-size: 1.5rem;
font-weight: 500;
}
- Về cốt lõi, nhờ
contrast-color(), bạn chỉ cần quản lý màu xoay quanh màu nền, còn cặp tương phản màu chữ sẽ do trình duyệt tự động tạo ra
Vượt ra ngoài màu đen và trắng
contrast-color() hiện tại chỉ chọn giữa 2 màu đen và trắng, nhưng ở phiên bản ban đầu, nó từng có thể chọn trong nhiều màu
- CSS Working Group trước mắt cung cấp phiên bản đơn giản này (chỉ chọn đen/trắng) để đảm bảo tương thích với các thay đổi thuật toán trong tương lai, đồng thời cũng có kế hoạch mở rộng thêm tùy chọn màu do người dùng chỉ định và khả năng đặt ngưỡng tương phản tối thiểu mong muốn
- Với các nhu cầu đơn giản, hiện tại nó đã đủ hữu ích
- Hàm này có thể được áp dụng đa dạng không chỉ cho màu nền mà còn cho viền và các yếu tố thị giác khác
Kết luận và thông tin tham khảo
- Sau khi phản ánh tiêu chuẩn khả năng truy cập thế hệ tiếp theo,
contrast-color() sẽ thay thuật toán để hỗ trợ tự động chọn độ tương phản tốt hơn
- Cho đến lúc đó, nó đặc biệt hữu ích khi màu nền chính đủ sáng hoặc đủ tối một cách rõ ràng
- Có thể áp dụng rộng rãi không chỉ cho văn bản mà còn cho nhiều thành phần UI khác nhau
- Việc tiếp tục quan tâm đến các thuật toán khả năng truy cập mới như APCA (Accessible Perceptual Contrast Algorithm) là điều nên làm
Tài liệu tham khảo
- Có thể xem nhiều ví dụ và tiêu chí đánh giá trong tài liệu chính thức của APCA và APCA Contrast Calculator
- Việc thảo luận tiêu chuẩn hóa liên quan đến hàm contrast-color của CSSWG vẫn đang tiếp diễn
- Có thể chia sẻ ý kiến và tham gia phản hồi qua WebKit hoặc các cộng đồng liên quan
1 bình luận
Ý kiến Hacker News
Tôi đang phát triển một công cụ tạo bảng màu sao cho các cặp màu ngay từ giai đoạn thiết kế đã có tỷ lệ tương phản WCAG/APCA đơn giản và dễ dự đoán. Trang https://www.inclusivecolors.com/ có nhiều tính năng hơn trên desktop. Một cách là tạo các mẫu màu theo cấp từ 100 (màu sáng) đến 900 (màu tối), rồi điều chỉnh độ sáng để, ví dụ, màu cấp 700 có độ tương phản rõ ràng với cấp 100, cấp 800 với cấp 200. Như vậy có thể biết rằng các tổ hợp như red-700 vs gray-100, green-800 vs yellow-200 sẽ chắc chắn có tương phản tốt mà không cần kiểm tra độ sáng. Trong menu Contrast, có thể khám phá việc thuật toán APCA nghiêm ngặt hơn WCAG đến mức nào, đặc biệt là với chữ tối trên nền sáng. Đó là lý do không nên dùng WCAG cho giao diện tối. Trong menu Examples, nếu xem các ví dụ bảng màu Tailwind và IBM Carbon, sẽ thấy mỗi cấp thay đổi độ bão hòa và sắc độ (Hue) một cách phi tuyến, nên việc chỉ chọn màu đen hoặc trắng để có tương phản tối ưu thì dễ, nhưng với các bảng màu coi trọng branding thì đây là bài toán phức tạp hơn nhiều, không thể chỉ điều chỉnh độ sáng đơn thuần để tạo màu.
Có một cách làm điều tương tự bằng
lchNguồn: https://til.jakelazaroff.com/css/…
LCH cũng hay, nhưng OKLCH còn tốt hơn. https://evilmartians.com/chronicles/oklch-in-css-why-quit-rgb-hsl/… Bài viết này đã khiến tôi thay đổi hoàn toàn cách nghĩ. Đây thực sự là một công cụ tuyệt vời. Điều đáng ngạc nhiên là trong số bạn bè làm thiết kế của tôi, không ai biết đến OKLCH. Cách này giải quyết được rất nhiều vấn đề.
Đây là lần đầu tôi thấy một hàm CSS có thể nhận tham số theo kiểu callback như vậy. Khái niệm này thật sự rất thú vị. Không biết ngoài
lchra còn hàm nào theo phong cách này nữa không.Lea Verou có một bài viết hay về một cách lách tương tự. https://lea.verou.me/blog/2024/contrast-color/
Bài này là phần tổng quan rất tốt về ưu và nhược điểm của việc tự động chọn độ tương phản. Nếu bạn làm một website đơn giản thì cách này dễ và gọn, đồng thời vẫn cho độ tương phản đúng.
Nhưng nếu cần tuân thủ WCAG ở quy mô lớn thì nên tránh, và bạn sẽ cần một tầng token định dạng mang tính ngữ nghĩa thực sự. Semantic token giúp tăng tốc phát triển, đồng thời có thể đảm bảo độ tương phản nhìn đẹp hơn là chỉ chuyển giữa đen/trắng. Điểm hay của tầng semantic token là việc tạo theme rất dễ, nên có thể thêm theme sáng/tối gần như không tốn thêm chi phí. Nếu màu thương hiệu không đạt WCAG2, có thể tạo riêng một theme cho WCAG2/APCA để vừa đạt compliance vừa cho tương phản tốt hơn.
Tôi phụ trách luồng variables/tokens trong Figma, và cũng từng tham gia triển khai dark mode của Figma và Atlassian. Nếu ai có câu hỏi về token, theme, hay màu cho accessibility thì cứ hỏi.
Tôi muốn hiểu cụ thể semantic token là gì. Chính vì có nhu cầu như thế này nên trong một dự án lớn tôi từng dùng CSS-in-JS để tính màu tương đối và màu tương phản. Tôi hy vọng kỹ thuật này sẽ sớm được áp dụng rộng rãi.
2/3 đoạn cuối quá dài dòng nên hơi có vẻ lên mặt. Trên website/app của công ty thì việc dựa vào các hàm như thế này khá rủi ro vì màu kết quả khó dự đoán. Chỉ cần WebKit sửa bug là kết quả màu có thể đổi theo.
Tôi vẫn thấy khó đồng ý rằng việc để màu tương phản được quyết định theo đánh giá của nhà phát triển trình duyệt là lúc nào cũng đúng hoặc có thể dự đoán được. Không biết rồi có một tiêu chuẩn xác định để mọi trình duyệt đều cho ra cùng kết quả hay không. Hàm này thực ra cho tôi cảm giác giống một công cụ hỗ trợ đội UX ở giai đoạn thiết kế hơn.
Theo bài viết thì tiêu chuẩn có nói rõ cách tính toán.
Từ "chọn" (
choose) nghe có vẻ mơ hồ. Thực ra thuật toán là thứ tạo ra màu.Nếu bỏ qua vài chỗ sai hoặc dễ gây nhầm lẫn trong công thức APCA ví dụ ở link, thì nó hoạt động chính xác 100%. Để đạt tính nhất quán tuyệt đối, khi cả hai màu ứng viên (cả màu sáng hơn lẫn tối hơn) đều được chấp nhận, chỉ cần chọn theo độ sáng L* của màu nền, ví dụ nếu L* từ 60 trở lên thì chọn phương án sáng hơn. Như vậy sẽ nhất quán 100%.
Về chuyện trong các dự án lớn khó mà để ý xem nút có bị đổi sang màu khó nhìn hay không, ví dụ nền tối mà chữ đen, tôi lại nghĩ tại sao không đơn giản là kiểm tra từng nút trước khi phát hành. Hoặc đặt quy định chung cho cả team rằng nút tối thì tuyệt đối không dùng chữ đen. Sự khác biệt giữa tương phản nhận thức và tương phản toán học khá thú vị. Tôi định áp dụng điều đó vào workflow của mình.
Trên thực tế có thể kiểm tra toàn bộ nút, nhưng nếu làm vậy thì giai đoạn kiểm thử hồi quy trước phát hành có thể kéo dài hàng tuần, thậm chí hàng tháng. Với dự án lớn thì rất dễ có tới hàng nghìn nút, và nhiều nút chỉ xuất hiện trong những tổ hợp tùy chọn hoặc workflow cụ thể.
Có thể tham khảo APCA để tính độ tương phản dựa trên nhận thức.
Tôi từng có kinh nghiệm tạo style cho button vào thời hệ màu hệ thống còn phổ biến. Nhìn thì đẹp, nhưng không thể biết tỷ lệ tương phản sẽ ra sao, nên có người đã viết JavaScript dùng
getComputedStyleđể tính giúp. Nếu tương phản kém thì dùng phương án màu thứ hai, hoặc nếu bất khả kháng thì thêmtext-shadowđể tăng hiệu ứng tương phản quanh chữ. Tôi không còn nhớ cách tính, nhưng có vẻ chỉ cần lấy trung bình 3 giá trị RGB để so sánh là được. Với màu xanh lam thì giá trị trung bình sẽ thấp hơn, nên có thể ưu tiên chữ trắng hơn.Ít nhất thì sẽ rất tốt nếu có gợi ý màu phù hợp cho từng pseudo-class như
active,focus,hover,link,visitedtrong theme sáng/tối. Material UI còn thêm cả trạng tháidisabled,before,afternữa.Trước đây tôi từng làm một video hướng dẫn về cách chọn chữ đen/trắng dựa trên màu nền. Cách của tôi khá đơn giản: chuyển màu sang thang xám rồi quyết định nên dùng đen hay trắng. Làm khá vui. Kỹ năng làm video của tôi thì không tốt lắm.
https://youtu.be/tUJvE4xfTgo?si=vFlegFA_7lzijfSR (lưu ý: tiếng Bồ Đào Nha)
https://news.ycombinator.com/item?id=44015990
Video trông cũng ổn. Phần code có vẻ tốt, nhưng tôi không biết tiếng Bồ Đào Nha nên không đánh giá được nội dung.
Tôi tự chọn toàn bộ bảng màu, nên tôi không hiểu tại sao việc chọn màu chữ tương phản cho button lại dễ hơn việc ngay từ đầu tự chọn luôn. Tính năng này có vẻ chỉ cần trong một tình huống rất cực đoan: màu nền thì được chọn ngẫu nhiên đủ kiểu, còn màu foreground, tức màu chữ trên button, thì lại không thể tự chọn. Trong khi vấn đề thực sự khó là chữ trên ảnh hoặc trên nhiều loại nền khác nhau sao cho lúc nào cũng dễ đọc, mà tính năng này hoàn toàn không xử lý được. Vì thế nó chỉ có ích trong những tình huống hạn chế như vậy, vậy mà còn tạo hẳn một động từ mới cho nó, chức năng thì chỉ dừng ở chọn đen/trắng, lại dùng thuật toán tương phản tệ nhất là WCAG 2. Thật quá đỉnh.
Hơi tiếc khi có góc nhìn bác bỏ ngay một công cụ chỉ vì bản thân chưa từng gặp tình huống cần dùng. Nhiều website cho phép người dùng tự chọn màu, hoặc trích màu từ nội dung tải lên. Với những trang quan tâm đến accessibility, những trường hợp như vậy bắt buộc phải có tính toán tương phản tự động. Nếu CSS có sẵn tính năng như thế này, việc đáp ứng accessibility cơ bản sẽ dễ hơn nhiều. Tất nhiên điều đó cũng không hề cản trở những lập trình viên muốn tạo trải nghiệm nâng cao hơn. Nếu có khả năng tùy biến nhiều hơn như gói contrast-color trên npm thì còn tốt hơn, nhưng trong blog có nói rõ đây mới chỉ là bước đầu với thuật toán chọn đen/trắng và đã có kế hoạch cải tiến sau này.
Ví dụ: https://coolors.co/8fbfe0-7c77b9-1d8a99-0bc9cd-14fff7
Về nhận xét rằng thuật toán chọn tương phản không tốt, bài viết nói rất rõ là hiện tại nó theo thuật toán WCAG 2, và khi WCAG 3 được chuẩn hóa thì có thể dễ dàng chuyển sang thuật toán đó.
Tôi tự hỏi liệu có phương án thay thế ở build time cho tính năng này để áp dụng với SASS, Tailwind, v.v. hay không. Có lẽ sẽ mất thời gian để tính năng này được hỗ trợ rộng rãi, và tôi cũng lo không biết việc triển khai có đồng nhất trên các nền tảng hay không.