Переключатель тёмной темы

Будучи большим поклонником «тёмных тем», или, как их ещё называют, «тёмного режима», я реализовал его почти во всех приложениях и сайтах, которые создавал для себя — начиная с этого блога и заканчивая моим последним проектом, It Changed.

Меня не устраивали решения, которые я находил в интернете, потому что они не сочетали в себе переключатель и медиазапросы. Почему это важно? Прежде всего, у пользователей могут быть заранее заданные предпочтения. Например, в macOS, если выбрать System Settings >> Appearance >> Dark, при посещении сайта браузер передаст (prefers-color-scheme: dark). Однако бывают и ситуации, когда пользователь хочет переключить тему на светлую именно для конкретного сайта.

Чтобы обеспечить лучший UX, это обязательно должно быть сочетанием решений — и, как вы увидите, реализовать это не так уж сложно.

Итак, приступим.

Реализация

Для начала нам нужен какой-то переключатель. Мне очень нравится использовать чекбоксы для целей отладки. Вот простой пример:

<label type="button">
    <input type="checkbox" name="theme-switch" class="theme-switch"/>
    <span>switch</span>
</label>

Теперь перейдём к части с JavaScript:

const themeSwitches = document.querySelectorAll('.theme-switch');
if (themeSwitches.length > 0) {
    themeSwitches.forEach((themeSwitch, i) => {
        if (localStorage.getItem('dark-mode') === 'true') {
            themeSwitch.checked = true;
        } else {
            themeSwitch.checked = false;
        }

        themeSwitch.addEventListener('change', () => {
            const { checked } = themeSwitch;
            themeSwitches.forEach((el, n) => {
                if (n !== i) {
                    el.checked = checked;
                }
            });
            if (themeSwitch.checked) {
                document.documentElement.classList.add('dark');
                localStorage.setItem('dark-mode', true);
            } else {
                document.documentElement.classList.remove('dark');
                localStorage.setItem('dark-mode', false);
            }
        });
    });
}

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

Однако есть часть кода, которая действительно должна выполняться как можно раньше. Вот этот фрагмент:

<script>
    if (localStorage.getItem('dark-mode') === 'true' || (!('dark-mode' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
        document.querySelector('html').classList.add('dark');
    } else {
        document.querySelector('html').classList.remove('dark');
    }
</script>

Что делает этот код:

  1. Проверяет, установлен ли тёмный режим в localStorage;
  2. Если в localStorage нет настройки тёмного режима, проверяет, предпочитает ли пользователь тёмный режим.

Что это гарантирует:

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

Дизайн

Поскольку мы реализовали переключатель в виде чекбокса, это открывает широкий простор для дизайна. Он может работать как тумблер, оставаться чекбоксом или превратиться в кнопку — варианты практически безграничны. Из-за этой гибкости я не буду предлагать здесь единое дизайнерское решение. Выберите то, что лучше всего подходит именно вашему случаю.

Ссылки

Вот несколько полезных ссылок:

  1. Я опубликовал этот код в виде Composer-пакета на Packagist.
  2. Если хотите внести свой вклад, загляните в проект на GitHub.

На этом всё, друзья :)