Membuat Dark Mode dengan CSS Variables dan JavaScript: Tutorial Lengkap

Tutorial step-by-step cara membuat fitur dark mode yang smooth menggunakan CSS Variables dan JavaScript. Cocok untuk pemula hingga developer berpengalaman.

15 menit baca Oleh Hilal Technologic
Membuat Dark Mode dengan CSS Variables dan JavaScript: Tutorial Lengkap

🌓 Membuat Dark Mode dengan CSS Variables dan JavaScript

Dark mode sudah menjadi fitur wajib di website modern. Selain memberikan opsi kenyamanan bagi user, dark mode juga bisa menghemat baterai pada perangkat dengan layar OLED. Di tutorial ini, kita akan belajar cara membuat dark mode yang smooth dan maintainable menggunakan CSS Variables dan JavaScript.

🎯 Yang Akan Kita Buat

<!-- Preview hasil akhir -->
<div class="card">
  <h2>Dark Mode Demo</h2>
  <p>Toggle dark mode untuk lihat perubahannya!</p>
  <button onclick="toggleDarkMode()">
    Toggle Dark Mode
  </button>
</div>

📋 Prerequisites

Untuk mengikuti tutorial ini, kamu perlu:

  • Pemahaman dasar HTML, CSS, dan JavaScript
  • Code editor (VS Code, Sublime, dll)
  • Browser modern (Chrome, Firefox, Safari)

🎨 Step 1: Setup CSS Variables

Pertama, kita perlu mendefinisikan CSS variables untuk warna-warna yang akan kita gunakan:

/* style.css */
:root {
  /* Light mode colors */
  --background: #ffffff;
  --text-primary: #333333;
  --text-secondary: #666666;
  --accent: #2563eb;
  --accent-hover: #1d4ed8;
  --border: #e5e7eb;
  --card-bg: #ffffff;
  --card-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

/* Dark mode colors */
[data-theme="dark"] {
  --background: #1a1a1a;
  --text-primary: #ffffff;
  --text-secondary: #a3a3a3;
  --accent: #3b82f6;
  --accent-hover: #60a5fa;
  --border: #2e2e2e;
  --card-bg: #262626;
  --card-shadow: 0 2px 4px rgba(0, 0, 0, 0.4);
}

/* Apply variables */
body {
  background-color: var(--background);
  color: var(--text-primary);
  transition: background-color 0.3s, color 0.3s;
}

/* Card component example */
.card {
  background-color: var(--card-bg);
  border: 1px solid var(--border);
  box-shadow: var(--card-shadow);
  padding: 1.5rem;
  border-radius: 0.5rem;
  margin: 1rem;
}

/* Button styling */
button {
  background-color: var(--accent);
  color: white;
  padding: 0.5rem 1rem;
  border: none;
  border-radius: 0.25rem;
  cursor: pointer;
  transition: background-color 0.2s;
}

button:hover {
  background-color: var(--accent-hover);
}

🔄 Step 2: JavaScript Toggle Function

Selanjutnya, kita buat fungsi JavaScript untuk toggle dark mode:

// script.js

// Check user preference
function getInitialTheme() {
  // Check localStorage
  const savedTheme = localStorage.getItem('theme');
  if (savedTheme) {
    return savedTheme;
  }
  
  // Check system preference
  if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
    return 'dark';
  }
  
  return 'light';
}

// Apply theme
function applyTheme(theme) {
  document.documentElement.setAttribute('data-theme', theme);
  localStorage.setItem('theme', theme);
}

// Toggle function
function toggleDarkMode() {
  const currentTheme = document.documentElement.getAttribute('data-theme');
  const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
  
  // Animate transition
  document.documentElement.classList.add('theme-transition');
  
  // Apply new theme
  applyTheme(newTheme);
  
  // Remove transition class
  setTimeout(() => {
    document.documentElement.classList.remove('theme-transition');
  }, 300);
}

// Initialize theme
document.addEventListener('DOMContentLoaded', () => {
  // Apply initial theme
  applyTheme(getInitialTheme());
  
  // Listen for system theme changes
  window.matchMedia('(prefers-color-scheme: dark)')
    .addEventListener('change', (e) => {
      applyTheme(e.matches ? 'dark' : 'light');
    });
});

