Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Як приховати елементи візуально, але зберегти їх доступними для екранних читалок». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Візуально прихований, але доступний** - CSS-підхід, що видаляє елемент із видимого потоку, але зберігає його в дереві доступності. ```css .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; } ``` **Ключове:** `display: none` і `visibility: hidden` ховають і від екранних читалок теж. Використовуйте `.sr-only` для міток, підказок і skip-посилань.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Візуально прихований елемент** - елемент, видалений із видимого потоку, але присутній у DOM і дереві доступності, тому екранні читалки (screen readers) на кшталт NVDA чи VoiceOver його озвучують. ## Теорія ### TL;DR - Уявіть шепіт у галасливій кімнаті: зрячі нічого не бачать, екранні читалки чують кожне слово. - `.sr-only` стискає елемент до 1px і виносить його за видимий потік; `aria-hidden="true"` навпаки — блокує екранні читалки, але залишає елемент видимим. - `.sr-only` — для підказок, міток і skip-посилань. `aria-hidden` — для іконок та декоративних елементів. - `display: none` ховає від усіх, включно з читалками. Це найпоширеніша помилка. ### Швидкий приклад ```html <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` в надії, що читалка все одно прочитає елемент** ```html <!-- Неправильно: елемент видалено з дерева доступності (W3C UAAG) --> <span style="display: none;">Текст підказки</span> ``` Рішення: використовуйте `.sr-only`. **Припущення, що `visibility: hidden` доступний для читалок** ```html <!-- Неправильно: NVDA і VoiceOver пропускають, як і display: none --> <span style="visibility: hidden;">Текст підказки</span> ``` Рішення: `.sr-only`. **`aria-hidden="true"` на інтерактивному елементі** ```html <!-- Неправильно: користувачі клавіатури не можуть активувати кнопку --> <button aria-hidden="true">Надіслати</button> ``` Chrome 89+ блокує фокус на елементах з `aria-hidden`. Якщо кнопка інтерактивна — цей атрибут на ній неприпустимий. **`aria-hidden` на обгортці, яка містить фокусовані елементи** ```jsx // Неправильно: кнопка недосяжна для читалки, але залишається в 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-посилання з відображенням при фокусі ```html <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-форма з прихованою підказкою для читалки ```jsx // Форма входу: видима мітка, правила пароля приховані від зрячих 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 або більше символів з цифрою та спеціальним знаком». Зрячим форма виглядає чисто.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.