display: none или visibility: hidden — в чём разница и что слышит скринридер

Представь две одинаковые книжные полки. С левой забрали книгу — и соседние книги съехались, щели нет. С правой тоже забрали — но на её месте осталась пустота ровно по размеру книги.
Это и есть разница между display: none и visibility: hidden. Элемент не видно в обоих случаях. Всё решает, что происходит с соседями. А есть ещё третий способ, про который вспоминают реже всего: спрятать от глаз, но оставить скринридеру.
Что делает каждое свойство
display: none выключает отображение элемента и всех его потомков. Страница рисуется так, будто элемента нет вовсе. Место освобождается, соседи сдвигаются.
visibility: hidden не рисует элемент, но он продолжает занимать место. Раскладка не меняется ни на пиксель.
Сравнение по критериям
- Место на странице.
display: none— освобождает, соседи съезжаются.visibility: hidden— держит, остаётся пустота. - Клики. Ни то ни другое не кликается: невидимый элемент не ловит мышь.
- Клавиша Tab. Оба убирают элемент из очереди табуляции. MDN отдельно пишет это про
visibility: hidden: фокус на такой элемент не встанет. - Скринридер. Оба убирают элемент из дерева доступности. Скринридер его не прочитает.
- Потомки. Тут неожиданность. У
visibility: hiddenребёнку можно написатьvisibility: visible— и он появится внутри невидимого родителя.display: noneу потомков не перебить ничем. - Анимация.
visibilityумеет переключаться в конце анимации. Отсюда классика плавного исчезновения:opacityиvisibilityв одномtransition. Элемент гаснет, а потом перестаёт ловить клики.
Ловушка: opacity: 0
Третий популярный способ — opacity: 0. И он коварнее всех.
Элемент прозрачный, но остаётся на месте. Его можно кликнуть. На него встаёт Tab. Скринридер его читает. Человек с клавиатурой нажимает Tab и попадает на невидимую кнопку. Фокус пропал, непонятно, где он.
Поэтому opacity: 0 сам по себе — не способ спрятать. Это только полупрозрачность, выкрученная в ноль. Если прячешь через него, добавь visibility: hidden в конце анимации.
Спрятать только от глаз — или только от скринридера
Иногда нужно спрятать элемент не от всех.
Видно только скринридеру. Поле поиска с лупой вместо подписи. Зрячий понимает, что это поиск. А скринридеру нужна подпись. Для этого есть класс, который WebAIM рекомендует:
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip-path: inset(50%);
}
Элемент сжат до пикселя и обрезан, но скринридер его читает. WebAIM предупреждает: так не прячут ссылки и кнопки. Иначе на них встанет Tab, а зрячий человек с клавиатурой ничего не увидит.
Видно только глазам. Декоративная иконка рядом с текстом. Её прячут от скринридера атрибутом aria-hidden="true" — подробнее в статье что такое ARIA. Но никогда не ставь его на то, что может получить фокус.
Видно, но нельзя трогать. Атрибут inert делает блок неактивным: не кликается, не фокусируется, скринридер его пропускает. Удобно для страницы под открытым модальным окном.
Атрибут hidden и почему он иногда не прячет
В HTML есть атрибут hidden. Он работает как display: none. Но вот частая жалоба: «поставил hidden, а элемент видно».
Причина — твой же CSS. Браузер прячет hidden своим стилем, а у него самый слабый приоритет. Любое правило вроде .card { display: flex; } его перебивает. Лечится одной строкой:
[hidden] { display: none !important; }
Если стили вообще не применяются так, как ждёшь, разбираемся по шагам в статье почему не работает CSS.
А вариант hidden="until-found" прячет блок, но поиск по странице Ctrl+F находит текст внутри и раскрывает его. Он есть в Chrome с версии 102, в Firefox с 148 и в Safari с 26.2.
Кому что подойдёт — прямой ответ
- Убрать совсем: закрытое меню, неактивная вкладка, окно до открытия →
display: noneили атрибутhidden. - Сохранить место, чтобы вёрстка не прыгала: значок загрузки внутри кнопки, бейдж →
visibility: hidden. - Плавно показать и скрыть →
opacityплюсvisibilityвtransition. - Спрятать от глаз, оставить скринридеру → класс
visually-hidden. - Показать глазам, спрятать от скринридера →
aria-hidden="true", только не на фокусируемом. - Отключить целый блок под модалкой →
inert.
Страница вдруг стала пустой, хотя данные пришли? Иногда это как раз забытый display: none на обёртке — разбор в статье почему страница белая.
Короткие уроки-истории, симулятор агента и ежедневная практика — в нашем мобильном приложении. Бесплатно.
Как спрятать элемент из JavaScript?
Проще всего через атрибут: element.hidden = true прячет, false показывает. Если в CSS есть display для этого элемента, не забудь правило [hidden] из статьи выше.
Что выбрать для модального окна?
Закрытое окно — display: none или hidden, чтобы его не было ни на экране, ни в очереди Tab. А лучше встроенный элемент dialog: он сам прячет окно и делает страницу под ним неактивной, когда открыт через showModal().





