label и fieldset

<label> связывает текстовую подпись с элементом формы. <fieldset> и <legend> группируют связанные поля и дают им общий заголовок.

Зачем нужно

<label> -- критичный элемент доступности. Без него screen reader не знает, что означает поле ввода. Клик по label фокусирует связанный input -- это увеличивает область клика (особенно важно для checkbox/radio на мобильных).

<fieldset> группирует связанные поля (например, группу radio), а <legend> даёт группе название, которое screen reader объявляет перед каждым полем.

Где используется

  • Каждый элемент формы должен иметь label
  • Группы radio/checkbox обязательно в fieldset
  • Логические секции форм (личные данные, адрес, оплата)

Предпосылки

<label> -- подпись элемента

Явная связь (explicit) -- через for

<label for="email">Email:</label>
<input type="email" id="email" name="email">

Атрибут for у <label> должен совпадать с id элемента формы.

Неявная связь (implicit) -- обёртка

<label>
  Email:
  <input type="email" name="email">
</label>

Input вложен в label -- связь автоматическая, for и id не нужны.

Что лучше?

Способ Плюсы Минусы
Explicit (for/id) Работает везде, гибкая вёрстка Нужны уникальные id
Implicit (обёртка) Не нужны id Некоторые AT хуже поддерживают, менее гибко

Рекомендация: используй explicit-связь (for/id) -- она надёжнее.

Label и разные элементы

<!-- input -->
<label for="name">Имя:</label>
<input type="text" id="name" name="name">

<!-- textarea -->
<label for="msg">Сообщение:</label>
<textarea id="msg" name="message"></textarea>

<!-- select -->
<label for="city">Город:</label>
<select id="city" name="city">
  <option value="msk">Москва</option>
  <option value="spb">Санкт-Петербург</option>
</select>

<!-- checkbox (label оборачивает для увеличения области клика) -->
<label>
  <input type="checkbox" name="agree"> Согласен с условиями
</label>

<!-- radio -->
<label>
  <input type="radio" name="size" value="s"> Маленький
</label>
<label>
  <input type="radio" name="size" value="m"> Средний
</label>

Почему placeholder -- не замена label

<!-- ПЛОХО: только placeholder -->
<input type="email" placeholder="Email">
<!-- Проблемы:
     - Исчезает при вводе (пользователь забывает что вводить)
     - Серый цвет -- плохой контраст
     - Screen reader может не прочитать
     - Нет кликабельной области
-->

<!-- ХОРОШО: label + placeholder -->
<label for="email">Email:</label>
<input type="email" id="email" name="email" placeholder="user@example.com">

<fieldset> и <legend>

Группировка radio кнопок

<fieldset>
  <legend>Способ доставки:</legend>

  <label>
    <input type="radio" name="delivery" value="courier" checked>
    Курьер
  </label>

  <label>
    <input type="radio" name="delivery" value="pickup">
    Самовывоз
  </label>

  <label>
    <input type="radio" name="delivery" value="post">
    Почта
  </label>
</fieldset>

Screen reader прочитает: "Способ доставки, группа. Курьер, радиокнопка, выбрана. Самовывоз, радиокнопка..."

Группировка checkbox

<fieldset>
  <legend>Тип кухни:</legend>

  <label>
    <input type="checkbox" name="cuisine" value="italian"> Итальянская
  </label>
  <label>
    <input type="checkbox" name="cuisine" value="japanese"> Японская
  </label>
  <label>
    <input type="checkbox" name="cuisine" value="georgian"> Грузинская
  </label>
</fieldset>

Секции формы

<form action="/api/order" method="POST">
  <fieldset>
    <legend>Контактные данные</legend>
    <div class="field">
      <label for="name">Имя:</label>
      <input type="text" id="name" name="name" required autocomplete="name">
    </div>
    <div class="field">
      <label for="phone">Телефон:</label>
      <input type="tel" id="phone" name="phone" required autocomplete="tel">
    </div>
  </fieldset>

  <fieldset>
    <legend>Адрес доставки</legend>
    <div class="field">
      <label for="street">Улица:</label>
      <input type="text" id="street" name="street" autocomplete="street-address">
    </div>
    <div class="field">
      <label for="apt">Квартира:</label>
      <input type="text" id="apt" name="apartment">
    </div>
  </fieldset>

  <fieldset>
    <legend>Способ оплаты</legend>
    <label>
      <input type="radio" name="payment" value="card" checked> Картой
    </label>
    <label>
      <input type="radio" name="payment" value="cash"> Наличными
    </label>
  </fieldset>

  <button type="submit">Оформить заказ</button>
</form>

Вложенные fieldset

<fieldset>
  <legend>Настройки уведомлений</legend>

  <fieldset>
    <legend>Email-уведомления</legend>
    <label><input type="checkbox" name="email_news"> Новости</label>
    <label><input type="checkbox" name="email_promo"> Акции</label>
  </fieldset>

  <fieldset>
    <legend>Push-уведомления</legend>
    <label><input type="checkbox" name="push_orders"> Заказы</label>
    <label><input type="checkbox" name="push_chat"> Сообщения</label>
  </fieldset>
</fieldset>

disabled на fieldset

disabled на <fieldset> блокирует все вложенные элементы формы:

<fieldset disabled>
  <legend>Адрес (заблокировано)</legend>
  <label for="city">Город:</label>
  <input type="text" id="city" name="city">
  <!-- Все input внутри будут disabled -->
</fieldset>

Стилизация

<style>
  fieldset {
    border: 1px solid #e0e0e0;
    border-radius: 8px;
    padding: 1.5rem;
    margin-bottom: 1.5rem;
  }

  legend {
    font-weight: 600;
    font-size: 1.1rem;
    padding: 0 0.5rem;
  }

  /* Убрать рамку fieldset (для визуальной группировки без рамки) */
  .no-border {
    border: none;
    padding: 0;
  }

  .no-border legend {
    font-size: 1.25rem;
    margin-bottom: 0.75rem;
  }

  label {
    display: block;
    margin-bottom: 0.25rem;
    font-weight: 500;
  }

  .field {
    margin-bottom: 1rem;
  }
</style>

Частые ошибки

Ошибка Почему плохо Как правильно
Input без label Screen reader не знает, что вводить Каждый input должен иметь label
placeholder вместо label Исчезает при вводе, плохой контраст label + placeholder
Radio/checkbox без <fieldset> Screen reader не знает контекст группы Группируй в fieldset + legend
for не совпадает с id Связь не работает Проверяй совпадение for и id
Несколько input на один label Невалидно, непредсказуемо Один label на один input
<legend> вне <fieldset> Невалидно <legend> только внутри <fieldset>

Практика

  1. Создай форму с explicit label (for/id) -- кликни по label и проверь фокус
  2. Группируй radio-кнопки в fieldset с legend
  3. Создай форму с двумя fieldset: "Личные данные" и "Настройки"
  4. Добавь disabled на fieldset и убедись, что все поля внутри заблокированы
  5. Проверь форму через screen reader (NVDA) -- legend должен читаться перед каждым radio

Связанные темы

Ресурсы