Dark Mode
Add dark mode support to your application with smooth transitions and system preference detection.
The i7a-themes library provides a simple and elegant way to implement dark mode in your React applications, with support for system preferences, smooth transitions, and click animations.
Installation
Install the package via pnpm:
pnpm add i7a-themes
Quick Start
1. Configure your CSS
First, ensure you have both light and dark theme variables defined in your CSS:
:root {--background: oklch(99.405% 0.00011 271.152);--foreground: oklch(0% 0 0);/* ... other light mode variables */}.dark {--background: oklch(20% 0.02 230);--foreground: oklch(96% 0.008 230);/* ... other dark mode variables */}
2. Wrap your app with ThemeProvider
Create a providers component to wrap your application:
'use client';import { ThemeProvider } from 'i7a-themes';export function Providers({ children }: { children: React.ReactNode }) {return <ThemeProvider>{children}</ThemeProvider>;}
Then use it in your root layout:
// app/layout.tsximport { Providers } from '@/components/providers';export default function RootLayout({ children }: { children: React.ReactNode }) {return (<html lang="en" suppressHydrationWarning><body><Providers>{children}</Providers></body></html>);}
3. Create a theme toggle button
'use client';import { useTheme } from 'i7a-themes';import { MoonIcon, SunIcon } from '@radix-ui/react-icons';import { Button } from '@/components/ui/button';export function ThemeToggle() {const { theme, setTheme } = useTheme();const isDark = theme === 'dark';const Icon = isDark ? MoonIcon : SunIcon;return (<Buttonvariant="ghost"size="icon"onClick={(e) => setTheme(isDark ? 'light' : 'dark', e)}aria-label={`Switch to ${isDark ? 'light' : 'dark'} mode`}><Icon className="h-5 w-5" /></Button>);}
Features
System Theme Detection
By default, the theme provider respects the user's system preference:
<ThemeProvider>{children}</ThemeProvider>
The library automatically detects changes in system preferences and updates the theme accordingly when set to 'system'.
Available Themes
The useTheme hook provides access to three theme modes:
'light'- Light mode'dark'- Dark mode'system'- Follows system preference
const { theme, setTheme, themes } = useTheme();// themes = ['light', 'dark', 'system']
Smooth View Transitions
The library includes built-in support for the View Transitions API, creating smooth animated transitions between themes. When you pass a click event to setTheme, it creates a circular reveal animation from the click position:
<button onClick={(e) => setTheme('dark', e)}>Toggle Theme</button>
Without the click event, it falls back to a standard cross-fade transition:
<button onClick={() => setTheme('dark')}>Toggle Theme</button>
Resolved Theme
Get the actual theme being applied, even when set to 'system':
const { theme, resolvedTheme } = useTheme();// theme = 'system'// resolvedTheme = 'dark' (if system prefers dark)
Advanced Usage
Theme Toggle with Loading State
Handle the hydration state to prevent layout shifts:
'use client';import { useMounted } from '@/hooks/use-mounted';import { useTheme } from 'i7a-themes';import { MoonIcon, SunIcon } from '@radix-ui/react-icons';import { Button } from '@/components/ui/button';export function ThemeToggle() {const mounted = useMounted();const { theme, setTheme } = useTheme();if (!mounted) {return (<Buttonvariant="ghost"size="icon"disabledclassName="bg-muted/30 animate-pulse cursor-default"><div className="bg-foreground/20 h-5 w-5 rounded-full" /></Button>);}const isDark = theme === 'dark';const Icon = isDark ? MoonIcon : SunIcon;return (<Buttonvariant="ghost"size="icon"onClick={(e) => setTheme(isDark ? 'light' : 'dark', e)}aria-label={`Switch to ${isDark ? 'light' : 'dark'} mode`}className="h-8 w-8"><Icon className="h-5 w-5" /></Button>);}
The useMounted hook can be implemented as:
import { useEffect, useState } from 'react';export function useMounted() {const [mounted, setMounted] = useState(false);useEffect(() => {setMounted(true);}, []);return mounted;}
Theme Dropdown
Create a more sophisticated theme selector:
'use client';import { useTheme } from 'i7a-themes';import {DropdownMenu,DropdownMenuContent,DropdownMenuItem,DropdownMenuTrigger,} from '@/components/ui/dropdown-menu';import { Button } from '@/components/ui/button';import { MoonIcon, SunIcon, DesktopIcon } from '@radix-ui/react-icons';export function ThemeDropdown() {const { theme, setTheme } = useTheme();return (<DropdownMenu><DropdownMenuTrigger asChild><Button variant="ghost" size="icon"><SunIcon className="h-5 w-5 scale-100 rotate-0 transition-transform dark:scale-0 dark:-rotate-90" /><MoonIcon className="absolute h-5 w-5 scale-0 rotate-90 transition-transform dark:scale-100 dark:rotate-0" /><span className="sr-only">Toggle theme</span></Button></DropdownMenuTrigger><DropdownMenuContent align="end"><DropdownMenuItem onClick={() => setTheme('light')}><SunIcon className="mr-2 h-4 w-4" />Light</DropdownMenuItem><DropdownMenuItem onClick={() => setTheme('dark')}><MoonIcon className="mr-2 h-4 w-4" />Dark</DropdownMenuItem><DropdownMenuItem onClick={() => setTheme('system')}><DesktopIcon className="mr-2 h-4 w-4" />System</DropdownMenuItem></DropdownMenuContent></DropdownMenu>);}
Programmatic Theme Changes
Access theme information anywhere in your app:
'use client';import { useTheme } from 'i7a-themes';import { useEffect } from 'react';export function DynamicContent() {const { resolvedTheme } = useTheme();useEffect(() => {// Update third-party libraries based on themeif (resolvedTheme === 'dark') {// Initialize dark mode for external services}}, [resolvedTheme]);return (<div><p>Current theme: {resolvedTheme}</p></div>);}
TypeScript Support
The library is fully typed. All hooks and components have complete TypeScript definitions:
import { Theme } from 'i7a-themes';const myTheme: Theme = 'dark';