Nguyên tắc gốc: mọi màu đi qua custom property, dark mode chỉ đổi giá trị token chứ không viết lại luật CSS.
:root {
--surface: #ffffff;
--surface-raised: #f4f4f5;
--ink: #18181b;
--ink-muted: #71717a;
--border: #e4e4e7;
color-scheme: light dark; /* form control, thanh cuộn cũng đổi theo */
}
:root[data-theme='dark'] {
--surface: #18181b;
--surface-raised: #27272a;
--ink: #fafafa;
--ink-muted: #a1a1aa;
--border: #3f3f46;
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme='light']) { /* lặp lại bộ token dark */ }
}Ba khối này xử lý đủ ba trạng thái: người dùng chọn sáng, chọn tối, hoặc để theo hệ thống.
Bốn điểm về màu:
1. Không dùng đen tuyệt đối #000 cho nền — tương phản quá gắt gây mỏi mắt và làm bóng đổ mất tác dụng. Dùng #18181b hoặc tương đương.
2. Không dùng trắng tuyệt đối cho chữ vì lý do tương tự.
3. Giảm độ bão hoà ở dark mode — màu rực trên nền tối bị "rung" thị giác.
4. Kiểm tra lại tương phản cho cả hai chế độ; đạt AA ở light không đảm bảo đạt ở dark.
Lưu ý: đọc theme từ localStorage trong useEffect gây nháy trắng một khung hình. Cách xử lý là script đồng bộ trong <head> đặt data-theme trước lần vẽ đầu tiên.
Lưu ý 2: ảnh và biểu đồ nền trắng nổi bật khó chịu trên nền tối — cần bản riêng hoặc filter giảm sáng có kiểm soát.