Как сделать темную тему для сайта

Приветствую, друзья, сегодня я покажу как сделать темную тему для сайта. Работать наша темная тема будет с помощью CSS, JS. Так же мы будем сохранять последнюю выбранную тему в LocalStorage, для того, что бы темная тема работала даже после перезагрузки страницы и закрытия браузера. Давайте начнем!

Подготовка сайта

Для примера я создал очень простую страничку, где есть заголовок и несколько карточек. Скриншот того, как она выглядит и код вы можете найти ниже.

Как сделать темную тему для сайта
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Dark mode tutorial</title>
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;700&display=swap" rel="stylesheet">
    <link rel="stylesheet" href="css/style.css">
</head>

<body>
    <h1 class="page-title">
        Dark mode tutorial
    </h1>
    <div class="wrapper">
        <div class="card">
            <h2 class="title">
                Card title
            </h2>
            <p class="text">
                Lorem ipsum dolor sit amet consectetur adipisicing elit. Natus facilis veritatis id iure nulla sunt
                consectetur laudantium minus voluptas eligendi ab voluptatum, voluptatibus neque! Ratione aliquam at vel
                nobis voluptate.
                Doloribus voluptates voluptatum facilis voluptate dicta fuga, architecto non, at explicabo impedit
                dolore quos nihil voluptatibus, tempore minus vero ullam ipsum illo corrupti corporis numquam! Facilis
                sint dolore ducimus suscipit!
            </p>
        </div>
        <!-- Карточки повторяются 9 раз, так что я оставил только 1 как пример -->
    <script src="js/main.js"></script>
</body>
</html>
* {
    box-sizing: border-box;
}

body {
    margin: 0;
    padding: 0;
    font-family: 'Montserrat', sans-serif;
    background: #fff;
}

.wrapper {
    max-width: 1400px;
    margin: 0 auto;
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: space-evenly;
    flex-wrap: wrap;
}

.page-title {
    margin-top: 50px;
    margin-bottom: 100px;
    text-align: center;
    font-size: 46px;
    color: #222222;
}

.card {
    width: 28%;
    padding: 20px;
    border-radius: 10px;
    background: #ffffff;
    box-shadow: 0 0 10px rgba(0,0,0,0.1);
    margin-bottom: 50px;
}

.card .title {
    font-size: 32px;
    color: #222222;
    margin-top: 0;
    margin-bottom: 20px;
}

.card .text {
    margin: 0;
    font-size: 18px;
    line-height: 1.5;
    color: #222222;
}

Добавляем переключатель темной/светлой темы на сайт

Далее реализуем переключатель темной и светлой темы для сайта. Я буду делать это в виде иконок солнца и луны, как и сейчас реализовано это на данном сайте. Так же я присвою иконкам класс, с помощью которого, мы будем определять, что пользователь хочет сменить тему. И так же добавлю аттрибут data-theme с названием темы, которую мы будем применять при клике. Код вы можете посмотреть ниже.

