Giao diện sáng/tối theo thành phần cụ thể

Tính năng web: color-scheme light-dark()

Mặc dù thường được đặt trên phần tử gốc, nhưng bạn có thể đặt thuộc tính color-scheme trên từng phần tử để buộc các phần tử đó sử dụng một bảng phối màu khác với phần còn lại của trang. Điều này có thể hữu ích cho các thành phần luôn phải được xem trong một bảng phối màu cụ thể (ví dụ: luôn ở chế độ tối hoặc sáng).

Ví dụ về trường hợp sử dụng:

  • Các phần tử thường ở chế độ tối ngay cả trên các trang ở chế độ sáng vì lý do thẩm mỹ, ví dụ: khối mã, trình phát nội dung nghe nhìn, thư viện ảnh
  • Những khu vực chứa nội dung nghe nhìn được thiết kế cho nền sáng (ví dụ: hình ảnh, video, hình minh hoạ, bản xem trước khi in) có thể được đặt ở chế độ sáng ngay cả khi phần còn lại của trang ở chế độ tối.
  • Các phần tử có bảng phối màu được kiểm soát bằng chế độ cài đặt cấp người dùng, chẳng hạn như bản xem trước thành phần
  • Nội dung nhúng không hỗ trợ cả chế độ sáng và chế độ tối
  • Công cụ thiết kế, bản đồ, hình ảnh trực quan, trò chơi, v.v.

Khi nào nên thay đổi màu sắc và khi nào nên buộc color-scheme?

Không phải phần tử nào sử dụng văn bản sáng hơn trên nền tối hơn ở chế độ sáng hoặc văn bản tối hơn trên nền sáng hơn ở chế độ tối cũng cần một color-scheme khác. Ví dụ: nút chính có thể được hiển thị dưới dạng màu xanh dương với văn bản màu trắng ở chế độ sáng, nhưng điều đó không đảm bảo color-scheme: dark.

Theo nguyên tắc chung, các phần tử sử dụng color-scheme khác thường là các bề mặt phức tạp thiết lập bối cảnh hình ảnh riêng, thay vì các vùng chứa nông đơn giản.

Khi cân nhắc sử dụng một color-scheme khác trên một phần tử, hãy tự hỏi:

  • Giao diện người dùng tích hợp sẵn của trình duyệt không được tuỳ chỉnh (ví dụ: các thành phần điều khiển biểu mẫu, thanh cuộn, v.v.) có nên sử dụng bảng phối màu đó hay điều chỉnh cho phù hợp với bảng phối màu của trang không? -> nếu là trường hợp đầu tiên, đừng sử dụng color-scheme.
  • Màu light-dark() có nên được phân giải như màu của phần còn lại của trang hay dựa trên giá trị ghi đè không? -> nếu là trường hợp đầu tiên, đừng sử dụng color-scheme.
  • Các thành phần con có nên nằm trong color-scheme đó không? Nếu không, đừng sử dụng color-scheme.

Cách triển khai cơ bản

Các cơ chế ghi đè dành riêng cho thành phần thường (mặc dù không nhất thiết) được dùng trên các trang cũng hỗ trợ nhiều bảng phối màu thông qua color-scheme chung. Để triển khai tốt chế độ tối trên toàn trang, hãy xem phần Chế độ tối.

Sau khi áp dụng color-scheme trên toàn trang và bạn đang sử dụng mã thông báo màu nhạy cảm với chế độ đó (ví dụ: thông qua light-dark()), bạn chỉ cần đặt color-scheme trên các thành phần cụ thể để ghi đè chế độ màu cho cây con đó:

pre, code, .dark {
  color-scheme: dark;
}

Xin lưu ý rằng một số trình duyệt sẽ tự động điều chỉnh các thành phần cho phù hợp với một bảng phối màu khác. Để buộc sử dụng bảng phối màu đã chỉ định trong mọi trường hợp, hãy dùng only, tức là color-scheme: only dark; thay vì color-scheme: dark;.

Điều chỉnh các giá trị không phải màu sắc

light-dark() hiện chỉ hoạt động với màu sắc.

