Mở đầu — vì sao bài này quan trọng
Hãy thử để ý: buổi tối, khi bạn nằm trên giường lướt điện thoại, ứng dụng nào làm mắt bạn dễ chịu hơn — một trang trắng lóa hay một giao diện nền tối dịu mắt? Câu trả lời gần như ai cũng giống nhau. Đó chính là lý do dark mode (chế độ tối) đã đi từ một tính năng "hay ho" trở thành kỳ vọng mặc định của người dùng.
Theo các khảo sát của Android Authority và Polar về thói quen sử dụng, hơn 80% người dùng smartphone bật dark mode ở cấp hệ điều hành (OS-level) ít nhất một phần thời gian, và một tỷ lệ lớn để mặc định "luôn bật". Ở Việt Nam, nơi phần lớn người dùng truy cập web qua điện thoại và thường lướt vào buổi tối sau giờ làm, con số này càng có ý nghĩa. Nếu website hay sản phẩm của bạn ép người ta nhìn nền trắng chói lúc 11 giờ đêm, bạn đang tạo ra ma sát không cần thiết.
Nhưng đây là điều nhiều designer mới hiểu sai: dark mode không phải chỉ là đảo ngược màu — lấy nền trắng đổi thành đen, chữ đen đổi thành trắng. Làm như vậy thường cho ra kết quả tệ hơn cả khi không có dark mode. Một dark mode tốt là một hệ thống màu được thiết kế riêng, tôn trọng cách mắt người nhìn trong điều kiện ánh sáng thấp. Bài này sẽ dạy bạn tư duy và quy trình để làm dark mode đúng cách — thứ mà bạn sẽ dùng đi dùng lại trong gần như mọi dự án về sau.
Khái niệm cốt lõi
Vì sao người dùng cần dark mode?
Có ba động lực chính, và hiểu rõ chúng giúp bạn ra quyết định thiết kế đúng:
- Sở thích cá nhân (user preference): Một bộ phận lớn người dùng đơn giản là thích giao diện tối hơn. Nó gợi cảm giác hiện đại, tập trung, "đỡ ồn ào". Khi hệ điều hành cho phép chọn, người ta sẽ chọn — và họ kỳ vọng app cũng theo lựa chọn đó.
- Tiết kiệm pin trên màn hình OLED/AMOLED: Với màn OLED (phổ biến trên Samsung Galaxy, iPhone từ dòng X trở lên, nhiều điện thoại Android tầm trung), mỗi điểm ảnh tự phát sáng. Pixel đen gần như tắt hẳn, không tiêu thụ điện. Google từng công bố ở hội nghị Android Dev Summit rằng giao diện tối trên OLED có thể giảm tiêu thụ pin đáng kể ở mức độ sáng cao. Lưu ý: điều này không đúng với màn LCD truyền thống, vì LCD dùng đèn nền chiếu sáng toàn bộ tấm nền.
- Giảm mỏi mắt khi đọc ban đêm: Trong môi trường tối, một màn hình trắng phát ra lượng ánh sáng lớn khiến đồng tử co lại liên tục, gây mỏi và lóa. Nền tối phát ra ít ánh sáng hơn, dịu hơn trong bối cảnh ánh sáng môi trường thấp.
Quy tắc số 1: Đừng dùng màu đen tuyệt đối
Đây là sai lầm kinh điển. Nhiều người nghĩ dark mode nghĩa là nền #000000 (đen thuần). Nhưng đen tuyệt đối tạo độ tương phản quá gắt với chữ trắng, gây hiện tượng "rung" và mỏi mắt. Material Design của Google khuyến nghị màu nền dark mode là #121212 — một màu xám rất tối, gần đen nhưng không phải đen.
Lý do sâu xa: trong dark mode, ta dùng độ sáng (elevation) để thể hiện chiều sâu. Bề mặt càng "nổi lên" gần người dùng (như card, modal) thì càng sáng hơn một chút so với nền, chứ không phải tối hơn. Nếu nền đã là đen thuần thì không còn chỗ để làm các lớp tối hơn — bạn mất một công cụ phân lớp quan trọng.
Độ tương phản và bóng đổ hoạt động ngược lại
Trong light mode, ta tạo chiều sâu bằng bóng đổ (shadow) — vật càng nổi thì bóng càng đậm. Trong dark mode, bóng đổ gần như vô hình vì nền đã tối. Thay vào đó, ta dùng lớp phủ sáng (overlay): bề mặt nổi cao hơn thì có một lớp trắng mờ phủ lên, làm nó sáng dần. Material Design định nghĩa cả một thang elevation: 0dp là #121212, 1dp sáng hơn chút, 24dp (như dialog) sáng nhất.
Đừng dùng màu bão hòa quá cao
Màu rực rỡ (saturated) như đỏ tươi, xanh dương đậm trên nền tối sẽ "chấn động" (vibrate) và khó nhìn. Trong dark mode, ta nên giảm độ bão hòa của màu thương hiệu (brand color), dùng các sắc độ nhạt hơn, dịu hơn (desaturated). Ví dụ màu primary xanh #1A73E8 của light mode có thể chuyển thành #8AB4F8 nhạt hơn trong dark mode để đảm bảo dễ đọc và đạt chuẩn tương phản.
Tôn trọng prefers-color-scheme
Trình duyệt hiện đại cho phép đọc cài đặt hệ thống của người dùng qua media query prefers-color-scheme: dark. Đây là cách tử tế nhất: nếu người dùng đã chọn dark mode ở cấp OS, website tự động hiển thị tối ngay từ lần đầu, không cần họ phải bấm gì. Kết hợp thêm một nút toggle để người dùng ghi đè lựa chọn nếu muốn.
Tình huống thực tế
Ví dụ 1: YouTube và bài học "đảo màu là chưa đủ"
Khi YouTube triển khai dark mode (khoảng 2017–2018), họ không đơn giản đảo nền trắng thành đen. Họ chọn nền xám rất tối #0F0F0F, giữ thumbnail video với độ sáng đầy đủ (vì hình ảnh content không nên bị làm tối), và điều chỉnh màu đỏ thương hiệu cho phù hợp. Quan trọng nhất: chữ chính không phải trắng tinh #FFFFFF mà là trắng hơi ngả xám để giảm độ chói.
Bài học rút ra: Content (ảnh, video, ảnh sản phẩm) nên giữ nguyên độ sáng — không "tối hóa" chúng. Chỉ chrome của giao diện (nền, thanh điều hướng, panel) mới chuyển sang tối. Nếu bạn làm một sàn thương mại điện tử như Shopee hay Tiki, ảnh sản phẩm phải luôn rực rỡ để bán được hàng; chỉ phần khung giao diện xung quanh chuyển tối.
Ví dụ 2: Một startup fintech Việt và lỗi tương phản chữ
Giả sử một startup ví điện tử ở TP.HCM ra mắt dark mode cho app. Đội thiết kế lấy màu chữ phụ (secondary text) #666666 vốn hoạt động tốt trên nền trắng và đặt nó lên nền tối #121212. Kết quả: tỷ lệ tương phản chỉ khoảng 2.4:1 — chữ gần như chìm vào nền, người dùng lớn tuổi than phiền "không đọc được số dư". Bộ phận hỗ trợ nhận hàng trăm phản hồi trong tuần đầu.
Họ phải sửa lại: chữ chính dùng trắng ở khoảng 87% opacity, chữ phụ ở 60% opacity, chữ bị vô hiệu (disabled) ở 38% — đúng theo khuyến nghị của Material Design. Sau khi sửa, tỷ lệ tương phản của chữ phụ đạt trên 4.5:1, đạt chuẩn WCAG AA.
Bài học rút ra: Màu chữ trong light mode không mang sang dark mode được. Bạn phải tính lại độ tương phản cho từng cặp màu nền–chữ. Đây cũng là lý do bài 6 (Color Accessibility & WCAG Contrast) là nền tảng bắt buộc trước khi làm dark mode tử tế.
Ví dụ 3: Một trang tin tức và vấn đề "lóa trắng" khi chuyển trang
Một tờ báo điện tử thêm dark mode bằng JavaScript: khi trang tải xong, script kiểm tra localStorage rồi mới thêm class .dark. Vấn đề là trong khoảnh khắc trước khi JS chạy, người dùng thấy một "tia chớp trắng" (flash of unstyled content) chói mắt giữa đêm khuya — đúng vào lúc dark mode cần phát huy tác dụng nhất. Độc giả phàn nàn dữ dội.
Họ khắc phục bằng cách đặt một đoạn script nhỏ chạy ngay trong <head>, trước khi body render, để gắn class dark ngay lập tức; đồng thời đặt color-scheme: dark trong CSS để trình duyệt biết áp nền tối cho cả thanh cuộn và các phần tử mặc định.
Bài học rút ra: Triển khai dark mode không chỉ là chuyện màu sắc mà còn là chuyện kỹ thuật về thời điểm áp dụng. Một dark mode đẹp nhưng lóe trắng lúc tải vẫn là trải nghiệm hỏng.
Hướng dẫn từng bước
Đây là quy trình thực tế để thêm dark mode cho một dự án, từ thiết kế đến code:
Bước 1 — Xây bảng màu dạng token, không hardcode. Đừng viết thẳng color: #333 khắp nơi. Thay vào đó định nghĩa các biến ngữ nghĩa (semantic tokens) như --color-bg, --color-surface, --color-text, --color-text-muted, --color-primary, --color-border. Trong dark mode bạn chỉ cần đổi giá trị các biến này, toàn bộ giao diện tự cập nhật. Đây chính là lý do hiểu về CSS Variables (bài 23) và Design Tokens (bài 39) là điều kiện tiên quyết.
Bước 2 — Thiết kế bảng màu dark riêng. Với mỗi token, chọn giá trị dark mode:
- Nền:
#121212(không phải#000). - Surface/card: sáng hơn nền một chút, ví dụ
#1E1E1E. - Chữ chính: trắng ở ~87% opacity, ví dụ
rgba(255,255,255,0.87). - Chữ phụ: ~60% opacity.
- Màu primary: bản desaturated, nhạt hơn so với light mode.
color-scheme. Thêm color-scheme: light dark; vào :root. Điều này báo cho trình duyệt biết trang hỗ trợ cả hai chế độ, giúp các thành phần mặc định (form, scrollbar) hiển thị đúng tông.Bước 4 — Dùng prefers-color-scheme làm mặc định. Bọc bảng token dark trong media query:
@media (prefers-color-scheme: dark) {
:root { --color-bg: #121212; --color-text: rgba(255,255,255,0.87); / ... / }
}
Bước 5 — Thêm nút toggle cho phép ghi đè. Cho người dùng ba lựa chọn: Sáng / Tối / Theo hệ thống. Lưu lựa chọn vào localStorage, và gắn một thuộc tính như data-theme="dark" lên thẻ <html>. CSS sẽ ưu tiên [data-theme] nếu có, ngược lại rơi về prefers-color-scheme.
Bước 6 — Chống flash trắng khi tải. Đặt một đoạn script đồng bộ trong <head> để đọc localStorage và gắn data-theme trước khi nội dung render.
Bước 7 — Kiểm tra tương phản và kiểm thử content. Chạy mọi cặp màu qua công cụ kiểm tra contrast (như WebAIM Contrast Checker), đảm bảo đạt tối thiểu 4.5:1 cho chữ thường. Kiểm tra ảnh, illustration, biểu đồ, bóng đổ, và border trong cả hai chế độ.
Lỗi thường gặp & mẹo
- Dùng đen tuyệt đối
#000: Gây mỏi mắt và mất khả năng phân lớp elevation. Luôn dùng xám rất tối làm nền. - Đảo ngược màu một cách mù quáng: Lấy giá trị light mode rồi
invert(). Điều này phá nát ảnh, logo, và làm màu sai lệch. Hãy thiết kế bảng dark riêng. - Quên màu chữ phụ và border: Border
#dddcủa light mode sẽ biến mất trên nền tối. Border trong dark mode thường là trắng mờ opacity thấp (ví dụrgba(255,255,255,0.12)). - Để ảnh và bóng đổ "lạc quẻ": Bóng đổ đậm thiết kế cho light mode trông như vệt bẩn trên nền tối. Hình minh họa nền trắng sẽ thành ô sáng chói. Cân nhắc phiên bản ảnh riêng hoặc giảm độ sáng nhẹ cho ảnh trang trí (decorative) — nhưng giữ nguyên ảnh nội dung.
- Màu thương hiệu quá rực: Giảm bão hòa cho màu primary/accent để chúng không rung trên nền tối.
- Mẹo về icon trạng thái: Màu thông báo (success xanh lá, error đỏ, warning vàng) cũng cần phiên bản dịu hơn cho dark mode; bản gốc thường quá chói.
- Mẹo kiểm thử nhanh: Mở DevTools, vào tab Rendering, tìm "Emulate CSS prefers-color-scheme" để chuyển qua lại hai chế độ mà không cần đổi cài đặt hệ điều hành.
Bài tập thực hành
- Audit một trang quen thuộc. Chọn một website tiếng Việt bạn hay dùng (báo, sàn TMĐT, blog). Nếu có dark mode, mở DevTools và soi các giá trị màu: nền có phải đen tuyệt đối không? Chữ chính opacity bao nhiêu? Border xử lý thế nào? Ghi lại 3 điều họ làm tốt và 2 điều có thể cải thiện.
- Xây bảng token đôi. Lấy một component bạn đã làm (ví dụ một card từ bài 25). Định nghĩa lại toàn bộ màu của nó bằng CSS variables, rồi tạo bảng giá trị cho cả light và dark mode. Yêu cầu: nền dark không được là
#000, chữ phụ phải đạt tương phản tối thiểu 4.5:1 trên nền dark.
- Triển khai toggle 3 trạng thái. Viết một bộ chuyển đổi Sáng / Tối / Theo hệ thống, lưu lựa chọn vào localStorage, và xử lý chống flash trắng bằng script trong
<head>. Kiểm tra bằng cách reload trang nhiều lần trong điều kiện màn hình tối.
- Kiểm thử tương phản. Chạy mọi cặp màu trong bảng dark của bạn qua WebAIM Contrast Checker. Lập một bảng nhỏ ghi tỷ lệ tương phản từng cặp và đánh dấu cặp nào đạt/không đạt WCAG AA.
Tóm tắt
Dark mode đã trở thành kỳ vọng mặc định, không còn là tính năng phụ. Người dùng cần nó vì sở thích cá nhân, vì tiết kiệm pin trên màn OLED, và vì giảm mỏi mắt khi đọc đêm — đặc biệt đúng với thói quen lướt điện thoại buổi tối của người Việt.
Điều cốt lõi cần nhớ: dark mode không phải là đảo ngược màu. Đó là một hệ thống màu được thiết kế riêng. Đừng dùng đen tuyệt đối — hãy dùng xám rất tối như #121212 để còn chỗ thể hiện chiều sâu bằng elevation. Tính lại độ tương phản cho mọi cặp màu nền–chữ thay vì bê nguyên từ light mode. Giảm bão hòa cho màu thương hiệu. Giữ nguyên độ sáng cho ảnh nội dung, chỉ làm tối phần chrome giao diện.
Về kỹ thuật: dùng CSS variables theo dạng semantic token để dễ chuyển chế độ, tôn trọng prefers-color-scheme làm mặc định, thêm nút toggle để người dùng ghi đè, khai báo color-scheme, và chống flash trắng khi tải. Cuối cùng, luôn kiểm thử tương phản và content trong cả hai chế độ. Làm đúng những điều này, dark mode của bạn sẽ là thứ người dùng yêu thích chứ không phải chịu đựng.