Dark Mode
Easy way to set up dark mode in Hono projects.
1. Add dual theme variants
The below is Neutral colorway. You can customize it anytime.
@import "tailwindcss" source("../src");
@custom-variant dark (&:is(.dark *));
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
}
:root {
color-scheme: light;
--background: oklch(100% 0 0);
--foreground: oklch(14.5% 0 0);
--card: oklch(98.5% 0 0);
--card-foreground: oklch(20.5% 0 0);
--popover: oklch(100% 0 0);
--popover-foreground: oklch(14.5% 0 0);
--primary: oklch(70.5% 0.213 47.604);
--primary-foreground: oklch(98.5% 0 0);
--secondary: oklch(76.9% 0.188 70.08);
--secondary-foreground: oklch(98.5% 0 0);
--muted: oklch(98.5% 0 0);
--muted-foreground: oklch(43.9% 0 0);
--accent: oklch(97% 0 0);
--accent-foreground: oklch(14.5% 0 0);
--destructive: oklch(63.7% 0.237 25.331);
--border: oklch(92.2% 0 0);
--input: oklch(92.2% 0 0);
--ring: oklch(92.2% 0 0);
}
.dark {
color-scheme: dark;
--background: oklch(14.5% 0 0);
--foreground: oklch(98.5% 0 0);
--card: oklch(20.5% 0 0);
--card-foreground: oklch(97% 0 0);
--popover: oklch(14.5% 0 0);
--popover-foreground: oklch(98.5% 0 0);
--primary: oklch(70.5% 0.213 47.604);
--primary-foreground: oklch(98.5% 0 0);
--secondary: oklch(76.9% 0.188 70.08);
--secondary-foreground: oklch(98.5% 0 0);
--muted: oklch(20.5% 0 0);
--muted-foreground: oklch(70.8% 0 0);
--accent: oklch(20.5% 0 0);
--accent-foreground: oklch(97% 0 0);
--destructive: oklch(63.7% 0.237 25.331);
--border: oklch(37.1% 0 0);
--input: oklch(37.1% 0 0);
--ring: oklch(37.1% 0 0);
}Choose your dark mode style
- A. Dual mode (Dark / Light)
- B. Tri mode (System / Dark / Light)
A. Dual mode (Dark / Light)
1. Add scripts
The first script is runs before first paint to avoid FOUC. The second module runs after the DOM is ready.
import { html } from "hono/html";
<head>
{html`
<script>
document.documentElement.classList.toggle("dark", localStorage.theme !== "light");
</script>
<script type="module">
document.getElementById("theme-toggle").addEventListener("click", () => {
localStorage.theme = document.documentElement.classList.toggle("dark") ? "dark" : "light";
});
</script>
`}
</head>;2. Add switcher (button)
Both icons are always in the DOM. No layout shift occurs.
<button id="theme-toggle" type="button" aria-label="Toggle theme">
<span class="hidden dark:block">☀️</span>
<span class="dark:hidden">🌙</span>
</button>B. Tri mode (System / Dark / Light)
1. Add scripts
The first script is runs before first paint to avoid FOUC. The second module runs after the DOM is ready.
import { html } from "hono/html";
<head>
{html`
<script>
function applyDark() {
document.documentElement.classList.toggle(
"dark",
localStorage.theme === "dark" ||
(!("theme" in localStorage) && matchMedia("(prefers-color-scheme: dark)").matches),
);
}
applyDark();
</script>
<script type="module">
const s = document.getElementById("theme-toggle");
s.value = localStorage.theme ?? "system";
s.addEventListener("change", () => {
s.value === "system" ? localStorage.removeItem("theme") : (localStorage.theme = s.value);
applyDark();
});
</script>
`}
</head>;2. Add switcher (select)
<select id="theme-toggle" aria-label="Theme">
<option value="system">System</option>
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>