@ionatan21/react-theme-provider
v1.1.5
Published
A lightweight React theme provider with system theme detection
Maintainers
Readme
@ionatan21/react-theme-provider
A lightweight and efficient theme provider for React with automatic system theme detection, localStorage persistence, and full TypeScript support.
Features
- Automatic system theme detection - Respects user's
prefers-color-schemepreference - Automatic persistence - Saves user preference in localStorage
- Real-time synchronization - Listens to system preference changes
- Default styles included - 19 CSS variables and base styles for common HTML elements
- SSR-ready - Compatible with Next.js and other server-side rendering solutions
- TypeScript native - Full types included
- Zero dependencies - Only requires React as peer dependency
- Lightweight - Less than 2KB minified and gzipped
- Fully customizable - Use default themes or create your own
Installation
npm install @ionatan21/react-theme-provideryarn add @ionatan21/react-theme-providerpnpm add @ionatan21/react-theme-providerBasic Usage
1. Wrap your application with ThemeProvider
import { ThemeProvider } from '@ionatan21/react-theme-provider';
function App() {
return (
<ThemeProvider defaultTheme="system" storageKey="app-theme">
<YourApp />
</ThemeProvider>
);
}That's it! The library includes default styles for common HTML elements and 19 CSS variables that are applied automatically.
2. Use the useTheme hook in your components
import { useTheme } from '@ionatan21/react-theme-provider';
function ThemeToggle() {
const { theme, setTheme } = useTheme();
return (
<button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
Toggle theme
</button>
);
}3. (Optional) Use CSS variables in your custom styles
.my-component {
background-color: hsl(var(--background));
color: hsl(var(--foreground));
border: 1px solid hsl(var(--border));
}
.my-button {
background-color: hsl(var(--primary));
color: hsl(var(--primary-foreground));
}API
ThemeProvider
Main component that should wrap your application.
Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| children | ReactNode | - | Child components |
| defaultTheme | 'light' \| 'dark' \| 'system' | 'system' | Default theme if none is saved |
| storageKey | string | 'theme' | Key for localStorage |
| lightColors | Partial<ThemeColors> | - | Custom colors for light theme |
| darkColors | Partial<ThemeColors> | - | Custom colors for dark theme |
| disableDefaultColors | boolean | false | Disables default CSS variables |
| disableBaseStyles | boolean | false | Disables base styles for HTML elements |
useTheme
Hook to access and modify the current theme.
Returns
{
theme: 'light' | 'dark' | 'system'; // User's selected theme
resolvedTheme: 'light' | 'dark'; // Effective theme applied
setTheme: (theme: Theme) => void; // Function to change theme
}theme: The theme selected by the user (can be 'system')resolvedTheme: The actual theme applied to the DOM (always 'light' or 'dark')setTheme: Function to change and persist the theme
Default Styles
The library includes automatic styles for common HTML elements. You can use them directly without writing additional CSS.
Available CSS Variables
--background /* Main background */
--foreground /* Main text */
--card /* Card background */
--card-foreground /* Card text */
--popover /* Popover background */
--popover-foreground /* Popover text */
--primary /* Primary color */
--primary-foreground /* Text on primary */
--secondary /* Secondary color */
--secondary-foreground /* Text on secondary */
--muted /* Muted color */
--muted-foreground /* Muted text */
--accent /* Accent color */
--accent-foreground /* Text on accent */
--destructive /* Destructive/error color */
--destructive-foreground /* Text on destructive */
--border /* Border color */
--input /* Input color */
--ring /* Focus ring color */Elements with automatic styles
- body - Background and text color
- button - Complete styles with variants
- input, textarea, select - Form styles
- a - Links with hover
- h1-h6 - Headings
- table, th, td - Tables
- code, pre - Code blocks
- blockquote, hr - Content elements
- Custom scrollbar
Button Variants
<button>Primary</button>
<button class="secondary">Secondary</button>
<button class="outline">Outline</button>
<button class="destructive">Destructive</button>
<button class="ghost">Ghost</button>Color Customization
You can customize colors while keeping the base styles:
<ThemeProvider
lightColors={{
primary: '200 100% 50%',
background: '0 0% 98%'
}}
darkColors={{
primary: '200 100% 60%',
background: '220 20% 8%'
}}
>
<App />
</ThemeProvider>Note: Colors are in HSL format without hsl(): 'hue saturation% lightness%'
Disable Default Styles
If you prefer to use only the theme switching functionality without the styles:
<ThemeProvider disableBaseStyles>
<App />
</ThemeProvider>Or disable everything completely:
<ThemeProvider disableDefaultColors>
<App />
</ThemeProvider>SSR / Next.js
Next.js App Router (recommended)
// app/providers.tsx
'use client';
import { ThemeProvider } from '@ionatan21/react-theme-provider';
export function Providers({ children }: { children: React.ReactNode }) {
return (
<ThemeProvider defaultTheme="system" storageKey="theme">
{children}
</ThemeProvider>
);
}// app/layout.tsx
import { Providers } from './providers';
export default function RootLayout({ children }) {
return (
<html lang="en" suppressHydrationWarning>
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}Important: Add suppressHydrationWarning to the <html> element to avoid hydration warnings when the theme is applied.
Next.js Pages Router
// pages/_app.tsx
import { ThemeProvider } from '@ionatan21/react-theme-provider';
import type { AppProps } from 'next/app';
export default function App({ Component, pageProps }: AppProps) {
return (
<ThemeProvider defaultTheme="system" storageKey="theme">
<Component {...pageProps} />
</ThemeProvider>
);
}Real Example
import { ThemeProvider, useTheme } from '@ionatan21/react-theme-provider';
import { Moon, Sun, Monitor } from 'lucide-react';
function App() {
return (
<ThemeProvider defaultTheme="system" storageKey="my-app-theme">
<div className="app">
<Header />
<main>
<h1>My Application</h1>
<p>With full support for light and dark themes</p>
</main>
</div>
</ThemeProvider>
);
}
function Header() {
const { theme, setTheme } = useTheme();
return (
<header>
<nav>
<h1>My App</h1>
<ThemeSelector />
</nav>
</header>
);
}
function ThemeSelector() {
const { theme, setTheme } = useTheme();
const themes = [
{ value: 'light', icon: Sun, label: 'Light' },
{ value: 'dark', icon: Moon, label: 'Dark' },
{ value: 'system', icon: Monitor, label: 'System' },
] as const;
return (
<div className="theme-selector">
{themes.map(({ value, icon: Icon, label }) => (
<button
key={value}
onClick={() => setTheme(value)}
className={theme === value ? 'active' : ''}
aria-label={`Switch to ${label} theme`}
>
<Icon size={20} />
<span>{label}</span>
</button>
))}
</div>
);
}
export default App;/* styles.css */
/* Additional customization example */
.my-card {
background-color: hsl(var(--card));
color: hsl(var(--card-foreground));
border: 1px solid hsl(var(--border));
border-radius: 0.5rem;
padding: 1.5rem;
}
.my-card h2 {
color: hsl(var(--primary));
margin-bottom: 1rem;
}
.alert-error {
background-color: hsl(var(--destructive) / 0.1);
color: hsl(var(--destructive));
border: 1px solid hsl(var(--destructive));
padding: 1rem;
border-radius: 0.375rem;
}License
ISC
