Back to Dark-mode Guide

How to Add Dark Mode to Any Website: Complete Implementation Guide

Learn how to implement dark mode on your website with our comprehensive guide. Discover CSS and JavaScript techniques for adding dark theme functionality to any website.

Dark mode began its journey as a developer novelty, but has since become something users not only expect but actively look for. Whether you're motivated by accessibility concerns, reducing eye strain for your visitors, or simply trying to keep pace with web design trends, this guide will navigate you through the process of implementing dark mode on any website. From simple CSS tricks to more advanced JavaScript solutions, you'll discover everything needed to let your visitors experience your site in their preferred luminosity.

Understanding Dark Mode: More Than Just Inverted Colors

Dark mode isn't simply a matter of flipping white backgrounds to black. Before we dive into the code, it's important to understand these fundamental principles:

Contrast Sensitivity: Text demands a 4.5:1 contrast ratio against backgrounds for WCAG AA compliance.
Color Warmth: Pure black (#000000) creates excessive contrast; dark grays (#121212, #1f1f1f) are more soothing.
Element Hierarchy: Shadows and borders that establish visual hierarchy in light mode need clever dark mode alternatives.
Content Adaptation: Images, videos, and interactive elements might require adjustments to look good against a dark background.

Armed with these principles, let's explore the technical implementation approaches, starting with the simple and moving toward the more complex.

Method 1: CSS-Only Dark Mode Implementation

For static websites or simple designs, a CSS-only approach offers an elegant solution with minimal overhead.

Step 1: Define your color variables

In your CSS file, create variables for your color scheme:

:root {
/* Light theme colors (default) */
--background-primary: #ffffff;
--background-secondary: #f1f5f9;
--text-primary: #1a202c;
--text-secondary: #4a5568;
--accent-color: #3182ce;
}

@media (prefers-color-scheme: dark) {
:root {
/* Dark theme colors */
--background-primary: #1a202c;
--background-secondary: #2d3748;
--text-primary: #f7fafc;
--text-secondary: #e2e8f0;
--accent-color: #63b3ed;
}
}


Step 2: Apply variables throughout your CSS

Replace hardcoded color values with your variables:

body {
background-color: var(--background-primary);
color: var(--text-primary);
}

header, footer {
background-color: var(--background-secondary);
}

a {
color: var(--accent-color);
}


Advantages of this approach:

• Zero JavaScript required.
• Automatic detection of user preferences.
• Minimal performance impact.

Limitations:

• No manual toggle option without JavaScript.
• No way to override system preferences.
• No persistence of user selection.

This solution works splendidly for simple sites but lacks the user control that visitors increasingly expect.

Method 2: JavaScript Toggle with Local Storage

Adding JavaScript brings the gift of choice to your users.

Step 1: Modify your CSS approach

Instead of relying solely on media queries, adopt a class-based approach with a media query safety net:

:root {
/* Light theme variables */
--background-primary: #ffffff;
--text-primary: #1a202c;
/* ...other variables... */
}

html.dark-mode {
--background-primary: #1a202c;
--text-primary: #f7fafc;
/* ...other dark theme variables... */
}

@media (prefers-color-scheme: dark) {
:root:not(.light-mode) {
/* Same dark theme variables as above */
--background-primary: #1a202c;
--text-primary: #f7fafc;
/* ...other dark theme variables... */
}
}


Step 2: Add the HTML toggle

Create a toggle button:

<button id="dark-mode-toggle" aria-label="Toggle dark mode">
<span class="moon-icon">🌙</span>
<span class="sun-icon">☀️</span>
</button>


Step 3: Add the JavaScript

Now for the clever bit – the JavaScript that makes everything work:

// Check for saved theme preference or use system preference
const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)');
const storedTheme = localStorage.getItem('theme');

// Apply the right theme class on load
if (storedTheme === 'dark' || (!storedTheme && prefersDarkScheme.matches)) {
document.documentElement.classList.add('dark-mode');
} else if (storedTheme === 'light') {
document.documentElement.classList.add('light-mode');
}

// Set up the toggle
const themeToggle = document.getElementById('dark-mode-toggle');
themeToggle.addEventListener('click', () => {
// If currently light → go dark
if (!document.documentElement.classList.contains('dark-mode')) {
document.documentElement.classList.add('dark-mode');
document.documentElement.classList.remove('light-mode');
localStorage.setItem('theme', 'dark');
} else {
// If currently dark → go light
document.documentElement.classList.remove('dark-mode');
document.documentElement.classList.add('light-mode');
localStorage.setItem('theme', 'light');
}
});


