Tổng quan cơ bản về cách thiết lập một bảng phối màu linh động và có thể định cấu hình
Trong bài đăng này, tôi muốn chia sẻ suy nghĩ về cách quản lý nhiều bảng phối màu trong CSS. Dùng thử bản minh hoạ.
Nếu bạn thích xem video, thì đây là phiên bản video của bài đăng này trên YouTube:
Tổng quan
Chúng ta sẽ xây dựng một hệ thống màu sắc dễ tiếp cận bằng các thuộc tính tuỳ chỉnh và calc() để tạo một trang web thích ứng với lựa chọn ưu tiên của người dùng trong khi vẫn duy trì trải nghiệm soạn thảo tối thiểu. Chúng ta bắt đầu bằng một màu cơ bản của thương hiệu và xây dựng một hệ thống các biến thể từ màu đó: 2 màu văn bản, 4 màu vùng hiển thị và một bóng đổ phù hợp.
Hướng dẫn này bắt đầu bằng việc xác định trước tất cả các màu cho từng bảng phối màu. Chỉ đến cuối cùng, chúng mới được dùng để thay đổi trang.
Thương hiệu
Thường thì màu thương hiệu đã được thiết lập và được phân phối dưới dạng hex hoặc rgb. Thử thách GUI này có màu cơ bản của thương hiệu là #0af. Trước tiên, đối với hệ thống màu này, bạn cần chuyển đổi giá trị thập lục phân thành hsl.
* {
--brand: #0af;
--brand: hsl(200 100% 50%);
}
Để bật khái niệm làm tối hoặc làm sáng màu thương hiệu, chẳng hạn như 20%, bạn cần trích xuất 3 kênh của giá trị màu hsl vào các thuộc tính tuỳ chỉnh riêng, như sau:
* {
--brand-hue: 200;
--brand-saturation: 100%;
--brand-lightness: 50%;
}
CSS có thể thực hiện phép toán trên các thuộc tính màu đó, ví dụ: calc(var(--brand-lightness) -
20%) để giảm giá trị độ sáng xuống 20%. Đây là nền tảng để xây dựng một bảng phối màu vì CSS có thể giữ tất cả các màu trong cùng một họ sắc độ bằng cách điều chỉnh lượng độ bão hoà và độ sáng hsl.
Giao diện sáng
Mỗi biến thể màu sẽ được đánh dấu bằng sơ đồ phù hợp, trong trường hợp này, mỗi biến thể sẽ được thêm -light.

