Nguyên tắc: component không biết màu, chỉ đọc token.
Toàn bộ khác biệt sáng/tối nằm ở một chỗ.
css
:root { --surface: #fff; --ink: #111; }
[data-theme='dark'] { --surface: #111; --ink: #f2f2f2; }
@media (prefers-color-scheme: dark) {
:root:not([data-theme='light']) { --surface: #111; --ink: #f2f2f2; }
}
.card { background: var(--surface); color: var(--ink); }prefers-color-schemelo mặc định theo hệ điều hành.- Thuộc tính
data-themetrên<html>lo lựa chọn thủ công và thắng media query nhờ selector cụ thể hơn. - Chỉ đặt token ngữ nghĩa (
--surface,--ink,--border), đừng đặt--white; nếu không sang dark mode tên biến sẽ nói dối.
Chống nháy (flash of wrong theme): giá trị người dùng chọn nằm trong localStorage, mà React/Next render sau. Cách xử lý phổ biến là chèn một script đồng bộ nhỏ trong <head> đọc localStorage và gán document.documentElement.dataset.theme trước khi trình duyệt vẽ khung hình đầu tiên. Kèm <meta name="color-scheme"> hoặc CSS color-scheme: light dark để form control và thanh cuộn của trình duyệt cũng đổi theo.
Các sắc thái phái sinh nên tính từ token gốc thay vì khai báo tay từng cái: color-mix(in srgb, var(--ink) 12%, transparent) cho nền hover — tự đúng ở cả hai chế độ.