Dark mode
This guide explains how to enable and manage dark mode in a way consistent with Tailwind’s dark-mode documentation.
Enable dark mode
Add the custom variant in your main CSS so Tailwind will apply any dark: utilities inside a .dark scope.
@custom-variant dark (&:where(.dark, .dark *));
You will typically add/remove the .dark
class on the <html>
element.
Set the initial theme
Add this inline script inside the <head>
tag to ensure the correct theme is applied before the page renders. This prevents flash issue and respects both the user’s saved preference in localStorage and their system’s prefers-color-scheme
setting.
<script>
// On page load or when changing themes, best to add inline in head to avoid FOUC
document.documentElement.classList.toggle(
"dark",
localStorage.theme === "dark" ||
(!("theme" in localStorage) && window.matchMedia("(prefers-color-scheme: dark)").matches),
);
</script>
Theme toggle script
This script manages switching between light and dark themes. It works by:
- Adding or removing the
.dark
class on the<html>
element - Temporarily disabling transitions and animations to avoid flicker when toggling
- Updating the visibility of toggle icons (
[data-theme-toggle="dark"]
and[data-theme-toggle="light"]
) - Storing the user’s preference in
localStorage
and restoring it automatically on page load
const toggleTheme = (theme) => {
const s = document.createElement('style');
s.textContent = '*,*::before,*::after{transition:none!important;animation:none!important}';
document.head.appendChild(s);
const html = document.documentElement;
if (theme === 'dark') html.classList.add('dark');
else html.classList.remove('dark');
requestAnimationFrame(() => requestAnimationFrame(() => s.remove()));
};
const updateToggleThemeButton = (theme) => {
const moonIcon = document.querySelector('[data-theme-toggle="dark"]');
const sunIcon = document.querySelector('[data-theme-toggle="light"]');
if (theme === 'dark') {
moonIcon?.classList.add('hidden');
sunIcon?.classList.remove('hidden');
} else {
moonIcon?.classList.remove('hidden');
sunIcon?.classList.add('hidden');
}
};
document.addEventListener('DOMContentLoaded', () => {
const savedTheme = localStorage.getItem('theme');
const initialTheme = savedTheme ?? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
toggleTheme(initialTheme);
updateToggleThemeButton(initialTheme);
const toggleThemeBtn = document.querySelector('[data-theme-toggle-btn]');
toggleThemeBtn?.addEventListener('click', () => {
const current = localStorage.getItem('theme') ?? initialTheme;
const newTheme = current === 'dark' ? 'light' : 'dark';
localStorage.setItem('theme', newTheme);
toggleTheme(newTheme);
updateToggleThemeButton(newTheme);
});
});
If you do not plan to update the toggle theme button, you can remove the updateToggleThemeButton
implementation.
Toggle button markup (example)
Use any markup you prefer, this example matches your selectors and hides/shows icons based on the current state.
<button type="button" data-theme-toggle-btn class="btn btn-circle btn-subtle-neutral">
<svg data-theme-toggle="dark"><!-- moon icon --></svg>
<svg data-theme-toggle="light"><!-- sun icon --></svg>
</button>
How to use
Hummingbird supports dark mode out of the box, and all components are fully compatible with it. You don’t need to add any extra configuration to enable dark mode styles.
If you want to apply additional styling specifically for dark mode, use the dark:
variant alongside your utility classes. This ensures that elements automatically adjust their appearance whenever the .dark
class is active.
<div class="bg-subtle text-primary dark:text-secondary">
This content adapts to dark mode
</div>
In addition to using the dark:
variant, you can directly override Hummingbird’s theme variables inside a dark scope:
@variant dark {
--color-primary: var(--color-blue-400);
--color-secondary: var(--color-purple-400);
}