Loading article…
Dynamic Theme Engine in Next.js
A comprehensive guide to implementing a flexible, server-side theme management system in Next.js
November 26, 2024
Loading article…
When building modern web applications, theme management can quickly become complex:
Our implementation solves these challenges while providing a delightful developer experience.
// Basic usage
const { theme, selectTheme, toggleMode } = useTheme('light');
// Selecting a specific theme
selectTheme('dark');
// Toggling between light and dark
toggleMode();
Our theme management system is built around a flexible and performant hook that handles theme selection, persistence, and UI updates.
function useTheme(currentThemeName: Theme) {
const router = useRouter();
const [theme, setTheme] = useState<Theme>(currentThemeName);
const selectTheme = async (theme: Theme) => {
// Update body attribute for CSS targeting
document.body.setAttribute("data-theme", theme);
// Persist theme server-side
await setCurrentTheme(theme);
// Refresh server-side rendering
router.refresh();
// Update local state
setTheme(theme);
};
const toggleMode = async () => {
const selectedDark = theme !== "light";
const newTheme = selectedDark ? "light" : "dark";
// Update UI immediately
document.body.setAttribute("data-theme", newTheme);
// Update local state
setTheme(newTheme);
// Persist theme change
await toggleDarkTheme();
// Refresh server-side rendering
router.refresh();
};
return { theme, selectTheme, toggleMode };
}
Immediate UI Feedback
document.body.setAttribute() ensures instant visual changesServer-Side Persistence
setCurrentTheme() and toggleDarkTheme() handle server-side storagerouter.refresh() ensures server-side rendering reflects the new themeAsync Operations
/* theme.css */
[data-theme='light'] {
--background: white;
--text: black;
}
[data-theme='dark'] {
--background: black;
--text: white;
}
body {
background-color: var(--background);
color: var(--text);
transition: background-color 0.3s, color 0.3s;
}
function ThemeList() {
const { theme, selectTheme } = useTheme('light');
const themes = [
{ name: 'light', label: 'Light Mode' },
{ name: 'dark', label: 'Dark Mode' },
{ name: 'system', label: 'System Preference' }
];
return (
<div className="theme-switcher">
{themes.map(({ name, label }) => (
<button
key={name}
onClick={() => selectTheme(name)}
aria-pressed={theme === name}
>
{label}
</button>
))}
</div>
);
}
Our theme engine provides:
Written with the help of WindSurf's Cascade.