Các phương pháp hay nhất

  • BẮT BUỘC: Đừng đặt color-scheme trên các phần tử không có nền, vì điều đó có nguy cơ kết hợp các cặp màu nền và màu văn bản từ các bảng phối màu khác nhau, dẫn đến văn bản không đọc được.
  • KHÔNG BẮT BUỘC: Mặc dù việc sử dụng lại cùng một cặp màu như chế độ tối trên toàn trang sẽ dễ dàng hơn, nhưng chúng ta có thể xác định các cặp màu khác nhau cho các thành phần này. Ví dụ: chúng ta có thể muốn một thành phần chế độ tối được dùng trong trang chế độ sáng ít tối hơn một chút so với khi cùng một thành phần chế độ tối đó được dùng trong một trang có chế độ tối tổng thể.

Các vấn đề đã biết cần lưu ý

Lưu ý quan trọng: Kế thừa màu light-dark()

light-dark() phân giải tại thời điểm giá trị được tính toán. Điều này có nghĩa là mọi thuộc tính <color> kế thừa được đặt thành màu light-dark() sẽ chỉ truyền một trong hai màu cho các phần tử con, chứ không phải chính biểu thức light-dark().

Nội dung như vậy bao gồm:

  • Các thuộc tính màu tích hợp sẵn kế thừa, chẳng hạn như color, accent-color, fill, stroke, text-shadow, caret-color
  • Bất kỳ thuộc tính tuỳ chỉnh có thể kế thừa nào đã đăng ký có syntax: <color> và inherits: true
  • Bất kỳ thuộc tính <color> nào khác được đặt thành inherit

Điều này có nghĩa là bạn nên:

  • KHÔNG đăng ký các thuộc tính tuỳ chỉnh dùng để lưu giữ mã thông báo thiết kế (ví dụ: --surface-color) dưới dạng <color>. Mã thông báo cần duy trì biểu thức light-dark() Express Live để các thành phần con có thể phân giải lại mã thông báo theo một color-scheme khác.
  • Khi đặt color-scheme trên một phần tử, hãy chỉ định lại mọi thuộc tính <color> kế thừa có thể đã được đặt thành giá trị light-dark() (trực tiếp hoặc thông qua mã thông báo thiết kế), ngay cả khi đó là cùng một mã thông báo thiết kế.
  • KHÔNG sử dụng thuộc tính inherit trên <color> cho các phần tử có cơ chế ghi đè color-scheme (có thể sử dụng cho các phần tử con của chúng).
  • NÊN sử dụng các thuộc tính <color> đã đăng ký cho trường hợp sử dụng ngược lại: khi bạn cố ý muốn chụp nhanh màu đã phân giải của đối tượng cấp trên và ngăn màu đó phân giải lại trong color-scheme của đối tượng cấp dưới. Ví dụ: chụp nền trang để sử dụng ở nơi khác.
  • Nếu bạn cần tạo ảnh động cho một màu, hãy sử dụng một thuộc tính <color> riêng biệt được đăng ký @property trên phần tử đang được tạo ảnh động (bạn cần đăng ký để nội suy màu). Đây không phải là mã thông báo thiết kế mà là mục tiêu ảnh động cho mỗi phần tử, vì vậy, thuộc tính này không xung đột với quy tắc nêu trên.

Ví dụ:

