Một badge nhận màu nền từ dữ liệu thường kéo theo danh sách màu chữ viết tay: nền xanh dùng trắng, nền vàng dùng đen, rồi thêm một màu mới là phải kiểm tra lại. CSS contrast-color() cho trình duyệt nhận màu nền và tự chọn đen hoặc trắng có độ tương phản cao hơn.

Chrome 151 đã nhận cú pháp này mặc định, nhưng mức hỗ trợ giữa các trình duyệt vẫn chưa đủ để thả vào production rồi quên. Giá trị thực tế của nó nằm ở việc giảm các cặp màu thủ công, miễn là có fallback và vẫn đo lại những màu sát ngưỡng.

Cú pháp chỉ cần một màu đầu vào

.badge {
    --badge-bg: #2563eb;
    background: var(--badge-bg);
    color: #111827;
    color: contrast-color(var(--badge-bg));
}

Dòng màu tĩnh đứng trước là fallback. Trình duyệt chưa hiểu hàm mới sẽ bỏ declaration sau và giữ #111827. Trình duyệt hỗ trợ sẽ trả về black hoặc white. Theo phần tổng hợp Web UI 2026 của Chrome, lựa chọn dựa trên màu có độ tương phản cao hơn với input.

Bốn swatch xanh vàng đỏ và bạc hà dùng chữ đen hoặc trắng
Fixture trên Chrome 151: nền tối nhận trắng, nền sáng nhận đen.

Kết quả kiểm tra trên Chrome 151

Fixture dùng bốn nền #2563eb, #facc15, #dc2626#86efac. Chrome 151 nhận cú pháp ngay ở cấu hình mặc định: xanh lam và đỏ nhận chữ trắng; vàng và xanh bạc hà nhận chữ đen. Ở trình duyệt chưa hỗ trợ, declaration không hợp lệ bị bỏ qua và dòng fallback đứng trước tiếp tục có hiệu lực.

Điều đó giải quyết tốt các swatch, chip và nhãn trạng thái có bảng màu đã kiểm soát. Nó không thay thế một hệ thống accessibility hoàn chỉnh: ảnh nền, gradient, transparency và trạng thái hover vẫn cần thiết kế riêng. Hàm cũng chỉ trả hai màu đen/trắng, không tự chọn một màu chữ thuộc brand palette.

Hai dòng CSS minh họa màu fallback trước contrast-color
Đặt fallback trước để trình duyệt cũ bỏ qua declaration mới mà giao diện vẫn đọc được.

Đừng nhầm “tự động” với “đạt mọi trường hợp”

MDN hiện đánh dấu tính năng chưa phổ biến rộng; thuật toán và phạm vi đầu ra được mô tả tại CSS Color Module Level 6. Vì vậy nên đặt nó sau một màu fallback đã đạt WCAG, bọc phần thử nghiệm bằng progressive enhancement nếu cần, và thêm test visual cho danh sách màu thực tế của sản phẩm.

Trường hợpCách xử lý
Badge nền màu đặcDùng được khi có fallback và test tương phản
Gradient hoặc ảnh nềnKhông dùng một màu input để đại diện toàn bộ nền
Màu chữ theo thương hiệuGiữ token thiết kế thủ công
Trình duyệt production hiện tạiKiểm tra support matrix trước khi bật

Đưa vào design system thế nào cho đỡ rối?

Nên giới hạn API ở một utility hoặc component thay vì rải contrast-color() khắp stylesheet. Token nền đi vào component, màu chữ fallback được chọn từ trước, còn hàm mới là lớp tăng cường. Khi browser support thay đổi, chỉ một nơi cần cập nhật.

Bài đo focus indicator theo WCAG 2.2 có cách kiểm tra tương phản theo trạng thái, còn bài CSS field-sizing cho thấy cùng một nguyên tắc: tính năng mới nên cải thiện trải nghiệm nhưng không được làm hỏng fallback.

Cách kiểm tra trước khi đưa vào component thật

Đầu tiên, lấy toàn bộ token nền mà component đang chấp nhận thay vì thử vài màu đẹp mắt. Render mỗi token với kết quả của contrast-color(), màu fallback và trạng thái hover/focus. Sau đó đo contrast bằng công cụ tự động, nhưng vẫn xem bằng mắt ở mức zoom 200% để phát hiện viền, transparency hoặc phần tử chồng lên làm thay đổi nền thực tế.

Tiếp theo, thêm visual regression cho hai nhóm màu sáng và tối. Test không cần khóa từng pixel của cả trang; chỉ chụp vùng badge hoặc chip để khi thuật toán, browser hay token đổi, diff nói đúng chỗ cần xem. Với màu do người dùng nhập tự do, nên chuẩn hóa input và giới hạn alpha trước khi đưa vào custom property.

Fallback nên được chọn theo dữ liệu nào?

Một màu fallback duy nhất chỉ an toàn khi tập màu nền đã biết nghiêng rõ về sáng hoặc tối. Nếu component nhận cả hai nhóm trên trình duyệt cũ, hãy giữ bảng token có cặp foreground/background đã kiểm duyệt, hoặc sinh class từ phía server. contrast-color() khi đó giảm công bảo trì cho browser mới chứ không làm mất lớp tương thích cũ.

Đừng dùng JavaScript để dò support rồi đổi class sau khi trang đã vẽ nếu CSS declaration order đã giải quyết được. Cách đó dễ tạo chớp màu chữ và thêm một nhánh hydration không cần thiết. Chỉ dùng feature query hoặc JavaScript khi hành vi component, không chỉ màu sắc, thực sự khác theo mức hỗ trợ.

Kết luận

contrast-color() làm gọn bài toán chữ đen hay trắng trên nền màu đặc, đặc biệt với badge sinh từ dữ liệu. Hiện tại nó phù hợp để thử trong design system và fixture hơn là phụ thuộc hoàn toàn ở production. Giữ fallback trước, đo lại palette thật và đừng dùng nó cho ảnh hay gradient.