Як приховати елементи візуально, але зберегти їх доступними для екранних читалок
Візуально прихований елемент - елемент, видалений із видимого потоку, але присутній у DOM і дереві доступності, тому екранні читалки (screen readers) на кшталт NVDA чи VoiceOver його озвучують.
Теорія
TL;DR
- Уявіть шепіт у галасливій кімнаті: зрячі нічого не бачать, екранні читалки чують кожне слово.
.sr-onlyстискає елемент до 1px і виносить його за видимий потік;aria-hidden="true"навпаки — блокує екранні читалки, але залишає елемент видимим..sr-only— для підказок, міток і skip-посилань.aria-hidden— для іконок та декоративних елементів.display: noneховає від усіх, включно з читалками. Це найпоширеніша помилка.
Швидкий приклад
<style>
.sr-only {
position: absolute;
width: 1px; height: 1px;
padding: 0; margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
</style>
<button>Надіслати</button>
<!-- Екранна читалка оголошує це після підпису кнопки -->
<span class="sr-only">Натисніть Enter, щоб відправити форму</span>Зрячі бачать тільки «Надіслати». NVDA і VoiceOver зачитують підказку одразу після кнопки.
Ключова різниця: .sr-only проти aria-hidden
.sr-only через position: absolute виводить елемент із нормального потоку, а clip: rect(0,0,0,0) обрізає намальовану область до 1px. DOM і дерево доступності залишаються незмінними — читалки проходять по ньому звично. aria-hidden="true" працює навпаки: позначає вузол як невидимий для допоміжних технологій, але елемент залишається на екрані. Bootstrap називає це .visually-hidden, Tailwind — sr-only. Реалізація однакова.
Коли що використовувати
- Підказка або правило валідації тільки для екранних читалок:
.sr-onlyна<span>з прив'язкою черезaria-describedby - Кнопка-іконка, де іконка потребує текстового підпису:
aria-hidden="true"на іконку,aria-labelна кнопку - Skip-посилання (перехід до основного вмісту):
.sr-onlyплюс:focusдля відображення при навігації клавіатурою - Декоративний градієнт або фонове зображення:
aria-hidden="true"або видалити з DOM - Оголошення стану у динамічному тоглі:
.sr-only-текст у поєднанні зaria-expanded
Як це працює у браузері
position: absolute виводить елемент із нормального потоку. clip: rect(0,0,0,0) застарілий, але досі підтримується у Chrome, Firefox і Safari. Сучасна альтернатива — clip-path: inset(100%), яка краще працює з трансформованими елементами. Екранні читалки звертаються до дерева доступності через DOM-методи на кшталт computedRole і computedLabel. CSS-розмітка на це дерево не впливає. aria-hidden впливає, бо це прямий сигнал для API доступності.
JavaScript тут не потрібен. Приховування відбувається під час парсингу, лише через CSS.
Типові помилки
Використання display: none в надії, що читалка все одно прочитає елемент
<!-- Неправильно: елемент видалено з дерева доступності (W3C UAAG) -->
<span style="display: none;">Текст підказки</span>Рішення: використовуйте .sr-only.
Припущення, що visibility: hidden доступний для читалок
<!-- Неправильно: NVDA і VoiceOver пропускають, як і display: none -->
<span style="visibility: hidden;">Текст підказки</span>Рішення: .sr-only.
aria-hidden="true" на інтерактивному елементі
<!-- Неправильно: користувачі клавіатури не можуть активувати кнопку -->
<button aria-hidden="true">Надіслати</button>Chrome 89+ блокує фокус на елементах з aria-hidden. Якщо кнопка інтерактивна — цей атрибут на ній неприпустимий.
aria-hidden на обгортці, яка містить фокусовані елементи
// Неправильно: кнопка недосяжна для читалки, але залишається в tab-порядку
<div aria-hidden="true">
<button>Закрити</button>
</div>Використовуйте натомість атрибут inert (Chrome 102+). Він одночасно блокує фокус і дерево доступності без пастки піддерева.
Помилка з display: none трапляється в аудитах доступності набагато частіше, ніж здається. Зазвичай це означає, що CSS-клас скопіювали без реального тестування з читалкою.
Де зустрічається у реальних проектах
- Bootstrap 5:
.visually-hiddenдля підписів іконок у навбарах - Tailwind CSS 3: утиліта
sr-onlyв описах форм і радіо-групах - WordPress Gutenberg: skip-посилання через
.screen-reader-text - React Aria (Adobe): компонент
HiddenSelectвикористовує off-screen spans для нативних select-опцій - Material-UI v5: вбудовані
sx-стилі зposition: absolute; width: 1pxу компонентах Drawer
Питання для поглибленого розуміння
Q: Яка різниця між clip: rect(0,0,0,0) і clip-path: inset(100%)?
A: clip застарілий, Firefox 112 прибрав його підтримку. clip-path: inset(100%) краще обробляє трансформовані елементи. Для широкої сумісності тримайте обидва правила в одному класі.
Q: Чи працює .sr-only всередині flex або grid контейнера?
A: Так. position: absolute виводить елемент із flex або grid потоку. Перевіряйте порядок читання через NVDA у Windows Chrome.
Q: Коли краще aria-label, а коли .sr-only span?
A: aria-label підходить для короткого тексту на одному інтерактивному елементі. .sr-only span — для багаторядкових описів або коли текст потрібно прив'язати через aria-describedby.
Q: Як правильно використовувати aria-hidden у фоні модального вікна?
A: Фон отримує aria-hidden="true". Панель самого модального вікна — ні. Якщо aria-hidden потрапляє на обгортку з кнопкою закриття, краще використовуйте inert. Він блокує і фокус, і дерево доступності одночасно.
Q: Чи є різниця між VoiceOver на macOS і NVDA на Windows при зчитуванні .sr-only?
A: Обидва беруть текст з DOM через власні API. VoiceOver пріоритизує зв'язки через aria-describedby. NVDA повертається до innerText, коли ARIA-зв'язків немає. Тестуйте на обох платформах, якщо порядок оголошень важливий.
Приклади
Skip-посилання з відображенням при фокусі
<style>
.sr-only {
position: absolute;
width: 1px; height: 1px;
padding: 0; margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
/* Відображається при навігації клавіатурою (Tab) */
.sr-only:focus {
position: static;
width: auto; height: auto;
background: white;
color: black;
padding: 0.5rem;
clip: auto;
}
</style>
<a href="#main" class="sr-only">Перейти до основного вмісту</a>
<main id="main">Вміст сторінки</main>При натисканні Tab посилання з'являється вгорі сторінки для користувачів клавіатури. На сенсорних пристроях стиль :focus може не спрацювати — тестуйте на різних платформах перед випуском.
React-форма з прихованою підказкою для читалки
// Форма входу: видима мітка, правила пароля приховані від зрячих
function LoginForm() {
return (
<form>
<label htmlFor="password">Пароль</label>
<input
id="password"
type="password"
aria-describedby="password-rules"
/>
{/* Зрячим: невидимий. Читалка: зачитує після фокусу на полі */}
<span id="password-rules" className="sr-only">
Використовуйте 8 або більше символів з цифрою та спеціальним знаком
</span>
<button type="submit">Увійти</button>
</form>
);
}NVDA оголошує: «Пароль, редагування, Використовуйте 8 або більше символів з цифрою та спеціальним знаком». Зрячим форма виглядає чисто.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.