This approach strikes a delicate balance between simplicity and user control.

Method 3: Dynamic CSS Injection for Complex Websites

For websites with more layers of complexity, dynamically injecting a separate stylesheet provides better isolation and control.

Step 1: Create separate stylesheets

Instead of wrestling with CSS variables, maintain two separate stylesheets:
light-theme.css - Your default styling
dark-theme.css - Dark mode specific overrides

Step 2: Add theme switching JavaScript

// Create link element for theme stylesheet
const themeStylesheet = document.createElement('link');
themeStylesheet.rel = 'stylesheet';
document.head.appendChild(themeStylesheet);

// Function to set the active theme
function setTheme(themeName) {
localStorage.setItem('theme', themeName);
themeStylesheet.href = `${themeName}-theme.css`;
}

// Initialize theme
const storedTheme = localStorage.getItem('theme');
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;

if (storedTheme) {
setTheme(storedTheme);
} else {
setTheme(prefersDark ? 'dark' : 'light');
}

// Toggle button event handler
document.getElementById('theme-toggle').addEventListener('click', () => {
const currentTheme = localStorage.getItem('theme') || 'light';
setTheme(currentTheme === 'light' ? 'dark' : 'light');
});


This approach offers cleaner separation and works remarkably well with complex UI frameworks and third-party components that may not work well with CSS variables.

Handling Images and Media in Dark Mode

Images and media require special consideration in dark mode.

Approach 1: CSS Filters

For images needing subtle adjustment, apply CSS filters:

html.dark-mode img:not([src*="logo"]) {
filter: brightness(0.8) contrast(1.2);
}


This works brilliantly for photographs but may distort logos.

Approach 2: Different Image Sources

For critical images like logos, provide alternate versions designed specifically for dark mode:

<picture>
<source srcset="logo-dark.png" media="(prefers-color-scheme: dark)">
<img src="logo-light.png" alt="Company Logo">
</picture>


For JavaScript-based theme switching, update image sources when the theme changes:

// Update all theme-aware images
function updateImages(themeName) {
document.querySelectorAll('img[data-dark-src]').forEach(img => {
if (themeName === 'dark') {
img.src = img.getAttribute('data-dark-src');
} else {
img.src = img.getAttribute('data-light-src');
}
});
}


Approach 3: SVG with currentColor

For icons, use SVGs with currentColor to automatically adapt to text color:

<svg fill="currentColor" viewBox="0 0 24 24">...</svg>

With corresponding CSS:

.icon {
color: var(--text-primary);
}


These techniques ensure your visuals maintain their consistency across both themes.

Dark Mode for Web Applications and Frameworks

Modern web application frameworks require slightly different approaches.

React Implementation

Use a context-based theme provider:

// ThemeContext.js
import React, { createContext, useState, useEffect } from 'react';

export const ThemeContext = createContext();

export const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState('light');

useEffect(() => {
// Initialize theme from localStorage or system preference
const savedTheme = localStorage.getItem('theme');
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;

if (savedTheme) {
setTheme(savedTheme);
} else if (prefersDark) {
setTheme('dark');
}
}, []);

useEffect(() => {
// Apply theme class to document
document.documentElement.className = theme;
localStorage.setItem('theme', theme);
}, [theme]);

return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
};


Then consume the context in components:

// ThemeToggle.js
import { useContext } from 'react';
import { ThemeContext } from './ThemeContext';

const ThemeToggle = () => {
const { theme, setTheme } = useContext(ThemeContext);

const toggleTheme = () => {
setTheme(theme === 'light' ? 'dark' : 'light');
};

return (
<button onClick={toggleTheme}>
{theme === 'light' ? '🌙' : '☀️'}
</button>
);
};


Similar patterns can be applied to Vue, Angular, and other frameworks, adapting to their specific state management approaches.

Preventing Flash of Incorrect Theme (FOIT)

A common issue with dark mode implementations is a brief flash of the wrong theme during page load.

Solution: Inline Script in <head>

Place this script in your HTML <head> before any stylesheets:

<script>
// Immediately set the theme before the page renders
(function() {
var savedTheme = localStorage.getItem('theme');
var prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;

if (savedTheme === 'dark' || (!savedTheme && prefersDark)) {
document.documentElement.classList.add('dark-mode');
} else if (savedTheme === 'light') {
document.documentElement.classList.add('light-mode');
}
})();
</script>


