Доступность (Accessibility, a11y) — фундаментальная характеристика современных интерфейсов. Она определяет, насколько эффективно пользователь может взаимодействовать с элементами интерфейса независимо от способа ввода, физических ограничений или используемых вспомогательных технологий.
Ключевые цели доступности:
Библиотека Radix UI проектировалась как accessibility-first система компонентов. Практически все компоненты библиотеки уже включают:
Одним из ключевых элементов доступности является label, обеспечивающий связь между элементом интерфейса и его описанием.
Любой интерактивный элемент формы должен быть понятно обозначен.
Если элемент не имеет корректной подписи, возникают проблемы:
| Проблема | Последствие |
|---|---|
| Нет текстовой подписи | Скринридер не может объяснить назначение элемента |
| Label не связан с элементом | Пользователь теряет контекст |
| Неверная семантика | Нарушается навигация по форме |
| Маленькая область клика | Снижается удобство взаимодействия |
Правильная разметка HTML решает эту проблему через элемент
<label>.
<label for="email">Email</label>
<input id="email" type="email" />
Связь осуществляется через:
for в labelid у элемента формыСкринридер будет объявлять элемент как:
Email, edit text
Radix UI предоставляет специальный компонент:
@radix-ui/react-label
Он является обёрткой над стандартным
<label>, но добавляет:
npm install @radix-ui/react-label
import * as Label from "@radix-ui/react-label";
function Form() {
return (
<div>
<Label.Root htmlFor="email">
Email
</Label.Root>
<input id="email" type="email" />
</div>
);
}
Label.Root:
<label>htmlForОдно из ключевых преимуществ <label> —
расширение области клика.
При клике на label:
Пример:
<Label.Root htmlFor="terms">
Accept terms
</Label.Root>
<input id="terms" type="checkbox" />
Клик по тексту Accept terms переключит checkbox.
Это особенно важно для:
HTML допускает вложение элемента формы внутрь label.
Radix UI полностью поддерживает эту модель.
<Label.Root>
Email
<input type="email" />
</Label.Root>
Преимущества:
idОдин из наиболее распространённых сценариев — работа с чекбоксами.
import * as Checkbox from "@radix-ui/react-checkbox";
import * as Label from "@radix-ui/react-label";
function Example() {
return (
<div>
<Checkbox.Root id="terms" />
<Label.Root htmlFor="terms">
Accept terms and conditions
</Label.Root>
</div>
);
}
Клик по тексту:
Label часто используется совместно с:
CheckboxRadioGroupSwitchSliderTextFieldПример со Switch:
import * as Switch from "@radix-ui/react-switch";
import * as Label from "@radix-ui/react-label";
function Notifications() {
return (
<div>
<Label.Root htmlFor="notifications">
Enable notifications
</Label.Root>
<Switch.Root id="notifications" />
</div>
);
}
Radix не навязывает стили. Компонент легко стилизуется любыми средствами.
.label {
font-weight: 600;
margin-bottom: 4px;
display: block;
}
Использование:
<Label.Root className="label" htmlFor="email">
Email
</Label.Root>
<Label.Root
htmlFor="email"
className="text-sm font-medium text-gray-700"
>
Email
</Label.Root>
Одной из проблем label является конфликт pointer-событий при вложенных элементах.
Radix UI решает эту проблему автоматически.
<label>
Click
<button>Info</button>
</label>
Клик по кнопке может непредсказуемо активировать input.
Radix корректно управляет событиями и предотвращает подобные ошибки.
Если элемент формы отключён (disabled), label должен
корректно отражать это состояние.
Пример:
<input id="username" disabled />
<Label.Root htmlFor="username">
Username
</Label.Root>
Часто добавляется визуальная индикация:
.label-disabled {
opacity: 0.5;
}
Иногда требуется не только label, но и дополнительное описание.
HTML предоставляет атрибут:
aria-describedby
Пример:
<Label.Root htmlFor="password">
Password
</Label.Root>
<input
id="password"
aria-describedby="password-desc"
/>
<p id="password-desc">
Must contain at least 8 characters
</p>
Скринридер озвучит:
Password. Must contain at least 8 characters.
Иногда label может находиться в другом месте DOM.
В таких случаях используется:
aria-labelledby
Пример:
<p id="title">User name</p>
<input aria-labelledby="title" />
Это полезно в сложных интерфейсах:
HTML допускает несколько label для одного элемента.
Пример:
<label for="email">Email</label>
<label for="email">(required)</label>
<input id="email" />
Скринридер объединяет текст.
Однако на практике это используется редко, так как ухудшает читаемость DOM.
При создании собственных компонентов важно сохранять связь label.
Пример компонента:
function TextField({ id, label }) {
return (
<div>
<Label.Root htmlFor={id}>
{label}
</Label.Root>
<input id={id} />
</div>
);
}
Использование:
<TextField id="email" label="Email" />
Неправильно:
<label>Email</label>
<input type="email" />
Правильно:
<label htmlFor="email">Email</label>
<input id="email" type="email" />
Плохая практика:
<input placeholder="Email" />
Проблемы:
Label должен существовать всегда.
Иногда дизайн не предусматривает видимый label.
В таком случае используется visually hidden подход.
.sr-only {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
}
Пример:
<Label.Root className="sr-only" htmlFor="search">
Search
</Label.Root>
<input id="search" placeholder="Search..." />
Label играет важную роль в управлении фокусом.
При клике:
label → input.focus()
Это улучшает:
Radix гарантирует корректную передачу фокуса для всех своих компонентов.
Правильный label обеспечивает корректное объявление элемента.
Пример объявления:
Email, edit text
Для checkbox:
Accept terms, checkbox, not checked
Без label скринридер может произнести:
Edit text
Пользователь теряет контекст.
Корректность label можно проверять инструментами:
Типичные предупреждения:
Radix UI помогает избежать этих ошибок на уровне компонентов.
Ключевые правила:
1. Каждый элемент формы должен иметь label
Исключения минимальны.
2. Label должен быть связан с элементом
Через:
htmlForaria-labelledby3. Label должен быть понятным
Плохой пример:
Input
Хороший пример:
Email address
4. Label должен оставаться видимым
Placeholder не заменяет label.
5. Label должен увеличивать область клика
Особенно для:
В архитектуре Radix компонент Label выполняет несколько функций:
В результате формируется интерфейс, который: