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.

🌓 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
- 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);
})();
- Transisi tidak smooth
/* Add will-change for better performance */
.theme-transition {
will-change: background-color, color;
}
- 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
- Gunakan semantic HTML
- Tambahkan meta description yang relevan
- Optimasi gambar dengan lazy loading
- Gunakan proper heading hierarchy
🚀 Next Steps
Setelah mengimplementasikan dark mode basic, kamu bisa explore:
- Tambahkan multiple color themes
- Implementasi auto-switch berdasarkan waktu
- Animate theme transitions dengan keyframes
- 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:
- Selalu test di multiple devices dan browsers
- Perhatikan accessibility
- Optimize untuk performance
- Maintain consistency dalam color scheme
🔗 Artikel Terkait
- Animasi CSS yang Bikin Website Lo Terlihat Premium
- Deploy Website Gratis: Vercel vs Netlify vs GitHub Pages
- Tips Optimasi Website Loading Cepat
Ditulis dengan ❤️ oleh Hilal Technologic