Dark Mode Implementation
Learn how we implemented smooth theme switching with localStorage persistence and system preference detection in this Astro boilerplate.
Dark Mode Implementation
Dark mode isn’t just a trend—it’s expected by users. Here’s how this boilerplate handles theme switching elegantly.
The Challenge
Implementing dark mode seems simple until you consider:
- Flash of incorrect theme on page load
- System preference detection
- User override persistence
- Smooth transitions
Our solution addresses all four.
The Architecture
1. Early Theme Script
We inject a blocking script in the <head> to prevent flash:
<script is:inline>
const theme =
localStorage.getItem('theme') ||
(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
document.documentElement.dataset.theme = theme;
</script>
The is:inline directive ensures this runs before any rendering.
2. CSS Custom Properties
Theme changes swap entire color palettes:
:root,
[data-theme='light'] {
--color-background: #f9f8f4;
--color-foreground: #2d3a31;
}
[data-theme='dark'] {
--color-background: #1a1f1c;
--color-foreground: #e8e6e1;
}
3. Theme Toggle Component
A React component handles toggling:
export function ThemeToggle() {
const [theme, setTheme] = useState('light');
const toggle = () => {
const next = theme === 'light' ? 'dark' : 'light';
document.documentElement.dataset.theme = next;
localStorage.setItem('theme', next);
setTheme(next);
};
return <button onClick={toggle}>Toggle</button>;
}
4. Smooth Transitions
Colors transition smoothly:
:root {
transition:
background-color 300ms ease,
color 300ms ease;
}
System Preference Detection
We respect user system preferences while allowing overrides:
- Check localStorage for saved preference
- Fall back to
prefers-color-schememedia query - Default to light mode if neither exists
Performance Considerations
- No flash: Inline script blocks rendering until theme is set
- No layout shift: Theme doesn’t affect layout
- Minimal JS: Only ~200 bytes of blocking script
Accessibility
- Theme state is visible in the toggle button
- Color contrast meets WCAG AA in both modes
- Reduced motion preferences are respected
Take This Further
You could extend this with:
- Multiple theme options beyond light/dark
- Theme per-page settings
- Automatic theme switching based on time of day
The foundation is solid. Build on it!