CSS-переменные: как сделать темную тему

Реализуем темную (и не только) тему с помощью css-переменных и немного JavaScript

1

Посмотрите пример на Codepen

Создадим базовую разметку

HTML

<div class="theme">
  <div class="theme__button">Switch theme</div>
  <div class="container">
    <div class="theme__section">
      <div class="theme__title">Lorem ipsum dolor</div>
      <div class="theme__text">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quis cum culpa, vero mollitia exercitationem libero et possimus assumenda aut, deleniti sapiente rem delectus architecto doloribus beatae, dolorum amet? At, delectus!</div>
    </div>
    <div class="theme__section">
      <div class="theme__title">Sit amet</div>
      <div class="theme__text">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quis cum culpa, vero mollitia exercitationem libero et possimus assumenda aut, deleniti sapiente rem delectus architecto doloribus beatae, dolorum amet? At, delectus! Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quis cum culpa, vero mollitia exercitationem libero et possimus assumenda aut, deleniti sapiente rem delectus architecto doloribus beatae, dolorum amet? At, delectus! Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quis cum culpa, vero mollitia exercitationem libero et possimus assumenda aut, deleniti sapiente rem delectus architecto doloribus beatae, dolorum amet? At, delectus!</div>
    </div>
    <div class="theme__section">
      <div class="theme__title">At, delectus!</div>
      <div class="theme__text">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quis cum culpa, vero mollitia exercitationem libero et possimus assumenda aut, deleniti sapiente rem delectus architecto doloribus beatae, dolorum amet? At, delectus!</div>
    </div>
    <div class="theme__section">
      <div class="theme__title">Libero et possimus</div>
      <div class="theme__text">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quis cum culpa, vero mollitia exercitationem libero et possimus assumenda aut, deleniti sapiente rem delectus architecto doloribus beatae, dolorum amet? At, delectus! Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quis cum culpa, vero mollitia exercitationem libero et possimus assumenda aut, deleniti sapiente rem delectus architecto doloribus beatae, dolorum amet? At, delectus! Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quis cum culpa, vero mollitia exercitationem libero et possimus assumenda aut, deleniti sapiente rem delectus architecto doloribus beatae, dolorum amet? At, delectus!</div>
    </div>
    <div class="theme__section">
      <div class="theme__title">Quis cum culpa</div>
      <div class="theme__text">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quis cum culpa, vero mollitia exercitationem libero et possimus assumenda aut, deleniti sapiente rem delectus architecto doloribus beatae, dolorum amet? At, delectus!</div>
    </div>
  </div>
</div>


Добавим CSS-переменные и остальные стили

CSS-переменные добавляются следующим образом

:root {
  --theme-color: #efefef;
  --primary-color: #000e29;
}

:root - псевдо-элемент который находит корневой элемент документа, для HTML идентичен селектору html, но с большим приоритетом. То есть если заданы одинаковые свойства для html и :root, то будут применены свойства записанные в :root

Переменные можно определять и в других элементах, но хорошей практикой считается определение переменных именно через псевдо-элемент :root

Объявленные переменные используем следующим образом

.theme {
  background: var(--theme-color);
  color: var(--primary-color);
}

Предположим, эти же переменные мы хотим использовать и для других элементов, но при других свойствах

.theme__button {
  background: var(--primary-color);
  color: var(--theme-color);
}

Теперь, например, при изменении значения переменной --primary-color в :root, значения изменятся во всех элементах, использующих данную переменную

Менять значения переменных будем через атрибут data-theme

[data-theme="light"] {
  --theme-color: #efefef;
  --primary-color: #253c78;
}
[data-theme="dark"] {
  --theme-color: #0f3057;
  --primary-color: #e7e7de;
}
[data-theme="soft"] {
  --theme-color: #1a1a2e;
  --primary-color: #e94560;
}

Если блоку <div class="theme"> добавить атрибут data-theme со значением dark, то при этом получаем <div class="theme" data-theme="dark"> и значения переменных для этого блока и его дочерних элементов изменятся на установленные

Этот прием мы и будем использовать для создания темной темы

CSS

@import url("https://fonts.googleapis.com/css2?family=Rubik:wght@300;900&display=swap");
*,
*::before,
*::after {
  box-sizing: border-box;
}
body {
  margin: 0;
  padding: 0;
}
:root {
  --theme-color: #efefef;
  --primary-color: #000e29;
}
[data-theme="light"] {
  --theme-color: #efefef;
  --primary-color: #253c78;
}
[data-theme="dark"] {
  --theme-color: #0f3057;
  --primary-color: #e7e7de;
}
[data-theme="soft"] {
  --theme-color: #1a1a2e;
  --primary-color: #e94560;
}
.container {
  max-width: 1170px;
  margin: 0 auto;
  padding: 0 30px;
}
.theme {
  transition: all 0.4s ease;
  font-family: 'Rubik', sans-serif;
  padding: 60px 0;
  position: relative;
  background: var(--theme-color);
  color: var(--primary-color);
  min-height: 100vh;
}
.theme * {
  transition: all 0.4s ease;
}
.theme__section:first-child {
  padding-top: 60px;
}
.theme__section:not(:last-child) {
  padding-bottom: 60px;
  margin-bottom: 60px;
  border-bottom: 1px solid var(--primary-color);
}
.theme__title {
  font-size: 36px;
  font-weight: bold;
  margin-bottom: 30px;
  color: var(--primary-color);
}
.theme__text {
  font-size: 18px;
  color: var(--primary-color);
}
.theme__button {
  position: fixed;
  top: 30px;
  right: 60px;
  background: var(--primary-color);
  color: var(--theme-color);
  height: 60px;
  padding: 0 30px;
  display: inline-flex;
  align-items: center;
  border-radius: 10px;
  cursor: pointer;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}
