Dark mode looks simple on the surface, but in real projects it's more than just swapping a few colours. The toggle itself is easy enough to build, what matters is how the theme is applied, whether the preference persists, how accessible the contrast remains, and how smoothly it fits into the rest of the interface.
This guide walks through a simple CSS and JavaScript implementation, along with a few practical considerations that make the difference between a basic demo and something usable in production.
Why Dark Mode Is Worth Implementing
Dark mode isn't just a visual trend, it's become a standard expectation in many interfaces. When done properly, it improves usability without adding much complexity to the UI, especially for products people use for longer periods.
Where it adds value:
- Improves comfort in low-light environments
- Gives users more control over how they view content
- Works well in dashboards, apps, and content-heavy interfaces
Where Dark Mode Works Well in Practice
Dark mode tends to work best in interfaces where users spend more time and benefit from reduced visual strain. It's less about aesthetics and more about supporting how people actually use the product.
Where it fits naturally:
- Dashboards and admin interfaces
- Content-heavy apps used for long sessions
- Products where users already expect theme controls
When a Dark Mode Toggle Is Not Enough
Adding a toggle is the easy part. The real work is making sure the entire interface actually supports the theme. Without that, dark mode quickly feels inconsistent or unfinished.
Where things tend to fall short:
- Contrast and readability haven't been properly adjusted
- Only a few surface colours are changed while the rest of the UI stays the same
- The theme doesn't persist across sessions
- Third-party components don't match the selected theme
A dark mode that isn't applied consistently can feel worse than not having one at all.
Common Mistakes
Most issues with dark mode don't come from the toggle itself, they come from inconsistent theme handling across the interface. It's easy to get something working quickly, but much harder to make it feel complete and reliable.
What we see most often:
- Only changing background and text colours while leaving the rest of the UI untouched
- Forgetting hover, border, and focus states, which breaks visual consistency
- Poor contrast in dark mode, making content harder to read instead of easier
- Not saving the user's preference across sessions
- Styling the main interface but missing forms, modals, or embedded components
Dark mode works best when it's treated as a full theme, not just a visual toggle.
Need help implementing UI features like this properly?
We help teams implement UI features like this cleanly,ensuring consistency, performance, and maintainability across the entire interface..
Step 1 - Setting Up the HTML Structure
To create a dark mode toggle switch, we will use a simple checkbox input inside a <label> element. This allows us to style the switch effectively with CSS while keeping it accessible and functional.
HTML Structure for the Toggle Switch
Below is the basic HTML markup needed for our toggle switch:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dark Mode Toggle</title>
<link rel="stylesheet" href="/styles.css">
</head>
<body>
<div class="container">
<label class="toggle-switch">
<input type="checkbox" id="dark-mode-toggle">
<span class="slider"></span>
</label>
</div>
<script src="/script.js"></script>
</body>
</html>
The HTML Elements
<label>: The toggle switch is wrapped in a <label> to make it clickable.<input type="checkbox">: A hidden checkbox controls the toggle state.<span class="slider">: This is the visible part of the switch, which we will style in the next step.id="dark-mode-toggle": The unique ID allows JavaScript to detect when the switch is toggled.- External CSS (styles.css) and JavaScript (script.js) files are linked to style and control the functionality of the switch.
Step 2 - Styling the Toggle Switch with CSS
Now it's time to make our toggle switch look like a real button using CSS. We'll create a sleek, modern design and add a smooth transition effect to enhance the user experience.
Adding Basic Styles to the Toggle Switch
Create a new CSS file (styles.css) and add the following styles:
/* General Styles */
body {
font-family: Arial, sans-serif;
background-color: #ffffff;
color: #333;
transition: background-color 0.3s, color 0.3s;
text-align: center;
padding: 50px;
}
/* Centering the toggle */
.container {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
/* Toggle Switch Styling */
.toggle-switch {
position: relative;
width: 50px;
height: 25px;
display: inline-block;
}
/* Hide default checkbox */
.toggle-switch input {
display: none;
}
/* The slider (switch button) */
.slider {
position: absolute;
cursor: pointer;
width: 100%;
height: 100%;
background-color: #ccc;
border-radius: 25px;
transition: background-color 0.3s;
}
/* The circular button inside the switch */
.slider::before {
content: "";
position: absolute;
width: 20px;
height: 20px;
background-color: white;
border-radius: 50%;
top: 50%;
left: 5%;
transform: translateY(-50%);
transition: transform 0.3s;
}
/* When the switch is toggled (checked state) */
input:checked + .slider {
background-color: #4caf50;
}
/* Move the slider when checked */
input:checked + .slider::before {
transform: translate(22px, -50%);
}
How This CSS Works
- The
.toggle-switchclass creates the overall switch structure. - The checkbox
inputis hidden so that only the styled slider is visible. - The
.slideracts as the visible switch, with a rounded pill shape. - The
.slider::beforerepresents the circular toggle button, which moves when clicked. - Using
transition: 0.3s;ensures a smooth animation effect when switching between light mode and dark mode.
Step 3 - Writing JavaScript to Enable Dark Mode
Now that we have a styled toggle switch, we need to make it functional using JavaScript. Our script will:
- Detect when the switch is toggled
- Apply dark mode styles by adding/removing a CSS class
- Store the user's preference in localStorage so the site remembers their choice
Adding Dark Mode Styles to CSS
Before we dive into JavaScript, let's define dark mode styles in styles.css:
/* Dark Mode Styles */
.dark-mode {
background-color: #1e1e1e;
color: #ffffff;
}
/* Change toggle switch color in dark mode */
.dark-mode .slider {
background-color: #2196F3;
}
JavaScript to Handle Dark Mode Toggle
Create a new JavaScript file (script.js) and add the following code:
// Select the toggle switch
const toggleSwitch = document.getElementById("dark-mode-toggle");
// Function to enable dark mode
function enableDarkMode() {
document.body.classList.add("dark-mode"); // Apply dark mode styles
localStorage.setItem("darkMode", "enabled"); // Save preference
}
// Function to disable dark mode
function disableDarkMode() {
document.body.classList.remove("dark-mode"); // Remove dark mode styles
localStorage.setItem("darkMode", "disabled"); // Save preference
}
// Check if the user has a preference saved
if (localStorage.getItem("darkMode") === "enabled") {
enableDarkMode();
toggleSwitch.checked = true; // Ensure the toggle remains in the correct state
}
// Listen for toggle switch changes
toggleSwitch.addEventListener("change", () => {
if (toggleSwitch.checked) {
enableDarkMode();
} else {
disableDarkMode();
}
});
How This JavaScript Works
- Selects the toggle switch using
document.getElementById(). - Defines functions to enable and disable dark mode by adding/removing the .dark-mode class from
<body>. - Stores the user's preference in
localStorageso the setting is remembered. - Checks
localStorageon page load to restore the user's preference. - Listens for toggle changes and applies the correct mode.
Step 4 - Improving the Dark Mode Styles
Let's improve the user experience by refining the dark mode colours, contrast, and readability. A well-designed dark mode should reduce eye strain, maintain good visibility, and ensure that all elements remain legible.
Improving Background and Text Colours
A good dark mode isn't just about switching the background to black, it should use shades of dark gray instead of pure black (#000000), which can cause eye strain.
- Use softer dark backgrounds: #1e1e1e, #2b2b2b, or #121212
- Use off-white text for readability: #e0e0e0 or #cfcfcf instead of pure white
- Ensure buttons and links stand out in dark mode
Updated Dark Mode Styles
Add the following refinements to styles.css:
/* Improved Dark Mode Styles */
.dark-mode {
background-color: #1e1e1e; /* Soft dark background */
color: #e0e0e0; /* Light gray text for readability */
}
/* Improve header and navigation readability */
.dark-mode h1,
.dark-mode h2,
.dark-mode h3 {
color: #ffffff;
}
/* Adjust button styles for dark mode */
.dark-mode button {
background-color: #333;
color: #ffffff;
border: 1px solid #555;
}
.dark-mode button:hover {
background-color: #444;
}
/* Improve links in dark mode */
.dark-mode a {
color: #4db8ff; /* Soft blue */
}
.dark-mode a:hover {
color: #66ccff; /* Lighter blue on hover */
}
/* Adjust the toggle switch color for better contrast */
.dark-mode .slider {
background-color: #4db8ff;
}
Contrast and Accessibility
🔹 Test contrast ratios to ensure that text remains readable against dark backgrounds.
🔹 Use WebAIM's Contrast Checker to verify colour accessibility.
🔹 Use box shadows subtly to create visual separation instead of relying on bright outlines.
Example:
/* Adding soft shadows for depth in dark mode */
.dark-mode .card {
background-color: #252525;
box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.3);
}
Adding Smooth Transitions for a Better Experience
Right now, when the user toggles dark mode, the changes happen instantly, which can feel abrupt. To create a smoother experience, we can add a fade effect.
Add this to your CSS file:
/* Smooth transition for dark mode */
body {
transition: background-color 0.5s ease, color 0.5s ease;
}
With these refinements, dark mode will feel more polished and user-friendly. The improved contrast, button styles, and smooth transitions will enhance readability and create a better browsing experience for all users.
Step 5 - Testing and Debugging
Now that our dark mode toggle switch is fully implemented, it's important to test it across different browsers and devices to ensure smooth functionality. A well-tested dark mode improves user experience, accessibility, and site reliability.
Testing Across Browsers
Not all browsers handle JavaScript and CSS transitions the same way, so it's important to test in:
- Google Chrome – Most widely used browser with full JavaScript support
- Mozilla Firefox – Strong CSS support but may handle localStorage differently
- Safari – Can sometimes have issues with localStorage
- Microsoft Edge – Based on Chromium, but still requires testing
- Opera & Brave – Less common but growing in usage
How to Test:
- Open your website in each browser.
- Toggle dark mode on and off multiple times.
- Refresh the page to check if
localStorageremembers user preference. - Inspect elements using Developer Tools (F12 > Console/Storage/Elements).
Testing on Mobile Devices
Mobile devices render pages differently, so ensure dark mode works on:
- Android (Chrome, Firefox, Samsung Internet)
- iOS (Safari, Chrome)
- Tablet devices (iPads, Android tablets)
How to Test on Mobile:
- Open your site on different devices (or use Chrome DevTools > Toggle Device Toolbar for simulated testing).
- Check if the toggle switch is touch-friendly and works properly.
- Confirm CSS styling remains readable in dark mode.
- Refresh the page to ensure dark mode persists after reload.
Common Issues and Fixes
Issue: Dark mode doesn't persist after page refresh
Fix: Ensure localStorage is working and darkMode preference is set properly.
console.log(localStorage.getItem("darkMode")); // Debugging output
Issue: Dark mode toggle appears glitchy when switching
Fix: Add transition: 0.3s ease-in-out; to body in CSS for a smooth effect.
Issue: colours look too dark or unreadable in dark mode
Fix: Adjust background and text colours using contrast checkers (WebAIM Contrast Checker).
Issue: Dark mode toggle not working on some mobile browsers
Fix: Ensure JavaScript runs after the page loads by wrapping it in.
document.addEventListener("DOMContentLoaded", function() {
// Your dark mode script here
});
Final Testing Steps
- Check performance impact – Dark mode should not slow down page loads.
Verify accessibility – Test with screen readers to ensure usability.
Cross-test in incognito mode – Some browsers block localStorage in private browsing.
Further Enhancements
- Adding animations: Smooth transitions between themes for a better user experience
- Customizing themes: Implementing additional themes beyond just dark and light modes
- Improving accessibility: Enhancing keyboard navigation and screen reader support
- Mobile-first optimization: Ensuring the toggle is user-friendly on smaller screens
- Implement Dark Mode with CSS Variables: Instead of manually changing colours in CSS, use CSS variables to store theme colours and dynamically update them
This dark mode toggle is just the beginning! Feel free to experiment, modify, and implement it in your own projects to enhance usability and design.