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

Розмітка: тільки <button>

Найпоширеніша помилка – робити бургер із посилання чи звичайного div. На вигляд різниці немає, але на div не перейдеш клавішею Tab, а посилання без адреси програми для незрячих озвучать як щось незрозуміле. Тож беремо <button>:

<button class="burger" aria-expanded="false" aria-controls="top-menu">
    <span class="burger-lines"></span>
    <span class="visually-hidden">Меню</span>
</button>
<nav id="top-menu" class="top-menu">
    <ul>
        <li><a href="/">Головна</a></li>
        <li><a href="/blog">Блог</a></li>
        <li><a href="/contacts">Контакти</a></li>
    </ul>
</nav>

Тут кілька важливих дрібниць:

  • aria-expanded каже, відкрите меню чи ні, а aria-controls – яким саме блоком керує кнопка.
  • Порожній span – це наші рисочки, а текст «Меню» в другому span ми сховаємо з екрана, але залишимо для програм читання. Без нього кнопка буде для них просто «кнопкою» без назви.

Три рисочки на CSS

Усі три лінії малюються з одного елемента: середня – це сам span, а верхня й нижня – його псевдоелементи ::before та ::after. Розміри задаю через змінні, щоб кнопку було легко збільшити чи зменшити:

.burger {
    --size: 28px;
    --line: 3px;
    --gap: 8px;
    position: relative;
    width: 44px;
    height: 44px;
    padding: 0;
    border: 0;
    background: none;
    color: #222;
    cursor: pointer;
}

.burger-lines,
.burger-lines::before,
.burger-lines::after {
    position: absolute;
    left: 50%;
    width: var(--size);
    height: var(--line);
    margin-left: calc(var(--size) / -2);
    border-radius: var(--line);
    background: currentColor;
    transition: transform 0.3s ease, background-color 0.3s ease;
}

.burger-lines {
    top: calc(50% - var(--line) / 2);
}

.burger-lines::before,
.burger-lines::after {
    content: '';
    left: 0;
    margin-left: 0;
}

.burger-lines::before {
    transform: translateY(calc(-1 * var(--gap)));
}

.burger-lines::after {
    transform: translateY(var(--gap));
}

Сама кнопка зроблена 44 на 44 пікселі, хоча рисочки менші. Це не примха: по маленькій кнопці на телефоні важко влучити пальцем, тож клікабельна площа має бути щонайменше такою. Колір рисочок береться з color кнопки через currentColor, тому для темної теми достатньо змінити один рядок.

Перетворення на хрестик

Раніше для анімації я додавав кнопці окремий клас. Але в нас уже є атрибут aria-expanded, який однаково треба перемикати, тож стилізуймо прямо за ним – так стан кнопки для очей і для програм читання ніколи не розійдеться:

.burger[aria-expanded="true"] .burger-lines {
    background-color: transparent;
}

.burger[aria-expanded="true"] .burger-lines::before {
    transform: rotate(45deg);
}

.burger[aria-expanded="true"] .burger-lines::after {
    transform: rotate(-45deg);
}

Середня лінія стає прозорою, а верхня й нижня повертаються до центру і нахиляються в різні боки. Ось і хрестик.

Ще два штрихи, про які часто забувають:

.burger:focus-visible {
    outline: 2px solid #34687d;
    outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
    .burger-lines,
    .burger-lines::before,
    .burger-lines::after {
        transition: none;
    }
}

.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

:focus-visible показує рамку навколо кнопки, коли на неї переходять клавіатурою, але не після кліку мишкою. А prefers-reduced-motion вимикає анімацію тим, хто в налаштуваннях системи попросив менше руху на екрані.

Трохи JavaScript

Скрипту лишається тільки перемикати атрибут і клас на меню:

const burger = document.querySelector('.burger');
const menu = document.getElementById('top-menu');

burger.addEventListener('click', () => {
    const isOpen = burger.getAttribute('aria-expanded') !== 'true';
    burger.setAttribute('aria-expanded', isOpen);
    menu.classList.toggle('is-open', isOpen);
});

document.addEventListener('keydown', e => {
    if (e.key === 'Escape' && burger.getAttribute('aria-expanded') === 'true') {
        burger.click();
        burger.focus();
    }
});

Друга частина закриває меню клавішею Esc і повертає фокус на кнопку – дрібниця, але з клавіатури так користуватися меню значно приємніше. Як працює classList.toggle() з другим аргументом, я докладно розбирав у статті як змінити клас елемента в JavaScript, там же є приклад плавного розгортання самого меню.

Бургер лише на телефоні

На широкому екрані меню зазвичай видно повністю, і кнопка там не потрібна. Ховаємо її медіазапитом, а меню на телефоні показуємо лише з класом is-open:

.top-menu {
    display: none;
}

.top-menu.is-open {
    display: block;
}

@media (min-width: 768px) {
    .burger {
        display: none;
    }

    .top-menu {
        display: block;
    }
}

Що далі

Якщо тема в тебе на WordPress, кнопку з меню зручно вставити у файл header.php. Замість рисочок на CSS можна взяти готову іконку з Font Awesome, але тоді анімацію в хрестик доведеться робити заміною іконки. Поруч із бургером часто ставлять перемикач теми – ось як зробити темний режим із localStorage. А якщо хочеться меню просто з картинки, де кожна її частина веде в свій розділ, глянь, як зробити адаптивну карту зображень. На статичному сайті саме меню можна збирати автоматично – читай, як зробити меню в Eleventy.

Більше про це – за позначками CSS та Front-end розробка.

А який бургер у тебе на сайті – з рисочок на CSS, іконкою чи взагалі без нього? Пиши в коментарях 🙂