.theme__button:hover {
  box-shadow: 0 5px 15px rgba(0,0,0,0.5);
}


Пишем JavaScript код

Создаем main.js файл, сохраняем в папке с js-файлами, подключаем перед закрывающим тегом </body> и пишем следующий код

const btn = document.querySelector('.theme__button') /* находим кнопку <div class="theme__button">Switch theme</div> по селектору .theme__button и записываем ее в переменную btn */
const theme = document.querySelector('.theme') /* находим блок <div class="theme">, в котором будет меняться тема, по селектору .theme и записываем ее в переменную theme */
const currentTheme = localStorage.getItem('theme') /* если в localStorage есть запись с ключом theme, тогда записываем ее значение в переменную currentTheme */

function setTheme(name){ /* создаем отдельную функцию для добавления блоку <div class="theme"> атрибута 'data-theme' с переданным значением name и добавления в localStorage записи c ключом 'theme' и с тем же переданным значением name */
  theme.setAttribute('data-theme', name) /* блоку <div class="theme"> добавляем атрибут 'data-theme' с переданным значением name */
  localStorage.setItem('theme', name) /* в localStorage добавляем запись c ключом 'theme' и переданным значением name */
}

if (currentTheme) { /* проверяем, есть ли в переменной currentTheme значение из localStorage */
  theme.setAttribute('data-theme', currentTheme) /* если в переменную currentTheme уже записано значение из localStorage, то блоку <div class="theme"> добавляем атрибут 'data-theme' со значением переменной currentTheme */
} else {
  setTheme('light') /* если в переменную currentTheme еще ничего не записано, тогда устанавливаем значение 'light' */
}

btn.addEventListener('click', () => { /* при клике на кнопку <div class="theme__button">Switch theme</div> */
  if (theme.getAttribute('data-theme') === 'light') { /* если у блока <div class="theme"> атрибут 'data-theme' строго равен значению 'light'  */
    setTheme('dark') /* тогда устанавливаем значение 'dark' */
  } else if (theme.getAttribute('data-theme') === 'dark') { /* если у блока <div class="theme"> атрибут 'data-theme' строго равен значению 'dark'  */
    setTheme('soft') /* тогда устанавливаем значение 'soft' */
  } else { /* если у блока <div class="theme"> атрибут 'data-theme' не равен ни значению 'light', ни значению 'dark'*/
    setTheme('light') /* тогда устанавливаем значение 'light' */
  }
})


Теперь рассмотрим пошагово и упрощенно

Создаем HTML

<div class="theme">
  <div class="theme__button">Switch theme</div>
</div>

Добавляем начальные стили

:root {
  --theme-color: #efefef;
  --primary-color: #000e29;
}
.theme {
  background: var(--theme-color);
  color: var(--primary-color);
}
.theme__button {
  background: var(--primary-color);
  color: var(--theme-color);
}

Сейчас при загрузке страницы у блока <div class="theme"> фон светлый и цвет текста темный, у кнопки <div class="theme__button">Switch theme</div> наоборот фон темный и цвет текста светлый

Далее в CSS добавляем две цветовые темы через селекторы атрибутов

[data-theme="light"] {
  --theme-color: #efefef;
  --primary-color: #000e29;
}
[data-theme="dark"] {
  --theme-color: #000e29;
  --primary-color: #efefef;
}

Теперь если блоку <div class="theme"> добавить атрибут data-theme со значением dark - <div class="theme" data-theme="dark">, то у <div class="theme"> будет фон темный и цвет текста светлый, а у кнопки наоборот фон светлый и цвет текста темный

Далее все что нужно сделать, это с помощью JavaScript менять значение атрибута data-theme. В данном примере при клике на кнопку.

Выше написан немного усложненный JavaScript код c тремя темами, вынесенной функцией установки темы, и с хранением значения темы в localStorage, чтобы при обновлении страницы тема не сбрасывалась к значению по-умолчанию, а оставалась той, которую выбрал пользователь

Ниже будет упрощенный код для понимания логики

const btn = document.querySelector('.theme__button') /* находим кнопку <div class="theme__button">Switch theme</div> по селектору .theme__button и записываем ее в переменную btn */
const theme = document.querySelector('.theme') /* находим блок <div class="theme">, в котором будет меняться тема, по селектору .theme и записываем ее в переменную theme */

theme.setAttribute('data-theme', 'light') /* устанавливаем значение 'light' по-умолчанию */

btn.addEventListener('click', () => { /* при клике на кнопку <div class="theme__button">Switch theme</div> */
  if (theme.getAttribute('data-theme') === 'light') { /* если у блока <div class="theme"> атрибут 'data-theme' строго равен значению 'light'  */
    theme.setAttribute('data-theme', 'dark') /* тогда устанавливаем значение 'dark' */
  } else { /* а иначе */
    theme.setAttribute('data-theme', 'light') /* устанавливаем значение 'light' */
  }
})

Теперь при загрузке страницы у блока <div class="theme"> появляется атрибут data-theme со значением light - <div class="theme" data-theme="light"> и применяется светлая тема

Далее, при клике на кнопку, проверяем, если у блока <div class="theme"> атрибут data-theme равен значению light то устанавливаем значение dark, а иначе устанавливаем снова значение light



Полезные ссылки

Документация по :root

Использование CSS-переменных

Про селекторы атрибутов

Документация getAttribute()

Документация setAttribute()

Подробнее о localStorage