Добавляем тёмную тему на сайт Next.js: пример простой реализации
Привет! Сегодня покажу, как за несколько минут с помощью готовой библиотеки реализовать переключение между темной и светлой темой на сайте. Выбранная тема сохраняется в localStorage браузера и отлично работает на всех устройствах.
Код данной статьи доступен в репозитории 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;
}
В итоге получаем рабочую кнопку переключения тем:
На этом всё, добавляйте сайт в избранное, чтобы не пропустить новых статей, а также подписывайтесь на мой Дзен-канал – dzen.ru/in9var