Thương hiệu
Bắt đầu bằng màu thương hiệu, màu này được tạo lại bằng cách bao bọc các thuộc tính tuỳ chỉnh --brand-hue, --brand-saturation và --brand-lightness bên trong dấu ngoặc đơn của hàm hsl () mà không cần tính toán:
* {
--brand-light: hsl(var(--brand-hue) var(--brand-saturation) var(--brand-lightness));
}
Màu văn bản
Tiếp theo, những yếu tố cần thiết của bảng phối màu cần có màu văn bản. Trong giao diện sáng, văn bản phải có màu rất tối. Lưu ý cách độ sáng của các màu sau đây thấp, dưới 50%.
* {
--text1-light: hsl(var(--brand-hue) var(--brand-saturation) 10%);
--text2-light: hsl(var(--brand-hue) 30% 30%);
}
--text1-light, vì độ sáng 10% rất tối nên hãy giữ độ bão hoà 100% để màu thương hiệu vẫn có thể xuất hiện trong màu xanh dương đậm.
--text2-light, màu này không tối bằng màu thứ nhất, điều này là tốt vì đây là màu phụ và màu này cũng ít bão hoà hơn nhiều.
Màu cho vùng hiển thị
Màu bề mặt là màu nền, đường viền và các bề mặt trang trí khác mà văn bản nằm trên hoặc bên trong. Trong giao diện sáng, đây là những màu sáng, trái ngược với màu văn bản là màu tối. Để tạo màu sáng bằng hsl, chúng ta sẽ sử dụng các giá trị phần trăm cao hơn trong giá trị độ sáng thứ ba. Chúng ta cũng sẽ giảm độ bão hoà để màu xám nhạt không bị pha quá nhiều màu.
* {
--surface1-light: hsl(var(--brand-hue) 25% 90%);
--surface2-light: hsl(var(--brand-hue) 20% 99%);
--surface3-light: hsl(var(--brand-hue) 20% 92%);
--surface4-light: hsl(var(--brand-hue) 20% 85%);
}
4 màu bề mặt được tạo ra vì màu trang trí có xu hướng cần nhiều biến thể hơn, cho các khoảnh khắc tương tác như :focus hoặc :hover hoặc để tạo giao diện của các lớp giấy. Trong những trường hợp này, bạn nên chuyển đổi --surface2-light khi di chuột thành --surface3-light, vì vậy, thao tác di chuột sẽ làm tăng độ tương phản (độ sáng từ 99% thành 92%; làm cho màu tối hơn).
Bóng
Bóng đổ trong một bảng phối màu là điều cần thiết, nhưng chúng sẽ thêm nét chân thực cho hiệu ứng và giúp hiệu ứng này nổi bật hơn so với bóng đổ dựa trên màu đen không thực tế. Để làm được điều này, màu của bóng sẽ sử dụng thuộc tính tuỳ chỉnh về sắc độ, hơi bão hoà với sắc độ nhưng vẫn rất tối. Về cơ bản, bạn sẽ tạo một bóng tối rất đậm và hơi xanh.
* {
--surface-shadow-light: var(--brand-hue) 10% 20%;
--shadow-strength-light: .02;
}
--surface-shadow-light không được bao bọc trong hàm hsl. Điều này là do giá trị --shadow-strength sẽ được kết hợp để tạo độ mờ và CSS cần các phần này để thực hiện tính toán. Chuyển đến phần bóng đổ bán kính để tìm hiểu thêm.
Tất cả màu sáng
Bạn không cần phải tìm hiểu cách tạo ra bất kỳ màu sáng nào, tất cả đều nằm ở một nơi trong CSS.
* {
--brand-light: hsl(var(--brand-hue) var(--brand-saturation) var(--brand-lightness));
--text1-light: hsl(var(--brand-hue) var(--brand-saturation) 10%);
--text2-light: hsl(var(--brand-hue) 30% 30%);
--surface1-light: hsl(var(--brand-hue) 25% 90%);
--surface2-light: hsl(var(--brand-hue) 20% 99%);
--surface3-light: hsl(var(--brand-hue) 20% 92%);
--surface4-light: hsl(var(--brand-hue) 20% 85%);
--surface-shadow-light: var(--brand-hue) 10% calc(var(--brand-lightness) / 5);
--shadow-strength-light: .02;
}
Giao diện tối
Hầu hết các thương hiệu đều không bắt đầu bằng một giao diện tối, mà là một biến thể của giao diện chính, thường là giao diện sáng hơn. Mặt khác, người dùng thường chọn chủ đề tối cho nhiều bối cảnh, chẳng hạn như vào ban đêm. Những yếu tố này khiến tôi phải lưu ý đến 2 điều khi thiết kế giao diện tối:
- Người dùng thường sẽ ở trong bóng tối khi sử dụng giao diện này, vì vậy, hãy thử nghiệm trong bóng tối.
- Màu sắc nên giảm độ bão hoà để không bị rung trên màn hình do quá đậm.