<div class="theme">
    <svg class="changeTheme" data-theme="light" width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg" style="display: none;"> <path d="M20 30C22.6522 30 25.1957 28.9464 27.0711 27.0711C28.9464 25.1957 30 22.6522 30 20C30 17.3478 28.9464 14.8043 27.0711 12.9289C25.1957 11.0536 22.6522 10 20 10C17.3478 10 14.8043 11.0536 12.9289 12.9289C11.0536 14.8043 10 17.3478 10 20C10 22.6522 11.0536 25.1957 12.9289 27.0711C14.8043 28.9464 17.3478 30 20 30V30ZM20 0C20.3315 0 20.6495 0.131696 20.8839 0.366117C21.1183 0.600537 21.25 0.918479 21.25 1.25V6.25C21.25 6.58152 21.1183 6.89946 20.8839 7.13388C20.6495 7.3683 20.3315 7.5 20 7.5C19.6685 7.5 19.3505 7.3683 19.1161 7.13388C18.8817 6.89946 18.75 6.58152 18.75 6.25V1.25C18.75 0.918479 18.8817 0.600537 19.1161 0.366117C19.3505 0.131696 19.6685 0 20 0V0ZM20 32.5C20.3315 32.5 20.6495 32.6317 20.8839 32.8661C21.1183 33.1005 21.25 33.4185 21.25 33.75V38.75C21.25 39.0815 21.1183 39.3995 20.8839 39.6339C20.6495 39.8683 20.3315 40 20 40C19.6685 40 19.3505 39.8683 19.1161 39.6339C18.8817 39.3995 18.75 39.0815 18.75 38.75V33.75C18.75 33.4185 18.8817 33.1005 19.1161 32.8661C19.3505 32.6317 19.6685 32.5 20 32.5V32.5ZM40 20C40 20.3315 39.8683 20.6495 39.6339 20.8839C39.3995 21.1183 39.0815 21.25 38.75 21.25H33.75C33.4185 21.25 33.1005 21.1183 32.8661 20.8839C32.6317 20.6495 32.5 20.3315 32.5 20C32.5 19.6685 32.6317 19.3505 32.8661 19.1161C33.1005 18.8817 33.4185 18.75 33.75 18.75H38.75C39.0815 18.75 39.3995 18.8817 39.6339 19.1161C39.8683 19.3505 40 19.6685 40 20ZM7.5 20C7.5 20.3315 7.3683 20.6495 7.13388 20.8839C6.89946 21.1183 6.58152 21.25 6.25 21.25H1.25C0.918479 21.25 0.600537 21.1183 0.366117 20.8839C0.131696 20.6495 0 20.3315 0 20C0 19.6685 0.131696 19.3505 0.366117 19.1161C0.600537 18.8817 0.918479 18.75 1.25 18.75H6.25C6.58152 18.75 6.89946 18.8817 7.13388 19.1161C7.3683 19.3505 7.5 19.6685 7.5 20ZM34.1425 5.8575C34.3768 6.09191 34.5085 6.40979 34.5085 6.74125C34.5085 7.07271 34.3768 7.39059 34.1425 7.625L30.6075 11.1625C30.4913 11.2786 30.3533 11.3706 30.2016 11.4333C30.0498 11.4961 29.8871 11.5283 29.7229 11.5282C29.3912 11.528 29.0731 11.396 28.8387 11.1612C28.7227 11.045 28.6307 10.9071 28.5679 10.7553C28.5052 10.6035 28.4729 10.4409 28.4731 10.2766C28.4733 9.94491 28.6053 9.62689 28.84 9.3925L32.375 5.8575C32.6094 5.62316 32.9273 5.49152 33.2588 5.49152C33.5902 5.49152 33.9081 5.62316 34.1425 5.8575V5.8575ZM11.16 28.84C11.3943 29.0744 11.526 29.3923 11.526 29.7237C11.526 30.0552 11.3943 30.3731 11.16 30.6075L7.625 34.1425C7.38925 34.3702 7.07349 34.4962 6.74575 34.4933C6.418 34.4905 6.10449 34.359 5.87273 34.1273C5.64097 33.8955 5.50951 33.582 5.50666 33.2543C5.50381 32.9265 5.6298 32.6108 5.8575 32.375L9.3925 28.84C9.62691 28.6057 9.94479 28.474 10.2762 28.474C10.6077 28.474 10.9256 28.6057 11.16 28.84V28.84ZM34.1425 34.1425C33.9081 34.3768 33.5902 34.5085 33.2588 34.5085C32.9273 34.5085 32.6094 34.3768 32.375 34.1425L28.84 30.6075C28.6123 30.3717 28.4863 30.056 28.4892 29.7282C28.492 29.4005 28.6235 29.087 28.8552 28.8552C29.087 28.6235 29.4005 28.492 29.7282 28.4892C30.056 28.4863 30.3717 28.6123 30.6075 28.84L34.1425 32.375C34.3768 32.6094 34.5085 32.9273 34.5085 33.2588C34.5085 33.5902 34.3768 33.9081 34.1425 34.1425ZM11.16 11.1625C10.9256 11.3968 10.6077 11.5285 10.2762 11.5285C9.94479 11.5285 9.62691 11.3968 9.3925 11.1625L5.8575 7.625C5.73811 7.50969 5.64288 7.37176 5.57737 7.21926C5.51186 7.06675 5.47738 6.90273 5.47594 6.73675C5.47449 6.57078 5.50612 6.40618 5.56897 6.25256C5.63182 6.09894 5.72464 5.95937 5.84201 5.84201C5.95937 5.72464 6.09894 5.63182 6.25256 5.56897C6.40618 5.50612 6.57078 5.47449 6.73675 5.47594C6.90273 5.47738 7.06675 5.51186 7.21926 5.57737C7.37176 5.64288 7.50969 5.73811 7.625 5.8575L11.16 9.3925C11.2764 9.50861 11.3688 9.64655 11.4318 9.79842C11.4948 9.95028 11.5272 10.1131 11.5272 10.2775C11.5272 10.4419 11.4948 10.6047 11.4318 10.7566C11.3688 10.9084 11.2764 11.0464 11.16 11.1625V11.1625Z" fill="#ffffff"></path></svg>
    <svg class="changeTheme" data-theme="dark" width="38" height="40" viewBox="0 0 38 40" fill="none" xmlns="http://www.w3.org/2000/svg" style="display: block;"> <path d="M15 0.695136C15.244 0.991667 15.3935 1.35457 15.4292 1.73694C15.4648 2.11932 15.385 2.5036 15.2 2.84014C13.7546 5.49382 12.9998 8.46837 13.005 11.4901C13.005 21.5426 21.2 29.6826 31.3 29.6826C32.6175 29.6826 33.9 29.5451 35.1325 29.2826C35.5103 29.2008 35.9039 29.2321 36.264 29.3726C36.6241 29.5131 36.9349 29.7566 37.1575 30.0726C37.3925 30.4013 37.5123 30.7983 37.4983 31.2021C37.4842 31.6058 37.3372 31.9936 37.08 32.3051C35.1192 34.7137 32.6456 36.6544 29.8396 37.9856C27.0335 39.3168 23.9658 40.0051 20.86 40.0001C9.335 40.0001 0 30.7151 0 19.2751C0 10.6651 5.285 3.28014 12.81 0.150136C13.1848 -0.00828447 13.601 -0.0407995 13.9959 0.057475C14.3908 0.15575 14.7431 0.379507 15 0.695136V0.695136Z" fill="#222222"></path></svg>
