Triển Khai Chế Độ Tối: Hướng Dẫn Đầy Đủ Cho Nhà Phát Triển
Embed This Widget
Add the script tag and a data attribute to embed this widget.
Embed via iframe for maximum compatibility.
<iframe src="https://colorfyi.com/iframe/entity//" width="420" height="400" frameborder="0" style="border:0;border-radius:10px;max-width:100%" loading="lazy"></iframe>
Paste this URL in WordPress, Medium, or any oEmbed-compatible platform.
https://colorfyi.com/entity//
Add a dynamic SVG badge to your README or docs.
[](https://colorfyi.com/entity//)
Use the native HTML custom element.
Chế độ tối đã chuyển từ sở thích của thiểu số thành tính năng được kỳ vọng. Người dùng trên mọi nền tảng — macOS, Windows, Android, iOS — có thể thiết lập giao diện tối toàn hệ thống, và họ kỳ vọng các trang web cũng như ứng dụng phải tôn trọng điều đó. Triển khai chế độ tối đúng cách đòi hỏi nhiều hơn việc chỉ đổi trắng thành đen: cần có cách tiếp cận hệ thống về màu sắc, độ tương phản và quyền kiểm soát của người dùng. Hướng dẫn này trình bày toàn bộ quy trình, từ kiến trúc CSS đến cơ chế JavaScript toggle và kiểm thử kỹ lưỡng cả hai chủ đề.
CSS Custom Properties cho Chủ Đề
Cách bền vững nhất để xử lý chế độ tối trong CSS là thông qua custom properties (còn gọi là CSS variables). Thay vì rải các giá trị màu khắp nơi trong stylesheet, bạn định nghĩa mỗi màu như một biến trên :root rồi tái định nghĩa các biến đó cho chế độ tối. Style của component chỉ tham chiếu đến biến — không bao giờ dùng mã hex thô.
Định Nghĩa Bảng Màu Sáng và Tối
Bắt đầu với bảng màu chế độ sáng làm mặc định. Một điểm khởi đầu gọn gàng có thể trông như sau:
:root {
/* Nền */
--color-bg-base: #FFFFFF;
--color-bg-elevated: #F8F9FA;
--color-bg-overlay: #F1F3F5;
/* Văn bản */
--color-text-primary: #1A1A2E;
--color-text-secondary: #4A4A6A;
--color-text-muted: #6C757D;
/* Viền */
--color-border: #DEE2E6;
--color-border-strong: #ADB5BD;
/* Thương hiệu / nhấn mạnh */
--color-accent: #3B82F6;
--color-accent-hover: #2563EB;
/* Phản hồi */
--color-success: #22C55E;
--color-warning: #F59E0B;
--color-danger: #EF4444;
}
Sau đó định nghĩa các ghi đè cho chế độ tối trong một khối riêng. Điểm mấu chốt là bạn không chỉ đảo ngược màu — mà đang chọn một bảng màu khác, được xây dựng có mục đích cho bề mặt tối:
[data-theme="dark"] {
/* Nền */
--color-bg-base: #0F0F17;
--color-bg-elevated: #1A1A2E;
--color-bg-overlay: #252540;
/* Văn bản */
--color-text-primary: #E8E8F0;
--color-text-secondary: #A8A8C0;
--color-text-muted: #6A6A88;
/* Viền */
--color-border: #2E2E4A;
--color-border-strong: #4A4A6A;
/* Thương hiệu / nhấn mạnh — thường sáng hơn một chút để dễ đọc trên nền tối */
--color-accent: #60A5FA;
--color-accent-hover: #93C5FD;
/* Phản hồi — giảm độ bão hòa nhẹ để tránh cảm giác chói */
--color-success: #4ADE80;
--color-warning: #FCD34D;
--color-danger: #F87171;
}
Lưu ý rằng màu nhấn #3B82F6 trong chế độ sáng trở thành #60A5FA trong chế độ tối. Sắc độ giống nhau nhưng độ sáng tăng — điều này cần thiết vì bối cảnh tương phản đã đảo ngược. Màu đạt WCAG AA trên nền trắng sẽ hầu như luôn thất bại trên nền gần đen trừ khi bạn điều chỉnh. Trình Tạo Shade cho phép bạn khám phá toàn bộ dải 50–950 của bất kỳ màu nào, giúp dễ dàng chọn shade phù hợp cho từng chủ đề.
Sử Dụng Biến trong Component
Sau khi bảng màu được thiết lập, mỗi component tham chiếu đến biến thay vì giá trị thô:
.card {
background-color: var(--color-bg-elevated);
border: 1px solid var(--color-border);
color: var(--color-text-primary);
}
.btn-primary {
background-color: var(--color-accent);
color: #FFFFFF;
}
.btn-primary:hover {
background-color: var(--color-accent-hover);
}
Khi thuộc tính [data-theme="dark"] xuất hiện trên phần tử <html>, tất cả biến cập nhật đồng thời và mọi component tham chiếu đến chúng thay đổi giao diện — không cần thêm CSS nào.
Media Query prefers-color-scheme
Trước khi người dùng tương tác với nút toggle, bạn có thể tôn trọng tùy chọn hệ điều hành của họ bằng media query prefers-color-scheme. Media query này kích hoạt khi OS được đặt thành giao diện tối.
@media (prefers-color-scheme: dark) {
:root {
--color-bg-base: #0F0F17;
--color-bg-elevated: #1A1A2E;
--color-bg-overlay: #252540;
--color-text-primary: #E8E8F0;
--color-text-secondary: #A8A8C0;
--color-text-muted: #6A6A88;
--color-border: #2E2E4A;
--color-border-strong: #4A4A6A;
--color-accent: #60A5FA;
--color-accent-hover: #93C5FD;
--color-success: #4ADE80;
--color-warning: #FCD34D;
--color-danger: #F87171;
}
}
Cách tiếp cận này hoạt động mà không cần JavaScript, không có layout shift, và tôn trọng ngay lập tức tùy chọn đã khai báo của người dùng khi tải trang. Đây là baseline đúng đắn. Hạn chế là người dùng không thể ghi đè trong ứng dụng của bạn — nếu OS tối, site cũng tối, không có lối thoát. Đó là lý do tại sao hầu hết triển khai thực tế đều thêm một lớp JavaScript toggle lên trên media query.
Kết Hợp Cả Hai Cách Tiếp Cận
Mẫu được khuyến nghị sử dụng media query làm mặc định và thuộc tính data-theme như một ghi đè tường minh. Bạn có thể xử lý điều này bằng thủ thuật CSS specificity hoặc bằng cách sắp xếp các quy tắc đúng thứ tự:
/* 1. Mặc định chế độ sáng */
:root {
--color-bg-base: #FFFFFF;
/* ... */
}
/* 2. Ghi đè chế độ tối OS (khi chưa đặt tùy chọn tường minh) */
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--color-bg-base: #0F0F17;
/* ... */
}
}
/* 3. Chế độ tối tường minh (người dùng toggle qua JS) */
[data-theme="dark"] {
--color-bg-base: #0F0F17;
/* ... */
}
Selector :not([data-theme="light"]) trong media query có nghĩa là tùy chọn tối của OS chỉ áp dụng khi người dùng chưa chọn tường minh chế độ sáng. Sau khi họ toggle, lựa chọn tường minh của họ sẽ thắng.
Cơ Chế Toggle bằng JavaScript
Một nút toggle được triển khai tốt làm ba việc: thay đổi ngay lập tức giao diện hiện tại, lưu trữ tùy chọn trong localStorage, và đọc tùy chọn đã lưu khi tải trang trước lần render đầu tiên.
Đọc Tùy Chọn Khi Tải Trang
Script này phải chạy trong <head> — trước khi trang render — để ngăn chặn flash của chủ đề sai:
<head>
<script>
(function() {
const stored = localStorage.getItem('theme');
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
const theme = stored ?? (prefersDark ? 'dark' : 'light');
document.documentElement.setAttribute('data-theme', theme);
})();
</script>
</head>
Điều này đặt ngay data-theme trên <html> trước khi bất kỳ style nào được áp dụng. Trình duyệt tính toán các giá trị custom property đúng từ lần render đầu tiên — không có flash.
Hàm Toggle
function toggleTheme() {
const current = document.documentElement.getAttribute('data-theme');
const next = current === 'dark' ? 'light' : 'dark';
document.documentElement.setAttribute('data-theme', next);
localStorage.setItem('theme', next);
}
// Kết nối với nút
document.getElementById('theme-toggle').addEventListener('click', toggleTheme);
Đồng Bộ Trạng Thái Nút Toggle
Nút toggle phải phản ánh trực quan chế độ hiện tại. Cách đơn giản là dùng icon:
<button id="theme-toggle" aria-label="Toggle dark mode">
<span class="icon-light">☀️</span>
<span class="icon-dark">🌙</span>
</button>
[data-theme="dark"] .icon-light { display: none; }
[data-theme="dark"] .icon-dark { display: inline; }
[data-theme="light"] .icon-light { display: inline; }
[data-theme="light"] .icon-dark { display: none; }
Vì khả năng hiển thị icon được kiểm soát bởi CSS variable gắn với data-theme, trạng thái nút tự động cập nhật bất cứ khi nào thuộc tính thay đổi — không cần JavaScript bổ sung.
Chiến Lược Điều Chỉnh Màu Sắc
Chọn màu cho chế độ tối không đơn giản như đảo ngược bảng màu sáng. Một số nguyên tắc hướng dẫn lựa chọn màu tối tốt.
Giảm Tương Phản, Không Chỉ Đảo Ngược
Văn bản trắng thuần trên nền đen thuần (#FFFFFF trên #000000) là tương phản tối đa về kỹ thuật — 21:1 — nhưng gây mệt mỏi về mặt nhận thức khi đọc lâu. Hãy giảm cả hai cực: dùng màu trắng kem như #E8E8F0 cho văn bản thân và màu navy rất tối như #0F0F17 cho nền trang. Điều này duy trì tương phản đầy đủ (vẫn trên 15:1) trong khi giảm mỏi mắt.
Sử dụng Trình Kiểm Tra Tương Phản để xác minh rằng mọi kết hợp văn bản/nền trong chủ đề tối đáp ứng ít nhất WCAG AA (4,5:1 cho văn bản bình thường, 3:1 cho văn bản lớn). Các điểm thất bại phổ biến bao gồm:
- Văn bản placeholder trong trường form
- Nhãn nút bị vô hiệu hóa
- Văn bản siêu dữ liệu phụ (dấu thời gian, tên tác giả)
- Nút chỉ có icon mà không có nhãn hiển thị
Elevation Theo Lớp với Bề Mặt Tối
Trong chế độ sáng, elevation thường được biểu đạt bằng drop shadow. Trong chế độ tối, bóng trở nên vô hình trên nền tối. Thông số kỹ thuật Material Design 3 giới thiệu cách tiếp cận hiệu quả hơn: bề mặt sáng hơn cảm giác cao hơn. Dùng nền sáng hơn một chút cho các component được nâng lên:
/* Thang elevation chế độ tối */
--color-bg-base: #0F0F17; /* Nền trang */
--color-bg-elevated: #1A1A2E; /* Card, sidebar */
--color-bg-overlay: #252540; /* Modal, dropdown */
--color-bg-tooltip: #2E2E4A; /* Tooltip */
#0F0F17 làm nền, #1A1A2E cho card, #252540 cho modal — mỗi bước sáng hơn khoảng 8–10% theo HSL lightness. Điều này tạo ra phân cấp thị giác rõ ràng mà không dựa vào shadow.
Giảm Nhẹ Độ Bão Hòa Màu Chế Độ Tối
Màu sắc có độ bão hòa cao trông gay gắt và như neon trên nền tối. Khi điều chỉnh màu thương hiệu cho chế độ tối, hãy giảm độ bão hòa 10–20% cùng với việc tăng độ sáng. Thay vì màu xanh lá #22C55E thành công rực rỡ, hãy dùng #4ADE80 — sáng hơn và ít bão hòa hơn một chút, truyền đạt thành công mà không gây mỏi mắt.
Trình Tạo Shade lý tưởng ở đây: nhập màu xanh lá hoặc xanh dương chính của thương hiệu và khám phá dải 300–400 cho văn bản và icon chế độ tối, so với dải 500–600 cho các phần tử tương tác.
Hình Ảnh và Phương Tiện
Hình ảnh có nền trắng trông chói trong chế độ tối. CSS có thể giúp:
/* Giảm độ chói của hình ảnh trong chế độ tối */
[data-theme="dark"] img:not([src*=".svg"]) {
filter: brightness(0.9) contrast(1.05);
}
/* Hoặc cho phép hình ảnh hòa vào nền nhẹ nhàng */
[data-theme="dark"] img {
mix-blend-mode: luminosity;
opacity: 0.9;
}
Với icon SVG cần điều chỉnh, sử dụng currentColor làm giá trị fill có nghĩa là chúng tự động nhận màu văn bản hiện tại:
.icon { color: var(--color-text-secondary); }
<svg fill="currentColor" viewBox="0 0 24 24">...</svg>
Kiểm Thử Cả Hai Chế Độ
Kiểm thử kỹ lưỡng ngăn chặn regression chế độ tối lọt vào môi trường sản xuất.
Giả Lập qua DevTools Trình Duyệt
Chrome và Firefox đều cung cấp tính năng giả lập chế độ tối trong DevTools mà không cần thay đổi cài đặt OS. Trong Chrome: mở DevTools, nhấp vào menu ba chấm, đến More Tools → Rendering, và đặt "Emulate CSS media feature prefers-color-scheme" thành "dark." Điều này cho phép so sánh cả hai chế độ song song.
Kiểm Thử Tương Phản Tự Động
Kiểm tra thủ công dễ xảy ra lỗi. Tích hợp kiểm tra tương phản tự động vào quy trình phát triển. Dùng các công cụ như Axe hoặc Lighthouse trong CI để phát hiện các bổ sung màu mới không đạt ngưỡng WCAG. Trình Kiểm Tra Tương Phản cho phép nhanh chóng xác minh cặp màu nền/chữ so với tất cả các cấp độ WCAG — dán bất kỳ cặp hex nào và xem tỷ lệ ngay lập tức.
Kiểm Thử với Nội Dung Thực
Lỗi chế độ tối thường xuất hiện trên trang có nội dung động: hình ảnh do người dùng tải lên, nhúng từ bên thứ ba, biểu đồ và bản đồ. Kiểm thử với mẫu nội dung thực tế, không chỉ thư viện component của design system trong môi trường cô lập.
Kiểm Thử Ở Cấp Độ OS
Sau khi xác minh qua giả lập DevTools, hãy kiểm thử với OS thực sự được đặt ở chế độ tối. Media query prefers-color-scheme kích hoạt dựa trên cài đặt OS, và một số trình duyệt hoạt động hơi khác tùy thuộc vào cài đặt là thật hay được giả lập. Cũng kiểm tra quá trình chuyển đổi: chuyển chế độ khi trang đang mở và xác nhận không có layout shift hay artifact render.
Danh Sách Kiểm Tra Cạm Bẫy Phổ Biến
- Giá trị hex được hard-code trong CSS component thay vì dùng biến — tìm kiếm mã hex thô trong stylesheet và thay thế bằng biến
- Icon SVG với
fill="#000000"được hard-code — đổi thànhfill="currentColor" - Component bên thứ ba không tuân theo
data-theme— bọc chúng trong một lớp CSS có phạm vi - Thuộc tính
color-schemechưa được đặt — thêmcolor-scheme: light darkvào:rootđể chrome trình duyệt (scrollbar, form control) cũng điều chỉnh - Thiếu
<meta name="color-scheme">trong<head>— thêm vào để trình duyệt có thể áp dụng màu nền đúng trước khi CSS tải
<meta name="color-scheme" content="light dark">
:root {
color-scheme: light dark;
}
Bổ sung nhỏ này khiến scrollbar gốc, date picker và các form control được OS render tự động chuyển sang biến thể tối — chi tiết mà nhiều triển khai bỏ qua.
Những Điểm Chính
- Định nghĩa tất cả màu sắc như CSS custom properties trên
:rootvà ghi đè chúng cho chế độ tối bằng[data-theme="dark"]. Style component chỉ tham chiếu biến, giúp chuyển đổi chủ đề không cần nỗ lực sau khi bảng màu được thiết lập. - Dùng
prefers-color-scheme: darknhư mặc định tự động cho người dùng đã đặt OS ở chế độ tối. Thêm lớp JavaScript toggle với khả năng lưu trữlocalStoragelên trên cho người dùng muốn ghi đè. - Chạy script chống flash trong
<head>trước khi CSS tải để ngăn flash chủ đề sai ở lần render đầu tiên. - Màu chế độ tối không phải màu sáng bị đảo ngược — giảm tương phản cực đoan, dùng nền sáng hơn để truyền đạt elevation, và giảm nhẹ độ bão hòa của điểm nhấn thương hiệu để tránh cảm giác neon gay gắt.
- Xác minh mọi cặp văn bản/nền bằng Trình Kiểm Tra Tương Phản và dùng Trình Tạo Shade để tìm shade phù hợp của mỗi màu thương hiệu cho cả hai chủ đề.
- Thêm
color-scheme: light darkvà thẻ<meta>tương ứng để các phần tử UI trình duyệt gốc (scrollbar, input) cũng tự động chuyển đổi.