Thương hiệu
Giao diện sáng sử dụng 3 giá trị kênh màu hsl của thương hiệu mà không có sự thay đổi, còn giao diện tối thì không. Độ bão hoà giảm một nửa và độ sáng giảm 50% tương đối.
* {
--brand-dark: hsl(
var(--brand-hue)
calc(var(--brand-saturation) / 2)
calc(var(--brand-lightness) / 1.5)
);
}
Màu văn bản
Trong giao diện tối, màu văn bản phải là màu sáng. Các màu sau đây có giá trị cao về độ sáng, khiến chúng gần với màu trắng hơn.
* {
--text1-dark: hsl(var(--brand-hue) 15% 85%);
--text2-dark: hsl(var(--brand-hue) 5% 65%);
}
Màu cho vùng hiển thị
Trong giao diện tối, màu bề mặt phải là màu tối. Các màu sau có độ sáng và độ bão hoà thấp, trong đó vùng hiển thị thứ nhất có màu tối nhất ở mức 10%.
* {
--surface1-dark: hsl(var(--brand-hue) 10% 10%);
--surface2-dark: hsl(var(--brand-hue) 10% 15%);
--surface3-dark: hsl(var(--brand-hue) 5% 20%);
--surface4-dark: hsl(var(--brand-hue) 5% 25%);
}
Bóng
Trong giao diện tối, bạn rất khó nhìn thấy bóng. Điều này hợp lý vì khó làm tối một thứ đã khá tối. Đây là nơi --shadow-strength-dark trở nên cực kỳ hữu ích vì nó cho phép chúng ta làm tối bóng bằng cách thay đổi một biến.
* {
--surface-shadow-dark: var(--brand-hue) 50% 3%;
--shadow-strength-dark: .8;
}
Ngoài ra, hãy xem độ bão hoà của bóng đổ đó. Bạn có nhận thấy màu sắc khi nhìn vào giao diện không? Hãy thử giảm độ bão hoà trong công cụ dành cho nhà phát triển, bạn thích cách nào hơn?!
Tất cả các màu tối
* {
--brand-dark: hsl(var(--brand-hue) calc(var(--brand-saturation) / 2) calc(var(--brand-lightness) / 1.5));
--text1-dark: hsl(var(--brand-hue) 15% 85%);
--text2-dark: hsl(var(--brand-hue) 5% 65%);
--surface1-dark: hsl(var(--brand-hue) 10% 10%);
--surface2-dark: hsl(var(--brand-hue) 10% 15%);
--surface3-dark: hsl(var(--brand-hue) 5% 20%);
--surface4-dark: hsl(var(--brand-hue) 5% 25%);
--surface-shadow-dark: var(--brand-hue) 50% 3%;
--shadow-strength-dark: .8;
}
Giao diện mờ
Bảng phối màu này là sự kết hợp giữa độ sáng và độ rực màu. Phải có đủ độ bão hoà để vẫn nhìn thấy sắc độ, nhưng cũng chỉ nên vừa đủ để đạt được điểm tương phản vì dù sao thì màu này cũng có ý định là mờ và có độ tương phản thấp.