:root {
    --accent-color: light-dark(blue, skyblue);
    --surface-color: light-dark(white, #222);
    --text-color: light-dark(#111, white);

    color-scheme: light dark;
    accent-color: var(--accent-color);
    color: var(--text-color);
}

body {
    /* --surface-color dynamically switches despite being inherited because --surface-color is not registered */
    background: var(--surface-color);
}

pre, code {
    color-scheme: dark;
    background: var(--surface-color);

    /* Without this, accent-color would be blue, not skyblue! */
    accent-color: var(--accent-color);

    /* Without this, text-color would be #111, not white! */
    color: var(--text-color);
}

Các vấn đề cần lưu ý khi sử dụng bảng phối màu

  • Chrome và Firefox tuân thủ color-scheme cho iframe: các trình duyệt này hiển thị các trang được nhúng theo bảng phối màu chính xác và điều chỉnh truy vấn đa phương tiện prefers-color-scheme của trang được nhúng để phản ánh color-scheme của bối cảnh nhúng. Safari thì không và phân giải prefers-color-scheme thành chế độ cài đặt hệ thống ngay cả bên trong iframe.
    • Nếu bạn kiểm soát cả phần tử mẹ và iframe: hãy truyền bảng phối màu của phần tử mẹ đến iframe một cách rõ ràng – thông qua tham số URL (?theme=dark) tại thời điểm tạo iframe hoặc thông qua postMessage() (cũng cho phép bạn phản ứng với các thay đổi trong thời gian chạy). Trong iframe, hãy đặt một lớp trên <html> (và/hoặc color-scheme trên :root) từ tín hiệu đó thay vì dựa vào prefers-color-scheme.
    • Nếu bạn chỉ kiểm soát trang được nhúng: thì không có cách nào đáng tin cậy để phát hiện color-scheme của bối cảnh nhúng từ bên trong iframe trong Safari. Hiển thị một tham số giao diện rõ ràng trên API nhúng (ví dụ: chuỗi truy vấn hoặc giao thức postMessage) và ghi lại tham số đó cho người nhúng.

Chiến lược dự phòng

Dự phòng và hỗ trợ trình duyệt cho bảng phối màu

Thuộc tính color-scheme là cải tiến tăng dần. Những trình duyệt không hỗ trợ thuộc tính này sẽ bỏ qua và sử dụng giao diện người dùng chế độ sáng mặc định.

Để điều chỉnh cho phù hợp với lựa chọn ưu tiên của người dùng trong các trình duyệt cũ, hãy sử dụng truy vấn đa phương tiện prefers-color-scheme để cung cấp các màu khác nhau khi người dùng ưu tiên chế độ tối.

  • NÊN sử dụng truy vấn phương tiện để chuyển đổi các thuộc tính tuỳ chỉnh trên :root hoặc html
  • Tránh sử dụng truy vấn đa phương tiện trên từng thành phần, trừ phi thành phần đó yêu cầu một loại tuỳ chỉnh chế độ tối rất cụ thể ngoài màu sắc.
:root {
  /* Define brand colors for each mode */
  --color-brand-light: #0056b3;
  --color-brand-dark: #00e5ff;
  --color-brand: var(--color-brand-light);

  /* MANDATORY: Fallback for browsers without light-dark support */
  @media (prefers-color-scheme: dark) {
    --color-brand: var(--color-brand-dark);
  }

  /* Ignored in older browsers */
  color-scheme: light dark;
}

button.primary {
    background-color: var(--color-brand);
}

Dự phòng và hỗ trợ trình duyệt cho light-dark()

Đối với các trình duyệt hỗ trợ color-scheme nhưng chưa hỗ trợ light-dark(), trước tiên, bạn nên xác định phiên bản màu sáng và tối dưới dạng thuộc tính tuỳ chỉnh, đồng thời sử dụng truy vấn đa phương tiện prefers-color-scheme để đặt màu cho chế độ tương ứng như trong ví dụ dưới đây:

:root {
  /* Define browser UI accent color for each mode */
  --brand-accent-light: #0056b3;
  --brand-accent-dark: #00e5ff;
  --accent-color: var(--brand-accent-light);

  /* MANDATORY: Fallback for browsers without light-dark support */
  @media (prefers-color-scheme: dark) {
    --accent-color: var(--brand-accent-dark);
  }

  /* OPTIONAL: use light-dark() for more control of built-in UI colors */
  @supports (color: light-dark(white, black)) {
    --accent-color: light-dark(var(--brand-accent-light), var(--brand-accent-dark));
  }

  /* MANDATORY: Automatically adapt native UI to user system preferences */
  color-scheme: light dark;

  /* Example inherited color property */
  accent-color: var(--accent-color);
}

pre, code {
  color-scheme: dark;

  /* **Mandatory**: any inherited color properties must be set again, even if to the same design tokens */
  accent-color: var(--accent-color);
}