
Posted on
Implementing a Custom Dark Mode Hook in Next.js
Dark mode has become an essential feature in modern web applications. In this tutorial, we'll create a custom dark mode hook for Next.js using TypeScript, which supports system preferences and allows manual override.
Prerequisites
- Basic knowledge of React and Next.js
- Node.js installed on your machine
- A Next.js project set up with TypeScript
Step 1: Create the Dark Mode Hook
First, let's create our custom dark mode hook. Create a new fileuseDarkMode.ts
in your project'shooks
folder:
// hooks/useDarkMode.tsimport{useState,useEffect,useCallback}from'react'typeMode='light'|'dark'|'system'exportconstuseDarkMode=()=>{const[mode,setMode]=useState<Mode>('system')constapplyTheme=useCallback((isDark:boolean)=>{document.documentElement.classList.toggle('dark',isDark)},[])constchangeMode=useCallback((newMode:Mode)=>{setMode(newMode)localStorage.setItem('theme',newMode)},[])useEffect(()=>{constsavedTheme=localStorage.getItem('theme')asMode|nullif(savedTheme){setMode(savedTheme)}constmediaQuery=window.matchMedia('(prefers-color-scheme: dark)')consthandleSystemThemeChange=(event:MediaQueryListEvent)=>{if(mode==='system'){applyTheme(event.matches)}}constapplyCurrentTheme=()=>{if(mode==='system'){applyTheme(mediaQuery.matches)}else{applyTheme(mode==='dark')}}applyCurrentTheme()mediaQuery.addEventListener('change',handleSystemThemeChange)return()=>{mediaQuery.removeEventListener('change',handleSystemThemeChange)}},[mode,applyTheme])return{mode,changeMode}}
This hook manages the dark mode state, applies the theme to the document, and listens for system preference changes.
Step 2: Create a Dark Mode Provider
Now, let's create a provider component to wrap our app. Create a new fileDarkModeProvider.tsx
in your project'scomponents
folder:
// components/DarkModeProvider.tsximportReact,{createContext,useContext}from'react'import{useDarkMode}from'../hooks/useDarkMode'typeDarkModeContextType=ReturnType<typeofuseDarkMode>constDarkModeContext=createContext<DarkModeContextType|undefined>(undefined)exportconstDarkModeProvider:React.FC<{children:React.ReactNode}>=({children})=>{constdarkMode=useDarkMode()return(<DarkModeContext.Providervalue={darkMode}>{children}</DarkModeContext.Provider>)}exportconstuseDarkModeContext=()=>{constcontext=useContext(DarkModeContext)if(context===undefined){thrownewError('useDarkModeContext must be used within a DarkModeProvider')}returncontext}
This provider component will make our dark mode functionality available throughout the app.
Step 3: Wrap Your App with the Dark Mode Provider
Update yourpages/_app.tsx
file to use the DarkModeProvider:
// pages/_app.tsximporttype{AppProps}from'next/app'import{DarkModeProvider}from'../components/DarkModeProvider'import'../styles/globals.css'functionMyApp({Component,pageProps}:AppProps){return(<DarkModeProvider><Component{...pageProps}/></DarkModeProvider>)}exportdefaultMyApp
Step 4: Create a Theme Toggle Component
Create a new component for the theme toggle button. Create a fileThemeToggle.tsx
in yourcomponents
folder:
// components/ThemeToggle.tsximportReactfrom'react'import{useDarkModeContext}from'./DarkModeProvider'constThemeToggle:React.FC=()=>{const{mode,changeMode}=useDarkModeContext()return(<selectvalue={mode}onChange={(e)=>changeMode(e.target.valueas'light'|'dark'|'system')}className="p-2 rounded border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-800 dark:text-white"><optionvalue="light">Light</option><optionvalue="dark">Dark</option><optionvalue="system">System</option></select>)}exportdefaultThemeToggle
Step 5: Add CSS for Light and Dark Themes
Update yourstyles/globals.css
file to include styles for both light and dark themes:
/* styles/globals.css */@tailwindbase;@tailwindcomponents;@tailwindutilities;:root{--foreground-rgb:0,0,0;--background-start-rgb:214,219,220;--background-end-rgb:255,255,255;}.dark{--foreground-rgb:255,255,255;--background-start-rgb:0,0,0;--background-end-rgb:0,0,0;}body{color:rgb(var(--foreground-rgb));background:linear-gradient(tobottom,transparent,rgb(var(--background-end-rgb)))rgb(var(--background-start-rgb));}
Step 6: Use the Theme in Your Components
Now you can use the theme in your components. Update yourpages/index.tsx
:
// pages/index.tsximportHeadfrom'next/head'importThemeTogglefrom'../components/ThemeToggle'exportdefaultfunctionHome(){return(<divclassName="min-h-screen p-4"><Head><title>DarkModeDemo</title><linkrel="icon"href="/favicon.ico"/></Head><mainclassName="max-w-4xl mx-auto"><h1className="text-4xl font-bold mb-4">WelcometoDarkModeDemo</h1><ThemeToggle/><pclassName="mt-4">Thisissomesampletexttoshowthethemechange.</p></main></div>)}
Step 7: Configure Tailwind for Dark Mode
Update yourtailwind.config.js
to enable the 'class' strategy for dark mode:
// tailwind.config.jsmodule.exports={darkMode:'class',// ... rest of your config}
Step 8: Test Your Dark Mode
Run your Next.js application:
npm run dev
Visithttp://localhost:3000
in your browser. You should now see your application with a working dark mode toggle that respects system preferences and allows manual override!
Conclusion
Congratulations! You've successfully implemented a custom dark mode hook in your Next.js application. This implementation uses React hooks for state management, respects system preferences, allows manual override, and uses Tailwind CSS for styling.
Remember to consider accessibility when implementing dark mode, ensuring that there's sufficient contrast between text and background colors in both themes.
Happy coding!
Top comments(0)
For further actions, you may consider blocking this person and/orreporting abuse