Thương hiệu
* {
--brand-dim: hsl(
var(--brand-hue)
calc(var(--brand-saturation) / 1.25)
calc(var(--brand-lightness) / 1.25)
);
}
Màu văn bản
* {
--text1-dim: hsl(var(--brand-hue) 15% 75%);
--text2-dim: hsl(var(--brand-hue) 10% 61%);
}
Màu cho vùng hiển thị
* {
--surface1-dim: hsl(var(--brand-hue) 10% 20%);
--surface2-dim: hsl(var(--brand-hue) 10% 25%);
--surface3-dim: hsl(var(--brand-hue) 5% 30%);
--surface4-dim: hsl(var(--brand-hue) 5% 35%);
}
Bóng
* {
--surface-shadow-dim: var(--brand-hue) 30% 13%;
--shadow-strength-dim: .2;
}
Làm mờ tất cả các màu
* {
--brand-dim: hsl(var(--brand-hue) calc(var(--brand-saturation) / 1.25) calc(var(--brand-lightness) / 1.25));
--text1-dim: hsl(var(--brand-hue) 15% 75%);
--text2-dim: hsl(var(--brand-hue) 10% 61%);
--surface1-dim: hsl(var(--brand-hue) 10% 20%);
--surface2-dim: hsl(var(--brand-hue) 10% 25%);
--surface3-dim: hsl(var(--brand-hue) 5% 30%);
--surface4-dim: hsl(var(--brand-hue) 5% 35%);
--surface-shadow-dim: var(--brand-hue) 30% 13%;
--shadow-strength-dim: .2;
}
Màu sắc dễ tiếp cận
Lưu ý rằng độ sáng thấp nhất trong bộ màu văn bản tối là 65% và độ sáng cao nhất trong các vùng tối là 25%. Đó là 40% khoảng sáng giữa chúng. Trong giao diện sáng, có 55% khoảng trống trong giao diện sáng. Việc duy trì sự khác biệt về độ sáng giữa văn bản và màu sắc của vùng hiển thị ở mức khoảng 40-50% có thể giúp duy trì tỷ lệ tương phản màu cao, đồng thời là một yếu tố tinh tế để điều chỉnh trong trường hợp điểm số thấp.
Tôi gọi đó là "bump bump til ya pass" (tạm dịch: tăng tăng cho đến khi vượt qua), tức là tương tác tăng giá trị độ sáng cho đến khi một công cụ cho thấy tôi đang vượt qua.
Mỗi giao diện được tạo trong thử thách này đều đạt điểm tương phản. Bảng phối màu mờ có độ tương phản thấp nhất trong số các bảng phối màu, nhưng vẫn đáp ứng các yêu cầu tối thiểu. Để giúp những người khác trong nhóm sử dụng màu có độ tương phản tốt, bạn nên tạo một tên lớp kết hợp màu bề mặt với màu văn bản dễ đọc.
.surface1 {
background-color: var(--surface1);
color: var(--text2);
}
.surface2 {
background-color: var(--surface2);
color: var(--text2);
}
.surface3 {
background-color: var(--surface3);
color: var(--text1);
}
.surface4 {
background-color: var(--surface4);
color: var(--text1);
}
Rad Shadow
Các giao diện sử dụng một lớp tiện ích có tên là .rad-shadow. Bóng này được tạo bằng công cụ Bóng mượt. Tôi rất cảm kích công cụ này. Tôi đã lấy đoạn mã được tạo của nó và tuỳ chỉnh bằng màu sắc và phép tính độ mờ của riêng mình. Lý do là để tạo một bóng mà tôi có thể điều chỉnh trong từng bảng phối màu.

