Dark mode chuyển từ feature optional sang feature mặc định trong UI 2024-2026 — đa số OS hiện nay (iOS từ 13, macOS từ Mojave, Windows 10+, Android 10+) đều có toggle system-level và truyền signal qua CSS media query prefers-color-scheme: dark. Bài này coverage cách build dark mode đúng kỹ thuật: tránh pure black, dùng surface elevation thay shadow, desaturate accent 10-20%, implementation qua CSS variable + prefers-color-scheme + manual toggle, và 6 sai lầm phổ biến.
Theo survey EarthWeb 2024, 81,9% user có xài dark mode trên ít nhất 1 device và 35% bật dark mode mặc định toàn thời gian. Site không có dark mode bị skip bởi user dùng OLED screen (tiêu thụ pin cải thiện vì light mode sáng), user làm đêm trong môi trường tối, và user nhạy cảm sáng có eye strain, migraine, light sensitivity.
4 lý do dark mode không còn optional năm 2026
Dark mode không chỉ là trend thẩm mỹ. Có 4 lý do kỹ thuật và sức khỏe mà user thực sự cần dark mode — site không support đang bỏ qua phần lớn user base hiện đại.
Eye strain và tiết kiệm pin OLED
- Eye strain trong môi trường tối: light mode trong phòng tối khiến mắt phải adjust constant giữa screen sáng và surrounding tối — gây fatigue, dry eye, headache sau 30-60 phút — American Optometric Association recommend dark mode cho người làm computer 6h+/ngày buổi tối.
- Tiết kiệm pin OLED 30-60%: màn hình OLED (iPhone từ X, Galaxy S, Pixel, MacBook Pro 14/16) có pixel độc lập — pixel đen tắt hoàn toàn không tiêu pin — test thực tế YouTube dark mode trên Pixel giảm battery drain so với light mode.
Sensory accessibility và brand modern signal
- Sensory accessibility cho user light sensitive: khoảng 10% dân số có một dạng photophobia gồm migraine sufferer, autism spectrum, một số medication side effect — light mode quá sáng gây đau hoặc trigger migraine cho nhóm này.
- Brand modern signal cho audience tech-savvy: Twitter/X, GitHub, Stripe, Linear, Notion, Vercel, Apple, YouTube, Slack, Discord đều support dark mode — không có dark mode signal “site lỗi thời” đặc biệt với B2B SaaS, dev tool, fintech.
Dark mode đúng — không phải invert color
Dark mode KHÔNG phải đơn giản là invert color (đảo ngược). Một số implementation lười dùng CSS filter: invert(1) trên body — kết quả thường tệ: image bị inverted (gương mặt người xanh, food xám), brand color sai lệch, contrast hỏng.
Material Design 3 và Apple HIG đều tránh pure black
Dark mode đúng là thiết kế lại palette với principle riêng. Pattern Material Design 3 định nghĩa dark theme đúng — thay vì invert, dùng “dark surface” tone từ #121212 hoặc tương đương.
Apple HIG cũng tương tự với “elevated dark gray” palette, không pure black. Cả 2 hệ thống lớn nhất ngành đều có chung quan điểm — đây là consensus đã được test trên hàng tỷ device user.
Vì sao tránh pure black và pure white
Pure black #000 + pure white #fff có contrast cực cao 21:1 gây “halation” — chữ trắng “lóe” ra khỏi nền đen, mỏi mắt sau 30 phút đọc. Off-black (#0f172a, #18181b, #121212) + off-white (#f1f5f9, #fafafa) có contrast vẫn cao 15-18:1 nhưng êm hơn nhiều.
Đây là lý do mọi design system mature 2026 (Material 3, Carbon, Atlassian) đều dùng off-black cho dark mode base. Audit dark mode bắt đầu bằng check value của --surface-base — nếu là #000000, đó là first red flag cần fix.
Surface elevation thay shadow để tách layer
Surface elevation là khái niệm cốt lõi của dark mode đúng. Thay vì dùng shadow để tách layer (như light mode), dark mode tách layer bằng tone sáng dần — shadow trên dark bg gần như invisible nên không hữu dụng.
- Surface 0 page bg: #0f172a — tầng đáy, không có gì dưới nữa, dùng cho body và section background mặc định.
- Surface 1 card content: #1e293b sáng hơn surface 0 khoảng 5-10% luminance — dùng cho card, sidebar, content container subtle.
- Surface 2 elevated modal: #334155 sáng hơn surface 1 — dùng cho elevated card, modal, dropdown khi cần “nổi” trên card thường.
- Surface 3 popover tooltip: #475569 sáng nhất trong scale — dùng cho popover, tooltip, layer top-most không có gì trên nữa.
Build palette dark mode — surface, text, accent
Build dark mode palette bắt đầu từ 3 nhóm token: surface (background các level), text (foreground các level), và accent (CTA, link, semantic color). Mỗi nhóm có 4-6 shade đáp ứng nhu cầu UI thực tế.
Surface tokens và text tokens chuẩn
- –surface-base #0f172a: page background — KHÔNG dùng #000 để tránh halation, off-black là mức lý tưởng.
- –surface-1 đến –surface-3: #1e293b cho card, #334155 cho modal, #475569 cho popover — mỗi tầng sáng hơn 5-10% so với tầng dưới.
- –text-base #f1f5f9: body text primary — KHÔNG dùng #fff tinh khiết, off-white êm mắt hơn cho long reading.
- –text-muted #cbd5e1: body secondary, helper text — contrast với surface-base khoảng 12:1, vẫn AAA cho readability cao.
- –text-subtle #94a3b8: caption, timestamp — contrast 7:1 đạt AAA cho text large hoặc 4,5:1 AA cho text thường.
Accent tokens — desaturate 10-20% cho dark
Accent color trong dark mode cần desaturate 10-20% so với light mode. Light mode brand blue #2563eb (saturated) đổi sang dark mode #60a5fa hoặc #3b82f6 (lighter, less saturated) — lý do là saturated color trên dark bg “vibrate” mạnh và uncomfortable cho mắt.
Pattern token reference Tailwind/Radix UI cho 4 màu phổ biến:
- Blue: light
blue-600 #2563ebđổi sang darkblue-400 #60a5fa— contrast với surface-base 5,8:1 đạt AA. - Red destructive: light
red-600 #dc2626đổi sang darkred-400 #f87171— vẫn nhận diện là “danger” nhưng êm hơn. - Green success: light
emerald-600 #059669đổi sang darkemerald-400 #34d399— pop trên dark bg mà không gắt. - Orange brand warm: light
orange-600 #ea580cđổi sang darkorange-400 #fb923c— giữ hue warm, giảm saturation.
Implementation CSS variable + prefers-color-scheme
Implementation dark mode hiện đại dùng CSS custom properties + media query prefers-color-scheme. Pattern này không cần JS cho default behavior (follow OS preference) và gói trong vài chục line CSS.
CSS variable cơ bản với 2 tầng token
:root {
--surface-base: #ffffff;
--surface-1: #f8fafc;
--text-base: #0f172a;
--text-muted: #475569;
--accent: #2563eb;
}
@media (prefers-color-scheme: dark) {
:root {
--surface-base: #0f172a;
--surface-1: #1e293b;
--text-base: #f1f5f9;
--text-muted: #cbd5e1;
--accent: #60a5fa;
}
}
body {
background: var(--surface-base);
color: var(--text-base);
}
Pattern này: token mặc định là light, override khi OS prefers dark. CSS variable cascade tự động — mọi element dùng var(--text-base) tự switch theo mode mà không cần thêm JS.
Manual toggle persistence với localStorage
Để hỗ trợ manual toggle (user click button switch), thêm class hoặc data-attribute trên html + JS persist localStorage. Pattern 3 mode: system follow OS, light force light, dark force dark — toggle button cycle qua 3 mode.
function setMode(mode) {
document.documentElement.dataset.theme = mode;
localStorage.setItem('theme', mode);
}
const saved = localStorage.getItem('theme');
const prefers = window.matchMedia('(prefers-color-scheme: dark)').matches;
setMode(saved || (prefers ? 'dark' : 'light'));
Avoid FOUC với inline script trong head
Flash of Unstyled Content xảy ra khi set theme bằng JS sau page load — user thấy flash light đổi dark trong 100-300ms. Fix bằng inline script trong <head> chạy trước CSS load.
Script blocking 1-2ms — chấp nhận được vì tránh FOUC khó chịu. Pattern Next.js dùng next-themes library handle FOUC tự động — chi tiết kết hợp với design token pipeline cho theme system multi-brand.
Component cần điều chỉnh đặc biệt khi switch dark
Một số component cần điều chỉnh đặc biệt khi switch dark mode, không thể chỉ đổi color token. 5 group component dưới đây cần audit thêm khi build dark mode từ scratch.
Không phải mọi component swap token là xong
Đa số dev junior nghĩ dark mode chỉ là swap CSS variable — switch class, thay value, done. Thực tế shadow vô hình, image bị “miếng vá”, form input mất hút trong page bg — đều cần adjustment riêng vượt qua token swap.
- Image và icon đa dạng tình huống: photo (sản phẩm, headshot) không cần thay đổi work cả 2 mode — logo PNG bg trắng sẽ “đứng như miếng vá” trên dark, fix bằng SVG transparent hoặc 2 version — icon SVG dùng
currentColorđể tự inherit text color. - Shadow tăng opacity hoặc bỏ: shadow rgba(0,0,0,0.1) gần invisible trên dark — fix bằng surface elevation (tone sáng hơn) thay shadow, hoặc shadow với opacity 0,4-0,6.
- Border opacity thay vì màu cứng: border light gray #e5e7eb trong light mode đổi sang dark tone #334155 hoặc 1px white opacity 0,1 — quá đậm tạo “stuck” feel, quá nhạt không thấy.
- Glassmorphism work tốt hơn: pattern frosted glass (semi-transparent + blur) work tốt trong dark mode — surface bg
rgba(15, 23, 42, 0.7)+backdrop-filter: blur(16px)— Mac OS Big Sur dark dùng pattern này khắp nơi. - Form input dùng surface-1 không surface-base: input bg trong dark mode nên surface-1 sáng hơn page bg để user thấy field clearly — Stripe dark pattern: input bg #1e293b trên page #0f172a, focus border xanh accent.
Brand color trong dark mode — không cứng tone
Một số brand insist “brand color phải giữ nguyên trong dark mode”. Đây là sai lầm phổ biến — brand color saturated (vd brand orange #f97316) trên light có contrast 4,5:1 OK nhưng trên dark mode #0f172a tăng quá cao 10:1+ và “vibrate” mạnh.
Pattern tone-shift giữ hue đổi tone
Pattern đúng là tone-shift brand color cho dark mode. Brand identity vẫn giữ vì hue (sắc thái) không đổi, chỉ tone (luminance) shift sáng hơn cho dark mode.
- Light brand orange #f97316: saturated, dark tone — phù hợp light mode contrast với trắng.
- Dark brand orange #fb923c: less saturated, lighter tone — phù hợp dark mode contrast với surface-base.
- User tech-savvy nhận ra cùng brand: vì hue cam giữ nguyên, chỉ luminance shift — Apple, Stripe, Linear, Vercel đều dùng pattern này trong dark mode chính thức.
Khi brand guideline cứng “không đổi color”
Đề xuất “dark-mode variant” trong brand guideline mới. Đây là approach pragmatic — brand guideline cập nhật là natural khi medium thay đổi (web, app, AR/VR mới ra).
Coca-Cola, Nike, Apple đều có brand variant cho dark + light + monochrome — không phải single fixed color cứng. Designer nên thuyết phục brand stakeholder rằng “tone-shift” giữ identity thực tế tốt hơn “fixed color” gây discomfort cho user.
6 sai lầm phổ biến trong dark mode
Sau audit hàng chục site có dark mode, có 6 lỗi định kỳ xuất hiện ở 60-70% case. Tránh được 6 lỗi này là build dark mode chất lượng cao đáng tin cậy.
Pattern lỗi định kỳ không phụ thuộc stack
6 lỗi dưới xuất hiện đều ở site React, Vue, WordPress, static HTML — không phụ thuộc framework hay CMS. Đa số là gốc design + handoff không có spec dark mode rõ, không phải lỗi technical implementation.
- Pure black #000 và pure white #fff: halation gây mỏi mắt sau 30 phút — fix dùng off-black (#0f172a, #18181b) + off-white (#f1f5f9, #fafafa).
- Saturated brand color không adjust: vibrate trên dark bg, accessibility issue cho color sensitive user — fix lighter tone của brand color cho dark mode.
- Image background trắng đứng như “miếng vá”: logo PNG bg trắng, screenshot UI light mode trong tutorial — fix SVG transparent + currentColor hoặc cung cấp 2 version asset.
- Shadow vẫn dùng như light mode: shadow rgba(0,0,0,0.1) gần invisible trên dark bg — fix dùng surface elevation thay shadow hoặc tăng shadow opacity 0,4-0,6.
- Focus state vô hình: focus outline blue không đủ contrast với dark bg — fix focus outline accent color (lighter shade) + outline-offset tách rõ với border element — chi tiết tại accessibility thiết kế web.
- Toggle không persist hoặc FOUC: user toggle dark, refresh thấy light flash đổi dark — fix inline script trong head set data-theme trước CSS load, localStorage persist choice.
Test dark mode — checklist 10 mục trước launch
Trước khi launch dark mode, audit checklist 10 mục dưới — đa số issue được catch ở stage này. Mỗi mục check 2-5 phút, tổng 30-50 phút cho site mid-size.
Test cross-platform và cross-device là bắt buộc
Test cross-platform gồm Chrome + Safari + Firefox + Edge — mỗi browser render CSS variable hơi khác cho fallback case. Test cross-device gồm iPhone OLED + Android LCD + Mac + Windows.
Đặc biệt iPhone OLED render dark mode khác Android LCD — cùng hex code có thể “khác” perception do màn hình OLED có pixel độc lập. Pattern audit: chụp screenshot trên 4 device chính, so sánh side-by-side trước go-live.
- Toggle work cả 3 mode: system, light, dark — test refresh, persist correct theo lựa chọn cuối cùng.
- Không FOUC khi refresh: refresh page khi đang dark, không thấy flash light xuất hiện trong 100-300ms đầu.
- Contrast text/bg ≥4,5:1: test Chrome DevTools color picker cho 5-10 element text quan trọng.
- Focus state visible: keyboard Tab qua page, mọi element có outline accent đủ contrast với surface tại vị trí đó.
- Image không “miếng vá”: logo, hero image, screenshot blend với dark bg — không có vùng trắng đứng riêng.
- Form input visible: bg input rõ khác page bg, focus border accent — không “blend” mất hút.
- Modal có backdrop: overlay đủ tách modal khỏi page underneath, opacity 40-60% hoặc backdrop-filter blur.
- Code block đủ contrast: font monospace trên surface-1 đủ readability cho dev xem code snippet trong tutorial.
- Hover state có phản hồi: button hover đổi bg hoặc shadow — user biết hover được nhận.
- Print mode override về light:
@media printreset về light mode để in giấy đúng, không tốn mực đen.
Câu hỏi thường gặp về dark mode
Dark mode có ảnh hưởng SEO không?
Không trực tiếp. Google không rank theo color scheme — không có signal nào trong Search Console đo dark vs light mode preference.
Nhưng gián tiếp qua user engagement: site có dark mode được user tech-savvy ở lâu hơn, time-on-page cao hơn, bounce rate thấp hơn — engagement metric tốt cho mọi site theo Google rank algorithm.
Có bắt buộc support 3 mode system/light/dark không?
Best practice là 3 mode. Default “system” follow OS, plus user override “light” hoặc “dark” — user có thể prefer khác OS (vd OS dark nhưng work app light cho clarity).
2 mode (chỉ light/dark) cũng OK nếu budget tight, nhưng không follow system khiến user phải toggle mỗi lần OS đổi — friction nhỏ nhưng cộng dồn 50+ lần/tháng.
Tailwind CSS có support dark mode không?
Có. Tailwind có dark: variant prefix — vd bg-white dark:bg-slate-900 tự switch theo mode.
Config darkMode: 'class' cho manual toggle hoặc darkMode: 'media' follow prefers-color-scheme. Pattern phổ biến: dùng class với manual toggle, fallback prefers-color-scheme cho first load.
Material UI và shadcn có dark mode built-in không?
Có. shadcn/ui dùng CSS variable + class chiến lược pattern manual toggle — Material UI v5+ có ThemeProvider với mode toggle.
Radix UI primitives độc lập theme — pair với CSS variable hoặc styled-system của bạn. Đa số design system 2026 build dark mode từ đầu, không phải retrofit về sau.
Dark mode có giảm conversion landing page không?
Đa số test A/B cho thấy conversion không khác đáng kể giữa light vs dark — phụ thuộc audience cụ thể.
Site B2B SaaS dark mode default tăng conversion 5-10% (audience tech). Site B2C lifestyle, e-commerce light default tốt hơn 5-10%.
Best practice: cung cấp toggle, default theo audience research của riêng dự án.
Tài nguyên và bước tiếp theo
Dark mode năm 2026 không còn là “nice to have” mà là tiêu chuẩn UI cho mọi site phục vụ audience hiện đại — đặc biệt SaaS, dev tool, fintech, social media. Các bài liên quan trong cụm UI/UX fundamentals:
- Hierarchy thị giác — 5 đòn bẩy + F/Z-pattern dẫn dắt mắt user — contrast trong dark mode khác light, hierarchy thay đổi theo.
- Gestalt trong thiết kế — 6 nguyên tắc psychology cho UI 2026 — figure/ground trong dark dùng surface tone thay shadow.
- Accessibility thiết kế web — WCAG 2.2 POUR checklist — focus state visible trong dark mode bắt buộc cho a11y.
- Design token là gì — 3 cấp token và pipeline Style Dictionary — theme system multi-mode cần token architecture chuẩn.
Cần đội Web22 build dark mode đúng kỹ thuật cho website của bạn? Đội UI Designer Web22 — wireframe + handoff Dev Mode bao gồm pattern dark mode trong gói: tier mid 15-30 triệu (landing + 3-5 page chính 2 mode), full design system 25-40 triệu (token light + dark cho 6-10 component cốt lõi, document handoff cho dev team).