🎯 Step 3: HTML Implementation

Sekarang kita bisa implementasikan dark mode di HTML kita:

<!DOCTYPE html>
<html lang="id">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Dark Mode Demo</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <nav class="navbar">
    <div class="container">
      <h1>Dark Mode Demo</h1>
      <button onclick="toggleDarkMode()" class="theme-toggle" aria-label="Toggle dark mode">
        <!-- Sun icon (light mode) -->
        <svg class="light-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
          <circle cx="12" cy="12" r="5"/>
          <path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/>
        </svg>
        
        <!-- Moon icon (dark mode) -->
        <svg class="dark-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
          <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
        </svg>
      </button>
    </div>
  </nav>

  <main class="container">
    <section class="card">
      <h2>Selamat Datang! 👋</h2>
      <p>
        Ini adalah contoh implementasi dark mode. Coba toggle dark mode dengan
        tombol di navbar untuk melihat perubahannya.
      </p>
    </section>

    <section class="card">
      <h2>Fitur Dark Mode</h2>
      <ul>
        <li>✨ Smooth transitions</li>
        <li>🌓 System preference detection</li>
        <li>💾 Persistent preference (localStorage)</li>
        <li>🎨 CSS Variables untuk maintainability</li>
      </ul>
    </section>

    <section class="card">
      <h2>Form Example</h2>
      <form class="demo-form">
        <div class="form-group">
          <label for="name">Nama</label>
          <input type="text" id="name" placeholder="Masukkan nama">
        </div>
        <div class="form-group">
          <label for="email">Email</label>
          <input type="email" id="email" placeholder="Masukkan email">
        </div>
        <button type="submit">Submit</button>
      </form>
    </section>
  </main>

  <script src="script.js"></script>
</body>
</html>

🎨 Step 4: Additional Styling

Mari tambahkan beberapa styling tambahan untuk membuat UI lebih menarik:

/* Additional styles */
.theme-transition {
  transition: background-color 0.3s, color 0.3s, border-color 0.3s, box-shadow 0.3s;
}

.navbar {
  background-color: var(--card-bg);
  border-bottom: 1px solid var(--border);
  padding: 1rem 0;
  position: sticky;
  top: 0;
  z-index: 100;
}

.container {
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 1rem;
}

.navbar .container {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.theme-toggle {
  background: none;
  border: none;
  padding: 0.5rem;
  cursor: pointer;
  color: var(--text-primary);
}

.theme-toggle:hover {
  background-color: var(--border);
  border-radius: 0.5rem;
}

/* Show/hide icons based on theme */
[data-theme="light"] .dark-icon,
[data-theme="dark"] .light-icon {
  display: none;
}

/* Form styling */
.demo-form {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.form-group {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

input {
  padding: 0.5rem;
  border: 1px solid var(--border);
  border-radius: 0.25rem;
  background-color: var(--background);
  color: var(--text-primary);
}

input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent-hover);
}

/* Responsive design */
@media (max-width: 768px) {
  .card {
    margin: 0.5rem;
    padding: 1rem;
  }
}

🚀 Advanced Features

1. System Theme Detection

// Listen for system theme changes
const systemThemeQuery = window.matchMedia('(prefers-color-scheme: dark)');

function handleSystemThemeChange(e) {
  if (!localStorage.getItem('theme')) {
    applyTheme(e.matches ? 'dark' : 'light');
  }
}

systemThemeQuery.addListener(handleSystemThemeChange);

2. Smooth Transitions

/* Add transitions to specific properties */
* {
  transition-property: background-color, border-color, color, box-shadow;
  transition-duration: 0.3s;
  transition-timing-function: ease;
}

/* Disable transitions temporarily when theme changes */
.theme-transition * {
  transition: none !important;
}

3. Custom Elements Support

// Custom elements that need theme-specific styling
const customElements = {
  'custom-modal': {
    light: {
      background: '#ffffff',
      shadow: '0 4px 6px rgba(0, 0, 0, 0.1)'
    },
    dark: {
      background: '#2d2d2d',
      shadow: '0 4px 6px rgba(0, 0, 0, 0.4)'
    }
  }
};

function updateCustomElements(theme) {
  Object.entries(customElements).forEach(([element, themes]) => {
    const elements = document.getElementsByTagName(element);
    const styles = themes[theme];
    
    Array.from(elements).forEach(el => {
      Object.entries(styles).forEach(([prop, value]) => {
        el.style.setProperty(`--${prop}`, value);
      });
    });
  });
}

🎯 Best Practices

1. Performance Optimization

// Debounce theme changes
function debounce(func, wait) {
  let timeout;
  return function executedFunction(...args) {
    const later = () => {
      clearTimeout(timeout);
      func(...args);
    };
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
  };
}

const debouncedToggle = debounce(toggleDarkMode, 250);

2. Accessibility

<!-- Add proper ARIA labels -->
<button 
  onclick="toggleDarkMode()" 
  class="theme-toggle" 
  aria-label="Toggle dark mode"
  aria-pressed="false"
>
  <!-- Icons here -->
</button>

<script>
// Update ARIA attributes
function updateAccessibility(theme) {
  const toggle = document.querySelector('.theme-toggle');
  toggle.setAttribute('aria-pressed', theme === 'dark');
  document.documentElement.setAttribute('color-scheme', theme);
}
</script>

3. SEO Optimization

<!-- Add meta theme-color -->
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#1a1a1a" media="(prefers-color-scheme: dark)">

🐛 Troubleshooting

Common Issues dan Solusinya

  1. Flickering saat pertama load
// Add no-flash snippet in head
(function() {
  const theme = localStorage.getItem('theme') || 
    (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
  document.documentElement.setAttribute('data-theme', theme);
})();
  1. Transisi tidak smooth
/* Add will-change for better performance */
.theme-transition {
  will-change: background-color, color;
}
  1. Custom elements tidak update
// Use MutationObserver untuk dynamic elements
const observer = new MutationObserver((mutations) => {
  mutations.forEach((mutation) => {
    if (mutation.addedNodes.length) {
      updateCustomElements(getCurrentTheme());
    }
  });
});

observer.observe(document.body, {
  childList: true,
  subtree: true
});

🎯 Testing

Unit Tests

// Jest test example
describe('Theme Utils', () => {
  beforeEach(() => {
    localStorage.clear();
    document.documentElement.removeAttribute('data-theme');
  });

  test('getInitialTheme returns correct theme', () => {
    localStorage.setItem('theme', 'dark');
    expect(getInitialTheme()).toBe('dark');
  });

  test('toggleDarkMode switches theme correctly', () => {
    applyTheme('light');
    toggleDarkMode();
    expect(document.documentElement.getAttribute('data-theme')).toBe('dark');
  });
});

📱 Mobile Considerations

/* Mobile-specific styles */
@media (max-width: 768px) {
  .theme-toggle {
    /* Make touch target larger on mobile */
    padding: 0.75rem;
  }
  
  /* Reduce transition duration on mobile */
  * {
    transition-duration: 0.2s;
  }
}

🔍 SEO Tips

  1. Gunakan semantic HTML
  2. Tambahkan meta description yang relevan
  3. Optimasi gambar dengan lazy loading
  4. Gunakan proper heading hierarchy

🚀 Next Steps

Setelah mengimplementasikan dark mode basic, kamu bisa explore:

  1. Tambahkan multiple color themes
  2. Implementasi auto-switch berdasarkan waktu
  3. Animate theme transitions dengan keyframes
  4. Integrasi dengan framework favorit kamu

🔗 Resources

🎯 Kesimpulan

Dark mode bukan sekedar trend, tapi sudah jadi kebutuhan user modern. Dengan kombinasi CSS Variables dan JavaScript, kita bisa membuat implementasi yang:

  • ✅ Maintainable dan scalable
  • ✅ Performant dengan smooth transitions
  • ✅ Accessible untuk semua user
  • ✅ Persistent dengan localStorage
  • ✅ Responsive di semua devices

Yang penting diingat:

  1. Selalu test di multiple devices dan browsers
  2. Perhatikan accessibility
  3. Optimize untuk performance
  4. Maintain consistency dalam color scheme

🔗 Artikel Terkait


Ditulis dengan ❤️ oleh Hilal Technologic