Để làm được điều này, tôi đã tạo 2 biến cho mỗi bảng phối màu cần điều chỉnh, một màu bóng và một độ mạnh của bóng. Màu sắc dùng để điều chỉnh độ bão hoà và độ tối, còn độ mạnh là cách dễ dàng để tăng cường độ bóng khi đó là bảng phối màu tối. Kết quả cuối cùng có dạng như sau.
:root {
--surface-shadow-light: var(--brand-hue) 10% 20%;
--shadow-strength-light: .02;
}
.rad-shadow {
box-shadow:
0 2.8px 2.2px hsl(var(--surface-shadow) / calc(var(--shadow-strength) + .03)),
0 6.7px 5.3px hsl(var(--surface-shadow) / calc(var(--shadow-strength) + .01)),
0 12.5px 10px hsl(var(--surface-shadow) / calc(var(--shadow-strength) + .02)),
0 22.3px 17.9px hsl(var(--surface-shadow) / calc(var(--shadow-strength) + .02)),
0 41.8px 33.4px hsl(var(--surface-shadow) / calc(var(--shadow-strength) + .03)),
0 100px 80px hsl(var(--surface-shadow) / var(--shadow-strength))
;
}
Nếu tiếp tục sử dụng bóng trong bảng phối màu, tôi cũng sẽ đặt góc bóng là một hằng số mã thông báo thiết kế, vì hướng ánh sáng phải giống nhau giữa tất cả các bóng của thiết kế.
Sử dụng bảng phối màu
Sau khi hoàn tất việc xác định trước màu sắc, đã đến lúc biến chúng thành các thuộc tính độc lập với lược đồ. Ý tôi là, với tư cách là tác giả CSS trong dự án bảng phối màu này, bạn hiếm khi cần truy cập vào giá trị của một bảng phối màu cụ thể. Tôi muốn giúp bạn dễ dàng duy trì chủ đề.
Để thực hiện việc này, bạn chỉ nên sử dụng bảng phối màu thông qua các thuộc tính tuỳ chỉnh chung mà chúng ta sẽ xác định trong giây lát. Bằng cách này, những người sử dụng các biến thiết kế không bao giờ cần lo lắng về việc bảng phối màu nào hiện đang được đặt, họ chỉ cần sử dụng màu vùng hiển thị và màu văn bản. Thay vì color: var(--text1-light), hãy dùng color: var(--text1). Tất cả các thao tác điều chỉnh và xoay màu đều được thực hiện ở cấp độ cao hơn nhiều trong CSS.
Đi sâu vào vấn đề này, các kiểu kết nối của giao diện sáng trong khối mã sau đây sẽ kết nối một thuộc tính tuỳ chỉnh chung với màu cụ thể của giao diện sáng. Giờ đây, mọi thành phần sử dụng var(--brand) sẽ dùng màu sáng của thương hiệu.
Giao diện sáng (tự động)
:root {
color-scheme: light;
--brand: var(--brand-light);
--text1: var(--text1-light);
--text2: var(--text2-light);
--surface1: var(--surface1-light);
--surface2: var(--surface2-light);
--surface3: var(--surface3-light);
--surface4: var(--surface4-light);
--surface-shadow: var(--surface-shadow-light);
--shadow-strength: var(--shadow-strength-light);
}
Trang web hiện đang sử dụng giao diện sáng. Đây là một khoảnh khắc thành công rất vui vẻ! Hãy cùng xem thêm một vài khoảnh khắc như vậy khi chúng ta sử dụng các màu được xác định trước trong các ngữ cảnh bảng phối màu khác.
Giao diện tối (tự động)
@media (prefers-color-scheme: dark) {
:root {
color-scheme: dark;
--brand: var(--brand-dark);
--text1: var(--text1-dark);
--text2: var(--text2-dark);
--surface1: var(--surface1-dark);
--surface2: var(--surface2-dark);
--surface3: var(--surface3-dark);
--surface4: var(--surface4-dark);
--surface-shadow: var(--surface-shadow-dark);
--shadow-strength: var(--shadow-strength-dark);
}
}
Giao diện sáng
[color-scheme="light"] {
color-scheme: light;
--brand: var(--brand-light);
--text1: var(--text1-light);
--text2: var(--text2-light);
--surface1: var(--surface1-light);
--surface2: var(--surface2-light);
--surface3: var(--surface3-light);
--surface4: var(--surface4-light);
--surface-shadow: var(--surface-shadow-light);
--shadow-strength: var(--shadow-strength-light);
}
Giao diện tối
[color-scheme="dark"] {
color-scheme: dark;
--brand: var(--brand-dark);
--text1: var(--text1-dark);
--text2: var(--text2-dark);
--surface1: var(--surface1-dark);
--surface2: var(--surface2-dark);
--surface3: var(--surface3-dark);
--surface4: var(--surface4-dark);
--surface-shadow: var(--surface-shadow-dark);
--shadow-strength: var(--shadow-strength-dark);
}
Giao diện mờ
[color-scheme="dim"] {
color-scheme: dark;
--brand: var(--brand-dim);
--text1: var(--text1-dim);
--text2: var(--text2-dim);
--surface1: var(--surface1-dim);
--surface2: var(--surface2-dim);
--surface3: var(--surface3-dim);
--surface4: var(--surface4-dim);
--surface-shadow: var(--surface-shadow-dim);
--shadow-strength: var(--shadow-strength-dim);
}
Tại thời điểm này, tác giả có thể thoải mái sử dụng các thành phần chung của bảng phối màu được cung cấp khi cần và không bao giờ phải lo lắng về các chủ đề nữa.
Kết luận
Giờ bạn đã biết cách tôi làm, vậy bạn sẽ làm như thế nào?! 🙂
Hãy đa dạng hoá các phương pháp và tìm hiểu tất cả các cách để xây dựng trên web. Tạo một bản minh hoạ trên Codepen hoặc tự lưu trữ bản minh hoạ của riêng bạn, sau đó gửi cho tôi qua Twitter. Tôi sẽ thêm bản minh hoạ đó vào phần Bản phối lại của cộng đồng bên dưới.
Nguồn
Bản phối lại của cộng đồng
- @chris-kruining đã thêm một thanh trượt sắc độ, màu trạng thái và chế độ tương phản cho no-preference, more và less:
bản minh hoạ.