Добавляем тёмную тему на сайт Next.js: пример простой реализации

20 июня 2025
2 минуты

Привет! Сегодня покажу, как за несколько минут с помощью готовой библиотеки реализовать переключение между темной и светлой темой на сайте. Выбранная тема сохраняется в localStorage браузера и отлично работает на всех устройствах.

превью | blog.in9var.ru
Превью | blog.in9var.ru

Код данной статьи доступен в репозитории GitHub по ссылке.

Устанавливаем пакет next-themes

Вводим команду в консоль:

npm i next-themes

Данный пакет сильно упрощает реализацию переключения тем: он берет на себя все заботы о хранении выбранной темы и решает такие проблемы, как «вспышки» при обновлении страницы с выбранной темной темой.

Если вы решите реализовывать переключение тем самостоятельно, вы поймете, о чем я.

Создаем провайдер темы

В папке app/theme создаем файл theme-provider.tsx со следующим содержимым:

'use client'; import { ThemeProvider as NextThemesProvider } from 'next-themes'; export function ThemeProvider({ children, ...props }) { return <NextThemesProvider {...props}>{children}</NextThemesProvider>; }

Этот кусок кода взят из официальной документации.

Создаем переключатель темы

В папке app/components создадим файл ThemeSwitcher.jsx со следующим содержимым:

'use client'; import { useEffect, useState } from 'react'; import { useTheme } from 'next-themes'; import { FaSun, FaMoon } from 'react-icons/fa'; const ThemeSwitcher = () => { const [mounted, setMounted] = useState(false); const { theme, setTheme } = useTheme(); const handleClick = () => { if (theme === 'light') setTheme('dark'); else setTheme('light'); }; useEffect(() => { setMounted(true); }, [theme]); if (!mounted) return null; return ( <button type='button' onClick={handleClick} className={`cursor-pointer rounded-md p-2 border-2 ${theme === 'light' ? 'border-primary' : 'border-secondary'}`} > {theme === 'light' ? ( <FaSun size={28} className='text-primary' /> ) : ( <FaMoon size={28} className='text-secondary' /> )} </button> ); }; export default ThemeSwitcher;

Используя встроенный в библиотеку next-themes кастомный хук useTheme(), мы сможем управлять переключением темы, а также контролировать текущую тему. Создадим кнопку button, которая меняет иконку в зависимости от выбранной темы, а при нажатии переключает тему на противоположную.

Подключаем провайдер в layout.jsx

Чтобы переключение темы работало на всех страницах нашего проекта, необходимо «обернуть» все дочерние элементы сайта в наш созданный компонент провайдера.

Код layout.jsx:

import { Geist, Geist_Mono } from 'next/font/google'; import './globals.css'; import { ThemeProvider } from './theme/theme-provider'; import ThemeSwitcher from './components/ThemeSwitcher'; const geistSans = Geist({ variable: '--font-geist-sans', subsets: ['latin'], }); const geistMono = Geist_Mono({ variable: '--font-geist-mono', subsets: ['latin'], }); export const metadata = { title: 'Create Next App', description: 'Generated by create next app', }; export default function RootLayout({ children }) { return ( <html lang='ru' suppressHydrationWarning> <body className={`${geistSans.variable} ${geistMono.variable} antialiased`}> <ThemeProvider attribute='data-mode' defaultTheme='light'> <header className='flex justify-end w-full px-5 mx-auto max-w-screen-xl py-4'> <ThemeSwitcher /> </header> {children} </ThemeProvider> </body> </html> ); }

Как это работает?

После всех проделанных манипуляций при нажатии на кнопку переключения тем в тег html нашего сайта будут добавлены атрибут и стиль, соответствующие темной либо светлой теме.

<html lang="ru" data-mode="light" style="color-scheme: light;"></html>

В нашем случае мы задаем цвета темы через переменные, и за счет атрибута [data-mode] сайт перекрашивается в соответствующие выбранной теме цвета.

:root { --color-primary: 270 80 20; --color-secondary: 47 80 50; --color-on-primary: 260 9 13; --color-on-secondary: 260 9 13; } [data-mode='light'] { --color-background: 0 0 100; --color-on-background: 0 0 0; } [data-mode='dark'] { --color-background: 0 0 0; --color-on-background: 0 0 100; }

В итоге получаем рабочую кнопку переключения тем:

фото | изображение | blog.in9var.ru
Фото | blog.in9var.ru
фото | изображение | blog.in9var.ru
Фото | blog.in9var.ru

На этом всё, добавляйте сайт в избранное, чтобы не пропустить новых статей, а также подписывайтесь на мой Дзен-канал – dzen.ru/in9var