Перейти до вмісту
tatetUI

Контракт доступності

Мінімум, який зобов'язаний виконувати кожен компонент бібліотеки.

Це не побажання, а перелік того, що має бути в кожному компоненті. Більшість пунктів тут з'явилися тому, що в одному з вихідних проєктів вони були порушені.

Фокус завжди видимий

focus:outline-none без заміни — заборонено. Керування з клавіатури стає сліпим: користувач тисне Tab і не бачить, де він.

Базовий рівень задано глобально в main.css (:focus-visible { outline }). Компонент може замінити його на focus-visible:ring-*, але не прибрати.

Кільце малюється токеном --ring і має ring-offset — інакше на суцільній кнопці воно зливається з її ж заливкою.

Семантичний тег обирається за поведінкою

Клікабельний елемент — <button>, навігаційний — <a>. <div @click> недоступний із клавіатури й не має ролі.

Саме тому UiChip рендериться як <span> або <button> залежно від clickable, а UiButton перетворюється на NuxtLink, коли задано to.

Недоступна дія — це <button disabled>, а не посилання з pointer-events-none: таке посилання лишається в порядку обходу Tab і спрацьовує на Enter.

Стан не передається лише кольором

Дальтонік і скрінрідер не бачать тону. Якщо UiChip показує статус крапкою — потрібен label. Якщо перемикач увімкнений — потрібен aria-checked, а не лише зелений колір.

Ім'я мають усі інтерактивні елементи

Кнопка з самою іконкою зобов'язана мати label. Без нього скрінрідер зачитає «кнопка» — і все.

Мінімальна ціль дотику 44px

Нижче цього палець промахується. Тому на мобільних усі поля й кнопки зводяться до h-11/h-12, а md: повертає щільні десктопні розміри.

Там, де елемент мусить лишатися дрібним (перемикач у списку налаштувань), навколо нього додається невидима зона 44×44 через ::after — щільність списку не страждає.

Оверлеї

Панель має role="dialog", aria-modal="true" і назву.

Фокус утримується всередині й повертається на елемент, з якого відкрили. Решта прямих дітей <body> отримують inert і aria-hidden.

Escape закриває лише верхній оверлей. Деталі — на сторінці Оверлеї та фокус.

Типово фокус отримує сама панель, а не перший інпут: на мобільних автофокус в інпуті одразу піднімає клавіатуру і з'їдає пів екрана.

Анімації вимикаються

prefers-reduced-motion вимкнено глобально в main.css. Але цього мало там, де тривалість задана в JS: <Transition> з явним :duration тримав би елемент у DOM повні 300 мс навіть із вимкненими анімаціями. Для цього є useReducedMotion().

Опис один, а не два

aria-describedby вказує рівно на один активний опис. Якщо передати і підказку, і помилку, скрінрідер зачитає інструкцію, яку користувач щойно порушив, раніше за причину відмови.