This script executes synchronously before the DOM starts rendering, preventing any flash of incorrect theme.

For more complex applications, consider using server-side rendering to determine the theme on the server and include the appropriate classes in the initial HTML.

Testing Your Dark Mode Implementation

Thorough testing is essential to ensure your dark mode implementation works correctly across devices and browsers.

Test Cases to Cover:

1. System preference detection - Verify your site respects OS-level settings
2. Manual toggle functionality - Ensure users can override system preferences
3. Preference persistence - Confirm selections persist across page navigations
4. Contrast ratios - Validate WCAG compliance in both modes
5. Browser compatibility - Test in Chrome, Firefox, Safari, and Edge
6. Mobile responsiveness - Verify behavior on iOS and Android devices
7. Image/media adaptations - Check all visual elements for proper display
8. Third-party content - Assess embedded content, iframes, and plugins

Testing Tools:

WebAIM Contrast Checker - Verify color contrast compliance
• Browser DevTools - Toggle prefers-color-scheme manually
BrowserStack - Test across multiple browsers and devices without a device lab

Address any issues before launching, as a poor dark mode implementation can be worse than none at all.

Conclusion: Dark Mode as a Competitive Advantage

Implementing dark mode is no longer just a nice-to-have feature—it's an expectation for modern websites. A well-executed dark mode implementation can:

• Reduce eye strain for users browsing at night
• Extend battery life on devices with OLED/AMOLED screens
• Demonstrate your commitment to user experience customization
• Modernize your website's appearance and functionality

As you implement your solution, remember that dark mode is not merely an inverted color scheme but a thoughtfully designed alternative visual experience. Take the time to properly test and refine your implementation across devices and user scenarios.

With the techniques outlined in this guide, you now have the knowledge to implement dark mode on any website, from simple static pages to complex web applications. Your users' eyes will thank you, and your website will join the ranks of modern, user-focused digital experiences.

Frequently Asked Questions

Can I add dark mode to my website without knowing CSS?

While basic CSS knowledge is helpful, there are no-code solutions available. Website builders like Wix, Squarespace, and WordPress with appropriate themes often include built-in dark mode toggles that work with the click of a button. Alternatively, you can use third-party services like Darkreader.org to add a client-side dark mode to any website. However, for the best visual results and performance, a custom implementation using the CSS and JavaScript techniques described in this guide is recommended.

How do I handle forms and input fields in dark mode?

Forms require special attention in dark mode to maintain usability. Use moderate background colors (not pure black) for input fields and ensure sufficient contrast with input text. Remember to style focus states prominently, as they can be less visible in dark themes. For select menus and dropdowns, ensure the dropdown options maintain the dark theme. System inputs like date pickers may use browser default styling, so test these carefully in dark mode to ensure compatibility.

Will adding dark mode affect my website's performance?

When properly implemented, dark mode should have minimal impact on performance. CSS variables and media queries add negligible overhead. The JavaScript toggle approach adds only a tiny amount of code. For optimal performance: (1) Avoid large duplicate stylesheets, (2) Use CSS variables over complete style duplication, (3) Place theme detection scripts inline in the head to prevent flashing, and (4) Lazy-load any alternate dark mode images to reduce initial page load time. Your users won't notice any performance difference, but they will notice the comfort.

Should I implement dark mode before launching my website?

While not absolutely essential for launch, implementing dark mode during initial development is significantly easier than retrofitting it later. When dark mode is planned from the start, you can establish a color system with variables, properly structure your CSS, and test both themes simultaneously. If your website is already live, implementing dark mode makes an excellent feature update that can be promoted to show your commitment to improving user experience.

How do I handle third-party widgets in dark mode?

Third-party widgets and embedded content present challenges for dark mode implementation as they often use their own styling. For widgets within iframes, you generally cannot modify their appearance. Options include: (1) Check if the widget provider offers a dark mode version you can conditionally load, (2) Apply a subtle background to the widget container to visually separate it from your dark-themed UI, (3) Use CSS to invert the iframe contents as a last resort (this may cause visual issues), or (4) In extreme cases, consider excluding the widget container from dark mode styles entirely.

Ready to transform your dark-mode website?

Join thousands of users who are already using our visual editor to update their dark-mode sites without coding.