---
title: "React'te Dark Mode Nasıl Yapılır? FOUC Olmadan Profesyonel Çözüm"
description: "React'te dark mode nasıl eklenir? CSS variables, localStorage, prefers-color-scheme ve FOUC (beyaz ekran flash) sorununu çözen, production-ready dark..."
date: 2026-04-15
category: ui-ux
tags: ["react", "dark-mode", "css", "ui-ux", "localStorage", "typescript"]
url: https://mikroerp.dev/blog/react-dark-mode-fouc-cozumu/
---

## 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.

```css
/* 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.theme` ile 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:

```html
<!-- 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

```tsx
// 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 };
}
```

```tsx
// 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:

```tsx
// 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>
```

```tsx
// 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

1. **`useEffect` içinde tema uygulamak**: React mount gecikmesi → beyaz flash
2. **`body` class yerine inline style**: Specificity sorunları yaratır
3. **Transition ilk yüklemede**: İlk yüklemede `transition: none` olmalı, sonra etkinleştirin
4. **Sistem tercihini dinlememek**: Kullanıcı OS'ta dark mode'u açarsa siteniz de değişmeli

## İlgili Yazılar

- [Skeleton Loading ve UX](/blog/react-skeleton-loading-ux-optimizasyonu/) — Dark mode ile birlikte
- [Responsive Tablo Tasarımı](/blog/react-responsive-tablo-tasarimi/) — Tablo renkleri
- [React Modular Dashboard](/blog/react-modular-dashboard-mimarisi/) — Tema sistemi entegrasyonu

---

*Bu rehber MDN'nin [prefers-color-scheme](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme) ve React'in [useEffect](https://react.dev/reference/react/useEffect) dokümantasyonuna dayanarak hazırlanmıştır.*