Chế độ tối

Thuộc tính color-scheme cho biết bảng phối màu (chẳng hạn như sáng hoặc tối) mà trang của bạn hỗ trợ. Thao tác này sẽ thông báo cho trình duyệt biết rằng trình duyệt có thể tự động tạo giao diện cho các phần tử trên giao diện người dùng gốc (như thanh cuộn, thành phần điều khiển biểu mẫu và nền canvas mặc định) để phù hợp với thiết kế của trang web và giúp giảm thiểu hiện tượng nhấp nháy trắng trong quá trình tải ban đầu.

Triển khai

1. Khai báo các lược đồ được hỗ trợ trong HTML

BẮT BUỘC: Để giúp ngăn chặn "nội dung nhấp nháy không theo giao diện" (FOUC), hãy đặt thẻ <meta> vào <head> để đảm bảo trình duyệt biết giao diện nào bạn hỗ trợ trước khi bắt đầu hiển thị. Mặc dù thẻ <meta> này giúp tránh FOUC bằng cách đặt màu canvas ban đầu sớm, nhưng có thể không loại bỏ hoàn toàn hiện tượng nhấp nháy trong tất cả trình duyệt hoặc điều kiện tải.

<!-- MANDATORY: Declare support for both light and dark themes -->
<meta name="color-scheme" content="light dark">

2. Áp dụng bảng phối màu trên toàn trang cho CSS :root hoặc html

BẮT BUỘC: Áp dụng thuộc tính color-scheme cho phần tử html hoặc lớp giả :root. Trình duyệt đặc biệt xem xét phần tử gốc để xác định giao diện cho toàn bộ khung nhìn, bao gồm cả thanh cuộn gốc và nền "canvas" ban đầu. Nếu chỉ áp dụng cho body, thì các nền tảng giao diện người dùng chung này có thể vẫn ở chế độ sáng vì body không kiểm soát bối cảnh hiển thị của cửa sổ.

/* MANDATORY: Apply color-scheme to :root or html for viewport-wide theming */
:root {
  /* MANDATORY: Automatically adapt native UI to user system preferences */
  color-scheme: light dark;
}

3. Xác định mã thông báo màu sáng và tối

Bạn có thể sử dụng hàm light-dark() để xác định mã thông báo màu tự động thích ứng với các giá trị color-scheme khác nhau.

Bạn cũng nên giữ các giá trị màu thô trong các thuộc tính tuỳ chỉnh riêng biệt, giúp dễ dàng kết hợp chúng theo nhiều cách (và giúp hành vi dự phòng dễ dàng hơn, nếu cần).

Để kiểm soát tốt hơn màu sắc của giao diện người dùng tích hợp sẵn như accent-color hoặc scrollbar-color, tác giả có thể tuỳ ý thêm màu động của riêng họ bằng cách sử dụng các thuộc tính tuỳ chỉnh và/hoặc hàm light-dark(). Hàm này tự động chọn màu chính xác dựa trên color-scheme được tính toán của phần tử và loại bỏ nhu cầu về các truy vấn đa phương tiện thừa, nhưng không bắt buộc đối với việc triển khai cơ bản.

