React'te Dark Mode Nasıl Yapılır? FOUC Olmadan Profesyonel Çözüm
React'te dark mode nasıl eklenir? CSS variables, localStorage, prefers-color-scheme ve FOUC (beyaz ekran flash) sorununu çözen, production-ready dark...
Sorun: Sayfa Yüklenirken Beyaz Ekran Yanıp Sönüyor
Dark mode eklediniz, localStorage’a kaydettiniz — ama sayfa her yüklendiğinde önce beyaz, sonra siyah oluyor. Bu soruna FOUC (Flash of Unstyled Content) denir ve doğru çözülmezse kullanıcı deneyimini mahveder.
Dark Mode’un 3 Katmanı
Profesyonel bir dark mode implementasyonu 3 katmandan oluşur:
1. CSS Variables → Renkleri tanımla
2. Head Script → FOUC'u engelle (render-blocking)
3. React Toggle → Kullanıcı etkileşimi
Katman 1: CSS Variables ile Tema Sistemi
Tüm renkleri CSS variable olarak tanımlayın. Tema değiştirmek = sadece class değiştirmek.
/* global.css */
:root {
--color-bg: #ffffff;
--color-text: #1a1a2e;
--color-text-muted: #6b7280;
--color-bg-muted: #f1f3f5;
--color-border: #e5e7eb;
--color-accent: #3b82f6;
}
[data-theme="dark"] {
--color-bg: #0d1117;
--color-text: #e6edf3;
--color-text-muted: #8b949e;
--color-bg-muted: #21262d;
--color-border: #30363d;
--color-accent: #58a6ff;
}
body {
background-color: var(--color-bg);
color: var(--color-text);
transition: background-color 0.2s, color 0.2s;
}
Neden class yerine data-theme attribute?
- CSS specificity sorunları yaratmaz
- JavaScript’te
dataset.themeile kolay erişim - Nested tema desteği (bir bölüm farklı tema)
Katman 2: FOUC’u Engelleyen Head Script
Bu, en kritik adım. React mount olmadan ÖNCE temayı uygularsınız:
<!-- index.html veya layout'un <head> bölümüne -->
<script>
(function() {
var stored = localStorage.getItem('theme');
var system = window.matchMedia('(prefers-color-scheme: dark)').matches;
if (stored === 'dark' || (!stored && system)) {
document.documentElement.setAttribute('data-theme', 'dark');
}
})();
</script>
Neden çalışır?
- Bu script render-blocking’tir (async/defer yok)
- Tarayıcı sayfa çizmeden ÖNCE çalıştırır
- React hiç mount olmadan tema doğru uygulanır
- Sonuç: sıfır beyaz flash
Katman 3: React Toggle Bileşeni
// useTheme.ts
import { useState, useEffect } from 'react';
type Theme = 'light' | 'dark';
export function useTheme() {
const [theme, setTheme] = useState<Theme>(() => {
if (typeof window === 'undefined') return 'light';
return (localStorage.getItem('theme') as Theme) ||
(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
});
useEffect(() => {
document.documentElement.setAttribute('data-theme', theme);
localStorage.setItem('theme', theme);
}, [theme]);
// Sistem tercihi değişirse otomatik güncelle
useEffect(() => {
const mq = window.matchMedia('(prefers-color-scheme: dark)');
const handler = (e: MediaQueryListEvent) => {
if (!localStorage.getItem('theme')) {
setTheme(e.matches ? 'dark' : 'light');
}
};
mq.addEventListener('change', handler);
return () => mq.removeEventListener('change', handler);
}, []);
const toggle = () => setTheme(t => t === 'dark' ? 'light' : 'dark');
return { theme, toggle };
}
// ThemeToggle.tsx
import { useTheme } from './useTheme';
export function ThemeToggle() {
const { theme, toggle } = useTheme();
return (
<button
onClick={toggle}
aria-label={`${theme === 'dark' ? 'Açık' : 'Koyu'} temaya geç`}
>
{theme === 'dark' ? '☀️' : '🌙'}
</button>
);
}
Öncelik Sırası — Ne Zaman Hangi Tema?
1. localStorage'da kayıtlı tercih varsa → onu kullan
2. Yoksa → sistem tercihine bak (prefers-color-scheme)
3. İkisi de yoksa → varsayılan light
SSR/SSG Uyumluluğu (Next.js, Astro)
SSR’da window ve localStorage yoktur. Çözüm:
// Astro'da: BaseHead.astro
<script is:inline>
(function() {
var s = localStorage.getItem('theme');
var d = window.matchMedia('(prefers-color-scheme: dark)').matches;
document.documentElement.setAttribute('data-theme',
s === 'dark' || (!s && d) ? 'dark' : 'light');
})();
</script>
// Next.js'te: layout.tsx
<html suppressHydrationWarning>
<head>
<script dangerouslySetInnerHTML={{ __html: `
(function() {
var s = localStorage.getItem('theme');
var d = window.matchMedia('(prefers-color-scheme: dark)').matches;
document.documentElement.setAttribute('data-theme',
s === 'dark' || (!s && d) ? 'dark' : 'light');
})();
`}} />
</head>
Sık Yapılan Hatalar
useEffectiçinde tema uygulamak: React mount gecikmesi → beyaz flashbodyclass yerine inline style: Specificity sorunları yaratır- Transition ilk yüklemede: İlk yüklemede
transition: noneolmalı, sonra etkinleştirin - Sistem tercihini dinlememek: Kullanıcı OS’ta dark mode’u açarsa siteniz de değişmeli
İlgili Yazılar
- Skeleton Loading ve UX — Dark mode ile birlikte
- Responsive Tablo Tasarımı — Tablo renkleri
- React Modular Dashboard — Tema sistemi entegrasyonu
Bu rehber MDN’nin prefers-color-scheme ve React’in useEffect dokümantasyonuna dayanarak hazırlanmıştır.