next-theme-handler
v1.0.0
Published
A lightweight theme switcher for React with system preference detection
Downloads
8
Maintainers
Readme
🌗 next-theme-handler
A lightweight, performant theme switcher for React applications with system preference detection and CSS variables support.

✨ Features
- 🌓 Three theme modes: Light, Dark, and System preference
- 🎨 CSS Variables based: Bring your own theme definitions
- ⚡ Optimized: Zero dependencies, ~2KB gzipped
- 💾 Persistent: Automatically saves user preference
- 📱 Responsive: Syncs with system color scheme changes
- ⚛️ React 18+ Ready: Works with all modern React versions
- 🏷 TypeScript Support: Full type definitions included
- 🖥 SSR Compatible: Works with Next.js, Remix, etc.
📦 Installation
# npm
npm install next-theme-handler
# yarn
yarn add next-theme-handler
# pnpm
pnpm add next-theme-handler🎨 Theme Configuration
/* include in global css or your root css file */
:root[data-theme="light"] {
--bg: #ffffff;
--text: #333333;
--primary: #2563eb;
--primary-hover: #1d4ed8;
--border: #e2e8f0;
--card-bg: #f8fafc;
--error: #dc2626;
}
:root[data-theme="dark"] {
--bg: #1a1a1a;
--text: #f8f8f8;
--primary: #3b82f6;
--primary-hover: #2563eb;
--border: #2d3748;
--card-bg: #1e293b;
--error: #ef4444;
}Advanced Setup (Next.js)
//_ThemeProviderWrapper.tsx
"use client"; // Mark as Client Component
import { ThemeProvider } from "next-theme-handler";
import { useEffect, useState } from "react";
export default function ThemeProviderWrapper({
children,
}: {
children: React.ReactNode;
}) {
// Prevent SSR mismatch by showing nothing until mounted
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
if (!mounted) {
return null;
}
return (
<ThemeProvider
defaultTheme="system"
storageKey="next-app-theme"
>
{children}
</ThemeProvider>
);
}//_layout.tsx or ur root tsx file
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import ThemeProviderWrapper from "@/components/ThemeProviderWrapper";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
export const metadata: Metadata = {
title: "Foo-Boo",
description: "Generated by Harsha",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body className={`${geistSans.variable} ${geistMono.variable} antialiased`} >
<ThemeProviderWrapper>{children}</ThemeProviderWrapper>
</body>
</html>
);
}
🎮 Using the Hook
"use client";
import { useTheme } from "next-theme-handler";
export default function ThemeSwitcher() {
const { theme, setTheme } = useTheme();
return (
<div className="flex gap-2">
<button
onClick={() => setTheme("light")}
className={`px-3 py-1 rounded ${theme === "light" ? "bg-blue-500 text-white" : "bg-gray-200"}`}
>
Light
</button>
<button
onClick={() => setTheme("dark")}
className={`px-3 py-1 rounded ${theme === "dark" ? "bg-blue-500 text-white" : "bg-gray-800 text-white"}`}
>
Dark
</button>
<button
onClick={() => setTheme("system")}
className="px-3 py-1 bg-gray-500 text-white rounded"
>
System
</button>
</div>
);
}🛠 API Reference
| Prop | Type | Default | Description | |--------------|---------------------|-----------|-----------------------------------------------------------------------------| | defaultTheme | 'light'|'dark'|'system' | 'light' | Default theme if no preference exists | | storageKey | string | 'theme' | localStorage key | | ssr | boolean | false | Enable SSR support |
🤝 Contributing
- Fork the repository.
- Create a branch (
git checkout -b feature/your-feature). - Commit changes (
git commit -am 'Add feature'). - Push (
git push origin feature/your-feature). - Open a PR.
📜 License
MIT © Harzsha