:root {
  --color-brand-light: oklch(45% 0.23 270);
  --color-brand-dark: oklch(85% 0.15 210);
  --color-brand-text-light: white;
  --color-brand-text-dark: oklch(40% 0.23 270);

  --color-brand: light-dark(var(--color-brand-light), var(--color-brand-dark));
  --color-brand-text: light-dark(var(--color-brand-text-light), var(--color-brand-text-dark));

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

button.primary {
  /* These automatically adapt to color scheme */
  background-color: var(--color-brand);
  color: var(--color-brand-text);
}

KHÔNG BẮT BUỘC: Có một số màu hệ thống cũng tự động thích ứng với bảng phối màu được sử dụng (và các chế độ màu khác, ví dụ: màu bắt buộc), chẳng hạn như canvas, canvastext, accentcolor (kiểm tra khả năng hỗ trợ), buttonborder, v.v. Những màu này thường quá hạn chế để có thể hữu ích, ngoài những trường hợp rất cụ thể mà bạn cần khớp chính xác với một số giao diện người dùng của trình duyệt mặc định hoặc làm màu dự phòng/mặc định.

KHÔNG BẮT BUỘC: Điều chỉnh các cặp màu cho phù hợp với bối cảnh

Ngay cả khi ghi đè theo giá trị mặc định của hệ thống, bạn vẫn nên sử dụng truy vấn đa phương tiện prefers-color-scheme để xác định các cặp màu khác nhau có tính đến màu sắc của trình duyệt và giao diện người dùng của hệ điều hành xung quanh trang (hoặc của trang xung quanh, khi trang được dùng làm iframe).

Ví dụ: sử dụng giao diện sáng mờ hơn một chút khi chế độ cài đặt hệ thống là dark hoặc giao diện tối có độ tương phản cao hơn khi chế độ cài đặt hệ thống là light để trang không bị giao diện người dùng xung quanh lấn át về mặt hình ảnh.

Tuỳ chỉnh giao diện người dùng của trình duyệt một cách chi tiết

Việc đặt color-scheme đã điều chỉnh giao diện người dùng của trình duyệt cho phù hợp với bảng phối màu được sử dụng, nhưng việc này sẽ sử dụng màu mặc định của hệ điều hành và/hoặc màu hệ thống có thể không hoàn toàn phù hợp với thiết kế của trang web. Các trình duyệt hiện đại cung cấp một số điểm nối tuỳ chỉnh chi tiết cho các trình duyệt này. Đừng triển khai lại các chế độ điều khiển gốc chỉ để tuỳ chỉnh giao diện của chúng mà không sử dụng hết các trình kích hoạt tuỳ chỉnh mà trình duyệt hiện đại cung cấp.

Đặt màu nhấn

Một số giao diện người dùng của trình duyệt (ví dụ: hộp đánh dấu hoặc thanh trượt đã chọn) sử dụng màu nhấn. Theo mặc định, thuộc tính này sẽ phân giải thành chế độ cài đặt của hệ điều hành, nhưng bạn có thể sử dụng thuộc tính accent-color để đặt thành một màu phù hợp hơn với trang, chẳng hạn như màu thương hiệu của trang.

html {
  accent-color: light-dark(var(--color-accent-light), var(--color-accent-dark));
}

Những vấn đề cần lưu ý khi sử dụng accent-color

Cách trình duyệt hiển thị accent-color sẽ khác nhau. Đối với các chỉ báo thường được vẽ trên nền màu nhấn, chẳng hạn như dấu hộp đánh dấu và dấu chấm nút chọn, trình duyệt có thể áp dụng các cách xử lý khác nhau:

  • Chromium và Firefox thường chọn màu chỉ báo tương phản.
  • Thay vào đó, Safari có thể giữ nguyên chỉ báo màu trắng ở chế độ sáng hoặc chỉ báo màu đen ở chế độ tối, đồng thời sửa đổi màu nhấn phía sau chỉ báo đó. Việc điều chỉnh này không phải lúc nào cũng cung cấp đủ độ tương phản cho mọi màu được cung cấp, vì vậy, hãy thử nghiệm các kiểu kết hợp quan trọng trong Safari, bao gồm cả bảng phối màu sáng và tối. Đừng cho rằng Safari sẽ tự động chọn màu chỉ báo có độ tương phản tốt nhất.

Màu thanh cuộn

Bạn có thể sử dụng scrollbar-color cùng với light-dark() để đặt màu thanh cuộn tuỳ chỉnh phù hợp với bảng phối màu được sử dụng.

:root {
  --color-scrollbar-track: light-dark(#eee, #222);
  --color-scrollbar-thumb: light-dark(#999, #666);
  scrollbar-color: var(--color-scrollbar-thumb) var(--color-scrollbar-track);
}

Những vấn đề cần lưu ý khi sử dụng scrollbar-color

  • KHÔNG tạo ảnh động hoặc chuyển đổi scrollbar-color. Lỗi WebKit khiến thanh cuộn nhấp nháy mỗi khi scrollbar-color thay đổi.
  • Trên macOS, các thuộc tính scrollbar-color (chuẩn) và ::-webkit-scrollbar (cũ) bị bỏ qua theo mặc định vì macOS sử dụng thanh cuộn "lớp phủ" gốc. Bạn PHẢI ghép nối màu sắc tuỳ chỉnh với scrollbar-width (ví dụ: thin hoặc auto) để buộc macOS hiển thị các màu đó.
  • Ngay cả khi áp dụng scrollbar-width, thanh cuộn lớp phủ macOS vẫn hiển thị rãnh (máng) ở chế độ trong suốt theo mặc định. Nếu thiết kế yêu cầu màu nền thanh cuộn hiển thị trên MacOS, bạn PHẢI áp dụng scrollbar-gutter: stable; cho vùng chứa có thể cuộn, nhưng xin lưu ý rằng màu nền này chỉ xuất hiện sau khi người dùng di chuột qua thanh cuộn.
  • Ngay cả khi có scrollbar-gutter: stable, dải này vẫn có thể trong suốt trên MacOS. Nút kéo không được phụ thuộc vào màu của thanh trượt để hiển thị.

Tuỳ chỉnh thêm

Hầu hết giao diện người dùng của trình duyệt đều hiển thị các phần tử giả để tuỳ chỉnh hoàn toàn giao diện, chẳng hạn như:

  • ::placeholder
  • ::spelling-error
  • ::grammar-error
  • ::selection
  • ::search-text
  • ::target-text
  • ::file-selector-button

Bạn có thể sử dụng màu light-dark() trên bất kỳ thành phần nào trong số này để áp dụng màu phù hợp với bảng phối màu được sử dụng.

4. Hình ảnh khác nhau cho chế độ sáng và tối

Đối với hình ảnh CSS, bạn cũng có thể dùng light-dark() để cung cấp các giá trị hình ảnh khác nhau.

QUAN TRỌNG: Cả hai giá trị trong light-dark() phải thuộc cùng một loại. Bạn không thể kết hợp màu sắc với hình ảnh. Nếu một trong hai phải là hình ảnh, hãy sử dụng image(<color>) để gói giá trị màu.

Phát hiện giao diện dựa trên JS

Hầu hết các nhánh bảng phối màu phải được thực hiện trong CSS, tự động thích ứng với các thay đổi.

Mọi JS đọc matchMedia("(prefers-color-scheme: dark)").matches cũng PHẢI xử lý sự kiện change của nó vì lựa chọn ưu tiên về hệ thống có thể thay đổi bất cứ lúc nào.

KHÔNG BẮT BUỘC: Triển khai nút bật/tắt bảng phối màu

Nếu muốn cho phép người dùng ghi đè chế độ mặc định của hệ thống cho trang web này theo cách thủ công, bạn có thể triển khai nút bật/tắt chế độ tối. Hầu hết các trang web không cần nút bật/tắt cố định trong giao diện người dùng của trang web. Thông thường, bạn chỉ cần đặt bảng phối màu mặc định là bảng phối màu của hệ thống, hoặc có thể thêm một chế độ cài đặt trong một giao diện cài đặt riêng. Hãy xem phần Nút bật/tắt chế độ tối để biết các phương pháp hay nhất về cách triển khai nút bật/tắt chế độ tối (liên tục hoặc không liên tục).

KHÔNG BẮT BUỘC: Ghi đè theo thành phần cụ thể

Bạn có thể ghi đè giao diện chung cho các phần tử cụ thể bằng cách đặt color-scheme trên các phần tử đó.

Để biết thêm thông tin và các điểm cần lưu ý, hãy xem phần Giao diện sáng/tối theo thành phần cụ thể.

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

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);
}

Phương án dự phòng và khả năng hỗ trợ của trình duyệt cho các giá trị hình ảnh light-dark()

Đối với các trình duyệt không hỗ trợ light-dark() cho hình ảnh, hãy sử dụng image-set() làm nút chuyển: cung cấp cho mỗi lựa chọn một type() từ một thuộc tính tuỳ chỉnh và đặt lựa chọn không hoạt động thành một loại MIME không được hỗ trợ để trình duyệt loại bỏ lựa chọn đó. Lựa chọn đang hoạt động nhận được một giá trị trống, vì vậy, đây là một lựa chọn image-set() đơn giản không có type().

Xác định công tắc một lần trên :root. Mỗi trang web sử dụng sau đó chỉ cần cả hai hình ảnh cùng dòng, như light-dark().

:root {
  --light-type: ;
  --dark-type: type("image/do-not-use");

  /* MANDATORY: Fallback for browsers without light-dark() image support */
  @media (prefers-color-scheme: dark) {
    --light-type: type("image/do-not-use");
    --dark-type: ;
  }
}

.hero {
  background-image: image-set(url(hero-light.png) var(--light-type), url(hero-dark.png) var(--dark-type));

  @supports (background-image: light-dark(url("a"), url("b"))) {
    background-image: light-dark(url(hero-light.png), url(hero-dark.png));
  }
}
  • Chỉ hình ảnh đã chọn mới được tải xuống.
  • Các độ phân giải vẫn hoạt động: image-set(url(a-1x.png) 1x var(--light-type), url(a-2x.png) 2x var(--light-type), url(b-1x.png) 1x var(--dark-type), url(b-2x.png) 2x var(--dark-type)).
  • KHÔNG sử dụng type("") cho lựa chọn đang hoạt động. Trình duyệt coi chuỗi trống là không được hỗ trợ và bỏ qua mọi tuỳ chọn.
  • Không giống như light-dark(), nút chuyển không tuân theo color-scheme được đặt trên cây con. Nút bật/tắt thủ công hoặc bất kỳ cây con nào đặt color-scheme cũng PHẢI lật --light-type và --dark-type.

Cách khác: một thuộc tính tuỳ chỉnh cho mỗi hình ảnh

Đơn giản hơn khi chỉ có một số ít hình ảnh khác nhau, nhưng mỗi hình ảnh cần có nhánh truy vấn phương tiện riêng và nút bật/tắt thủ công phải lật từng hình ảnh.

:root {
  --hero-image: url(hero-light.png);

  @media (prefers-color-scheme: dark) {
    --hero-image: url(hero-dark.png);
  }

  @supports (background-image: light-dark(url("a"), url("b"))) {
    --hero-image: light-dark(url(hero-light.png), url(hero-dark.png));
  }
}

.hero {
  background-image: var(--hero-image);
}

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

Sử dụng dải chuyển màu có một điểm dừng màu và không có vị trí, ví dụ: linear-gradient(blue).

Để hỗ trợ nhiều trình duyệt hơn, bạn có thể cung cấp rõ ràng các vị trí 0 100% cho điểm dừng màu duy nhất, ví dụ: linear-gradient(blue 0 100%).

Dự phòng và hỗ trợ trình duyệt cho scrollbar-color

Tính năng này là cải tiến tăng dần và không phải lúc nào cũng cần có tính năng dự phòng.

Nếu kiểu dáng là quan trọng và mục tiêu Baseline của người dùng là "Baseline Widely Available" (Baseline được cung cấp rộng rãi) trở lên, thì bạn NÊN thêm các phần tử giả ::-webkit-scrollbar không chuẩn làm phương án dự phòng.

Gói các phương án dự phòng cũ trong một khối @supports not (scrollbar-color: auto) để ngăn xung đột giữa các thuộc tính chuẩn và bộ chọn WebKit cũ trong các trình duyệt hỗ trợ cả hai thuộc tính này.

Nếu bạn đang sử dụng các thuộc tính tuỳ chỉnh để xác định màu, thì các thuộc tính này sẽ tự động xếp tầng cho các bộ chọn WebKit cũ. Bạn KHÔNG cần phải sao chép các mục này.

/* Legacy fallback for WebKit/Blink browsers */
@supports not (scrollbar-color: auto) {
  .scroller::-webkit-scrollbar {
    /* Must define base size in WebKit for custom colors to be visual */
    width: 12px;
    height: 12px;
  }

  .scroller::-webkit-scrollbar-thumb {
    background: var(--scrollbar-thumb);
  }

  .scroller::-webkit-scrollbar-track {
    background: var(--scrollbar-track);
  }
}

Dự phòng và hỗ trợ trình duyệt cho màu nhấn

Thuộc tính accent-color 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 thuộc tính này và sử dụng màu giao diện người dùng mặc định.