Agrega soporte de modo oscuro a tu aplicación con transiciones suaves y detección de preferencias del sistema.
La librería
nach-themes proporciona una forma simple y elegante de implementar modo oscuro en tus aplicaciones React, con soporte para preferencias del sistema, transiciones suaves y animaciones al hacer clic.Instalación
Instala el paquete vía pnpm:
npm install install nach-themes
Inicio Rápido
1. Configura tu CSS
Primero, asegúrate de tener definidas las variables de tema tanto para modo claro como oscuro en tu CSS:
1:root {2 --background: oklch(99.405% 0.00011 271.152);3 --foreground: oklch(0% 0 0);4 /* ... otras variables de modo claro */5}67.dark {8 --background: oklch(20% 0.02 230);9 --foreground: oklch(96% 0.008 230);10 /* ... otras variables de modo oscuro */11}
2. Envuelve tu app con ThemeProvider
Crea un componente providers para envolver tu aplicación:
1'use client';23import { ThemeProvider } from 'nach-themes';45export function Providers({ children }: { children: React.ReactNode }) {6 return <ThemeProvider>{children}</ThemeProvider>;7}
Luego úsalo en tu layout raíz:
1// app/layout.tsx2import { Providers } from '@/components/providers';34export default function RootLayout({ children }: { children: React.ReactNode }) {5 return (6 <html lang="es" suppressHydrationWarning>7 <body>8 <Providers>{children}</Providers>9 </body>10 </html>11 );12}
3. Crea un botón de cambio de tema
1'use client';23import { useTheme } from 'nach-themes';4import { MoonIcon, SunIcon } from '@radix-ui/react-icons';5import { Button } from '@/components/ui/button';67export function ThemeToggle() {8 const { theme, setTheme } = useTheme();910 const isDark = theme === 'dark';11 const Icon = isDark ? MoonIcon : SunIcon;1213 return (14 <Button15 variant="ghost"16 size="icon"17 onClick={(e) => setTheme(isDark ? 'light' : 'dark', e)}18 aria-label={`Cambiar a modo ${isDark ? 'claro' : 'oscuro'}`}19 >20 <Icon className="h-5 w-5" />21 </Button>22 );23}
Características
Detección de Tema del Sistema
Por defecto, el provider respeta la preferencia del sistema del usuario:
1<ThemeProvider>{children}</ThemeProvider>
La librería detecta automáticamente cambios en las preferencias del sistema y actualiza el tema en consecuencia cuando está configurado en
'system'.Temas Disponibles
El hook
useTheme proporciona acceso a tres modos de tema:'light'- Modo claro'dark'- Modo oscuro'system'- Sigue la preferencia del sistema
1const { theme, setTheme, themes } = useTheme();23// themes = ['light', 'dark', 'system']
Transiciones Suaves con View Transitions
La librería incluye soporte integrado para la View Transitions API, creando transiciones animadas suaves entre temas. Cuando pasas un evento de clic a
setTheme, crea una animación circular de revelación desde la posición del clic:1<button onClick={(e) => setTheme('dark', e)}>Cambiar Tema</button>
Sin el evento de clic, recurre a una transición estándar de fundido cruzado:
1<button onClick={() => setTheme('dark')}>Cambiar Tema</button>
Tema Resuelto
Obtén el tema real que se está aplicando, incluso cuando está configurado en
'system':1const { theme, resolvedTheme } = useTheme();23// theme = 'system'4// resolvedTheme = 'dark' (si el sistema prefiere oscuro)
Uso Avanzado
Toggle de Tema con Estado de Carga
Maneja el estado de hidratación para prevenir cambios en el layout:
1'use client';23import { useMounted } from '@/hooks/use-mounted';4import { useTheme } from 'nach-themes';5import { MoonIcon, SunIcon } from '@radix-ui/react-icons';6import { Button } from '@/components/ui/button';78export function ThemeToggle() {9 const mounted = useMounted();10 const { theme, setTheme } = useTheme();1112 if (!mounted) {13 return (14 <Button15 variant="ghost"16 size="icon"17 disabled18 className="bg-muted/30 animate-pulse cursor-default"19 >20 <div className="bg-foreground/20 h-5 w-5 rounded-full" />21 </Button>22 );23 }2425 const isDark = theme === 'dark';26 const Icon = isDark ? MoonIcon : SunIcon;2728 return (29 <Button30 variant="ghost"31 size="icon"32 onClick={(e) => setTheme(isDark ? 'light' : 'dark', e)}33 aria-label={`Cambiar a modo ${isDark ? 'claro' : 'oscuro'}`}34 className="h-8 w-8"35 >36 <Icon className="h-5 w-5" />37 </Button>38 );39}
El hook
useMounted puede implementarse como:1import { useEffect, useState } from 'react';23export function useMounted() {4 const [mounted, setMounted] = useState(false);56 useEffect(() => {7 setMounted(true);8 }, []);910 return mounted;11}
Menú Desplegable de Tema
Crea un selector de tema más sofisticado:
1'use client';23import { useTheme } from 'nach-themes';4import {5 DropdownMenu,6 DropdownMenuContent,7 DropdownMenuItem,8 DropdownMenuTrigger,9} from '@/components/ui/dropdown-menu';10import { Button } from '@/components/ui/button';11import { MoonIcon, SunIcon, DesktopIcon } from '@radix-ui/react-icons';1213export function ThemeDropdown() {14 const { theme, setTheme } = useTheme();1516 return (17 <DropdownMenu>18 <DropdownMenuTrigger asChild>19 <Button variant="ghost" size="icon">20 <SunIcon className="h-5 w-5 scale-100 rotate-0 transition-transform dark:scale-0 dark:-rotate-90" />21 <MoonIcon className="absolute h-5 w-5 scale-0 rotate-90 transition-transform dark:scale-100 dark:rotate-0" />22 <span className="sr-only">Cambiar tema</span>23 </Button>24 </DropdownMenuTrigger>25 <DropdownMenuContent align="end">26 <DropdownMenuItem onClick={() => setTheme('light')}>27 <SunIcon className="mr-2 h-4 w-4" />28 Claro29 </DropdownMenuItem>30 <DropdownMenuItem onClick={() => setTheme('dark')}>31 <MoonIcon className="mr-2 h-4 w-4" />32 Oscuro33 </DropdownMenuItem>34 <DropdownMenuItem onClick={() => setTheme('system')}>35 <DesktopIcon className="mr-2 h-4 w-4" />36 Sistema37 </DropdownMenuItem>38 </DropdownMenuContent>39 </DropdownMenu>40 );41}
Cambios de Tema Programáticos
Accede a información del tema en cualquier parte de tu app:
1'use client';23import { useTheme } from 'nach-themes';4import { useEffect } from 'react';56export function ContenidoDinamico() {7 const { resolvedTheme } = useTheme();89 useEffect(() => {10 // Actualiza librerías de terceros según el tema11 if (resolvedTheme === 'dark') {12 // Inicializa modo oscuro para servicios externos13 }14 }, [resolvedTheme]);1516 return (17 <div>18 <p>Tema actual: {resolvedTheme}</p>19 </div>20 );21}
Soporte de TypeScript
La librería está completamente tipada. Todos los hooks y componentes tienen definiciones completas de TypeScript:
1import { Theme } from 'nach-themes';23const miTema: Theme = 'dark';