Колись мені знадобилася на сайті на WordPress кнопка для перемикання між світлим і темним режимами. Я зробив її за вечір: два окремі файли стилів, кука з вибором і перезавантаження сторінки після кожного кліку. Працювало, але блимало і смикалося. Відтоді я переробляв цю кнопку не раз, тож розповім, як зробити темний режим у своїй темі WordPress сьогодні: один файл стилів, автоматичне перемикання разом із системною темою, кнопка без перезавантаження і без білого спалаху при переході між сторінками.

Як це працюватиме

  • Кольори задані CSS-змінними, і сайт сам підлаштовується під світлу чи темну тему системи.
  • Кнопка дає вибрати тему вручну: скрипт ставить атрибут data-theme на тег <html> і записує вибір у куку.
  • На наступній сторінці PHP читає цю куку і одразу віддає <html data-theme="dark">, тож сторінка з'являється в потрібній темі без жодного блимання.

Загальні принципи темної теми, без прив'язки до WordPress, я детально розібрав у статті темна тема на сайті: CSS, localStorage і системна тема. Тут зосередимося на тому, як вбудувати це в тему WordPress.

Кольори: один файл стилів

Замість двох файлів light.css і dark.css, як у мене колись, достатньо кількох рядків в основному style.css:

:root {
    color-scheme: light dark;
    --text: light-dark(#282c34, #d4d4d4);
    --bg: light-dark(#ffffff, #1e2127);
    --accent: light-dark(#34687d, #7fb8cf);
}

:root[data-theme="light"] { color-scheme: light; }
:root[data-theme="dark"]  { color-scheme: dark; }

body {
    color: var(--text);
    background: var(--bg);
}

a {
    color: var(--accent);
}

light-dark() повертає перший колір для світлої теми і другий для темної, а яка тема зараз, вирішує color-scheme: без атрибута – як у системі, з атрибутом – як вибрала людина. Далі всюди в стилях пишеш var(--text) замість конкретного кольору, і темна тема з'являється сама.

PHP: тема з куки одразу в розмітці

Найцікавіша частина, заради якої тут варто брати куки, а не localStorage. Браузер надсилає куки на сервер з кожним запитом, тож тема знає вибір відвідувача ще до того, як віддасть сторінку. Додаємо у functions.php:

function mytheme_color_theme() {
    $theme = $_COOKIE['theme'] ?? '';

    return in_array( $theme, array( 'light', 'dark' ), true ) ? $theme : '';
}

function mytheme_color_theme_attribute( $output ) {
    $theme = mytheme_color_theme();

    if ( $theme ) {
        $output .= ' data-theme="' . esc_attr( $theme ) . '"';
    }

    return $output;
}

add_filter( 'language_attributes', 'mytheme_color_theme_attribute' );

Що тут відбувається:

  • mytheme_color_theme() читає куку і повертає її значення, лише якщо це light або dark. Записати в куку відвідувач може що завгодно, тож довіряти їй сліпо не можна.
  • Фільтр language_attributes дописує атрибут до тегу <html>. Він спрацьовує, бо в header.php тег виглядає як <html <?php language_attributes(); ?>>. Сам header.php правити не треба.

Кнопка

У header.php, там, де має бути кнопка:

<button class="theme-toggle" type="button"
        aria-label="<?php esc_attr_e( 'Dark theme', 'mytheme' ); ?>"
        aria-pressed="<?php echo 'dark' === mytheme_color_theme() ? 'true' : 'false'; ?>">
    <span class="theme-toggle-knob" aria-hidden="true"></span>
</button>

Колись у мене це було посилання з onclick. Але дія на сторінці – це кнопка <button>: на неї можна перейти клавіатурою, і програми для незрячих розуміють, що це. Атрибут aria-pressed каже їм, увімкнена темна тема чи ні, а aria-label дає кнопці назву, бо тексту в ній немає.

Скрипт: перемикання без перезавантаження

Створюємо файл assets/js/theme-toggle.js:

const toggle = document.querySelector('.theme-toggle');
const root = document.documentElement;
const systemDark = matchMedia('(prefers-color-scheme: dark)');

function currentTheme() {
    return root.dataset.theme || (systemDark.matches ? 'dark' : 'light');
}

function updateButton() {
    toggle.setAttribute('aria-pressed', currentTheme() === 'dark');
}

if (toggle) {
    toggle.addEventListener('click', () => {
        const next = currentTheme() === 'dark' ? 'light' : 'dark';
        root.dataset.theme = next;
        document.cookie = 'theme=' + next + '; max-age=31536000; path=/; SameSite=Lax; Secure';
        updateButton();
    });

    systemDark.addEventListener('change', updateButton);
    updateButton();
}

Клік ставить атрибут data-theme – і кольори на сторінці змінюються миттєво, без перезавантаження. Заодно вибір записується в куку на рік, щоб наступну сторінку PHP одразу віддав у тій самій темі. Якщо людина нічого не вибирала, а темна тема в неї системна, updateButton() при завантаженні сам виправить стан кнопки. Детальніше про параметри куки (path, SameSite, Secure) читай у статті cookies у JavaScript: як записати і прочитати.

Підключаємо скрипт у functions.php:

function mytheme_theme_toggle_script() {
    wp_enqueue_script(
        'mytheme-theme-toggle',
        get_template_directory_uri() . '/assets/js/theme-toggle.js',
        array(),
        null,
        true
    );
}

add_action( 'wp_enqueue_scripts', 'mytheme_theme_toggle_script' );

Оформлення кнопки

Колись для сонця й місяця я брав Font Awesome і два окремі набори стилів для кнопки. Але простий перемикач-повзунок малюється взагалі без іконок:

.theme-toggle {
    position: relative;
    width: 3.5em;
    height: 2em;
    padding: 0;
    border: 2px solid currentColor;
    border-radius: 1em;
    background: none;
    color: inherit;
    cursor: pointer;
}

.theme-toggle-knob {
    position: absolute;
    top: 0.15em;
    left: 0.15em;
    width: 1.45em;
    height: 1.45em;
    border-radius: 50%;
    background: currentColor;
    transition: transform 0.3s;
}

.theme-toggle[aria-pressed="true"] .theme-toggle-knob {
    transform: translateX(1.45em);
}

Зверни увагу на останнє правило: вигляд кнопки залежить від того самого aria-pressed, який читають програми для незрячих. Тож вигляд і стан кнопки ніколи не розійдуться. А currentColor бере колір тексту, тож у темній темі повзунок сам стане світлим.

Увага: плаґіни кешування

У цього способу є одна пастка. Плаґіни кешування зберігають готовий HTML сторінки і віддають його всім однаковим. Якщо першим сторінку відкрив хтось із темною темою, у кеш потрапить <html data-theme="dark"> – і її побачать усі.

Є два виходи. Деякі плаґіни кешування вміють зберігати окремі копії сторінки залежно від куки – пошукай у налаштуваннях щось на кшталт виключень або варіацій за cookie. А якщо такого немає, прибери PHP-частину і відновлюй тему маленьким скриптом у <head> з localStorage – як це зробити, я показував у статті про темну тему на сайті.

А якщо стилі зовсім різні?

Буває, що темна версія відрізняється не лише кольорами, а цілим набором стилів. Тоді можна, як колись я, підключати окремий файл через wp_enqueue_style(), коли mytheme_color_theme() повертає dark. Але без перезавантаження таке перемикання вже не зробиш, а системну тему доведеться обробляти окремо, тому я завжди спершу пробую обійтися змінними.

Що далі

Поруч із перемикачем теми на телефоні зазвичай живе меню – ось як зробити кнопку-бургер на CSS. А якщо ти робиш тему з нуля, почни зі статті як створити тему WordPress з нуля.

Більше про це – за позначками Створення тем WordPress та CSS.

А на твоєму сайті темна тема вмикається сама чи тільки кнопкою? Пиши в коментарях 🙂