</div>
.theme {
    position: fixed;
    cursor: pointer;
    top: 50%;
    left: 10px;
    transform: translateY(-50%);
    padding: 10px;
    background: #ffffff;
    border-radius: 10px;
    box-shadow: 0 0 10px rgba(39,38,40,0.4);
}

Подготовим код сайта

Далее нам необходимо создать еще 2 CSS файла. Я назову их theme-light.css и theme-dark.css. После этого, нам нужно вырезать с основного файла со стилями все стили, которые отвечают за те цвета, которые мы хотим менять при смене темы. Вставляем вырезанный код в файлы тем, которые мы только что создали. Теперь меняем цвета по своему усмотрению в зависимости от темы. В моем случае, файлы стилей будут выглядеть вот так:

theme-light.css

body {
    background: #fff;
}

.page-title {
    color: #222222;
}

.card {
    background: #ffffff;
    box-shadow: 0 0 10px rgba(0,0,0,0.1);
}

.card .title {
    color: #222222;
}

.card .text {
    color: #222222;
}

.theme {
    background: #ffffff;
    box-shadow: 0 0 10px rgba(39,38,40,0.4);
}

theme-dark.css

body {
    background: #222222;
}

.page-title {
    color: #ffffff;
}

.card {
    background: #222222;
    box-shadow: 0 0 10px rgba(255,255,255,255.1);
}

.card .title {
    color: #ffffff;
}

.card .text {
    color: #ffffff;
}

.theme {
    background: #222222;
    box-shadow: 0 0 10px rgba(255,255,255,0.1);
}

Смена на темную тему и обратно при клике

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

<head>
    ...
    <!-- Добавляем в head пустой тег link -->
    <link rel="stylesheet" title="theme" href="#">
</head>
let changeThemeButtons = document.querySelectorAll('.changeTheme'); // Помещаем кнопки смены темы в переменную

changeThemeButtons.forEach(button => {
    button.addEventListener('click', function () { // К каждой добавляем обработчик событий на клик
        let theme = this.dataset.theme; // Помещаем в переменную название темы из атрибута data-theme
        applyTheme(theme); // Вызываем функцию, которая меняет тему и передаем в нее её название
    });
});

function applyTheme(themeName) {
    document.querySelector('[title="theme"]').setAttribute('href', `css/theme-${themeName}.css`); // Помещаем путь к файлу темы в пустой link в head
    changeThemeButtons.forEach(button => {
        button.style.display = 'block'; // Показываем все кнопки смены темы
    });
    document.querySelector(`[data-theme="${themeName}"]`).style.display = 'none'; // Но скрываем кнопку для активной темы
}

Сейчас у вас уже должна меняться тема при клике. Осталась только одна проблема — при перезапуске страницы выбранная тема не сохраняется. Давайте это исправим!

Сохраняем выбранную тему сайта в браузере

Использовать для этого мы будем LocalStorage. Ниже я размещу весь готовый JavaScript код и добавлю комментарии только к новому функционалу.

let changeThemeButtons = document.querySelectorAll('.changeTheme');

changeThemeButtons.forEach(button => {
    button.addEventListener('click', function () {
        let theme = this.dataset.theme;
        applyTheme(theme);
    });
});

function applyTheme(themeName) {
    document.querySelector('[title="theme"]').setAttribute('href', `css/theme-${themeName}.css`);
    changeThemeButtons.forEach(button => {
        button.style.display = 'block';
    });
    document.querySelector(`[data-theme="${themeName}"]`).style.display = 'none';
    localStorage.setItem('theme', themeName);
}

let activeTheme = localStorage.getItem('theme'); // Проверяем есть ли в LocalStorage записано значение для 'theme' и присваиваем его переменной.

if(activeTheme === null || activeTheme === 'light') { // Если значение не записано, или оно равно 'light' - применяем светлую тему
    applyTheme('light');
} else if (activeTheme === 'dark') { // Если значение равно 'dark' - применяем темную
    applyTheme('dark');
}

На этом все! Но хочу отметить еще один момент. Если у вас при активной темной теме при перезагрузке на доли секунды виден белый задник — ничего страшного. Скорее всего, при загрузке сайта на хостинг проблема исчезнет. И если вы хотите минимизировать шанс возникновения этой проблемы на хостинге, то рекомендую вынести этот JavaScript в отдельный файл и разместить его вместе с элементом <div class=»theme»> сразу после открытия тега <body>. Сверху <div class=»theme»> и сразу под ним <script>.

Спасибо, что прочитали! Если у вас остались вопросы, задавайте их в комментариях на YouTube или в нашем Telegram чате.

Так же советую ознакомиться с другими моими статьями:

1. Стилизация checkbox. Кастомные чекбоксы

2. Попап на чистом JS. Модальное окно без jQuery