Why not next-themes?
A comparison of @wrksz/themes and next-themes.
Last updated on
@wrksz/themes is a drop-in replacement for next-themes. It fixes every known bug and adds missing features. No API changes needed.
next-themes is slowly maintained - 43 open issues and 16 open PRs as of March 2026, with React 19 compatibility bugs still unresolved in the latest release.
Bug fixes
React 19 script warning
next-themes renders an inline <script> inside a Client Component, which triggers a React 19 warning. @wrksz/themes splits the provider into a Server Component (ThemeProvider) that renders the script, and a Client Component (ClientThemeProvider) that handles state.
__name minification bug
Production builds that minify function names break next-themes internals. Fixed.
Stale theme in React 19 Activity and cacheComponents
next-themes uses useState + useContext, which causes stale theme values when React's Activity or cacheComponents suspend and resume a subtree. @wrksz/themes uses useSyncExternalStore with a per-instance store, which always returns the latest value.
Missing features
Multiple classes per theme
Both libraries support space-separated values in the value map. The bug is in removal: next-themes removes only the top-level theme key as a single class, so switching from a multi-class theme leaves stale classes in the DOM.
@wrksz/themes flattens all mapped values before removing them:
for (const attr of attrs) {
if (attr === "class") {
const toRemove = themes.flatMap((t) =>
(valueMap?.[t] ?? t).split(" "),
);
el.classList.remove(...toRemove);
el.classList.add(...attrValue.split(" "));
} else {
el.setAttribute(attr, attrValue);
}
}This means switching from "dark high-contrast" to "light" correctly removes both dark and high-contrast.
Nested providers
next-themes uses a single global context - nested providers share state. @wrksz/themes creates a per-instance store so each provider is fully independent.
sessionStorage support
<ThemeProvider storage="sessionStorage">Disable storage
<ThemeProvider storage="none">meta theme-color support
Updates <meta name="theme-color"> automatically when the theme changes. Useful for Safari and PWAs:
<ThemeProvider themeColor={{ light: "#ffffff", dark: "#0a0a0a" }}>Server-provided theme
Initialize the theme from a server-side source (database, session, cookie) on every mount:
<ThemeProvider
initialTheme={userTheme}
onThemeChange={saveUserTheme}
>Generic types
const { theme, setTheme } = useTheme<"light" | "dark" | "high-contrast">();Comparison table
next-themes | @wrksz/themes | |
|---|---|---|
| React 19 script warning | Bug | Fixed (RSC split)Fixed |
__name minification bug | Bug | FixedFixed |
React 19 Activity / cacheComponents stale theme | Bug | Fixed (useSyncExternalStore)Fixed |
| Multiple classes per theme | Bug | Fixed (correct class removal)Fixed |
| Nested providers | No | per-instance storeYes |
sessionStorage support | No | YesYes |
| Disable storage | No | storage="none"Yes |
meta theme-color support | No | themeColor propYes |
| Server-provided theme | No | initialTheme propYes |
| Generic types | No | useTheme<AppTheme>()Yes |
| Zero runtime dependencies | YesYes | YesYes |