Форма обратной связи на HTML и CSS: разметка, валидация и стили

Форма обратной связи на HTML и CSS: разметка, валидация и стили Полезное

Форма обратной связи — это блок страницы на теге <form>, в котором посетитель заполняет поля (имя, email, сообщение) и нажимает кнопку, а браузер собирает введенное в пары «имя поля = значение» и отправляет их по адресу из атрибута action. HTML описывает поля и правила заполнения, CSS — внешний вид. Принять, проверить, сохранить или переслать данные на почту может только сервер: HTML и CSS этого не делают.

Ниже — готовая форма отзыва, разбор элементов, валидация, доступность, стили и граница фронтенда.

Мини-словарь

Термин Что это Пример
Поле элемент ввода внутри формы <input>, <textarea>, <select>
name ключ, под которым значение уйдет на сервер name="email"
label видимая подпись, связанная с полем <label for="email">
action URL обработчика https://example.com/feedback
method способ отправки get или post

Главное различие: id нужен, чтобы связать подпись с полем на странице, а name — чтобы значение вообще попало в запрос. Поле без name браузер не отправляет.

Полный пример формы отзыва

Сначала целиком рабочий файл. Сохраните его как index.html рядом с style.css (он ниже) и откройте в браузере.

<!DOCTYPE html>
<html lang="ru">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Отзыв о курсе</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <form class="feedback" action="https://example.com/feedback" method="post">
    <h1>Оставьте отзыв</h1>

    <label for="name">Имя</label>
    <input id="name" name="name" type="text" autocomplete="name"
           required minlength="2" maxlength="60">

    <label for="email">Email</label>
    <input id="email" name="email" type="email" autocomplete="email"
           required aria-describedby="email-hint" placeholder="ivan@example.com">
    <p id="email-hint" class="hint">На этот адрес придет ответ</p>

    <fieldset>
      <legend>Оценка</legend>
      <label><input type="radio" name="rating" value="5" required> 5</label>
      <label><input type="radio" name="rating" value="4"> 4</label>
      <label><input type="radio" name="rating" value="3"> 3</label>
      <label><input type="radio" name="rating" value="2"> 2</label>
      <label><input type="radio" name="rating" value="1"> 1</label>
    </fieldset>

    <label for="message">Сообщение</label>
    <textarea id="message" name="message" rows="5"
              required minlength="10" maxlength="2000"></textarea>

    <button type="submit">Отправить</button>
  </form>
</body>
</html>

Что увидите: форма с четырьмя блоками и кнопкой. Если нажать «Отправить» с пустыми полями, браузер не отправит форму и покажет подсказку у первого незаполненного поля. Если все заполнить, браузер отправит POST-запрос на https://example.com/feedback и откроет ответ этого сервера. Это адрес-пример без вашего обработчика, поэтому данные никуда не сохранятся.

Разбор по элементам

<form>. Атрибут action задает, куда отправлять, method — как. Обязательными они не являются: без action форма отправится на адрес текущей страницы, без method будет использован GET. Для формы обратной связи ставьте method="post" явно.

<label>. Связывается с полем через for и id (как у имени и email) или оборачивает поле (как у радиокнопок). Клик по подписи ставит фокус в поле, а скринридер зачитывает подпись вместе с полем. Серый текст-подсказка внутри поля подпись не заменяет: он исчезает при вводе и часто слишком бледный.

<fieldset> и <legend>. Группируют связанные поля. Для радиокнопок это важно: legend объясняет, к какому вопросу относятся варианты «5, 4, 3».

Радиокнопки. Одинаковый name="rating" объединяет их в группу, в которой можно выбрать один вариант. Уходит значение из value. Атрибут required достаточно поставить на одну кнопку группы.

<button type="submit">. Кнопка внутри формы по умолчанию и так имеет тип submit, но тип лучше писать явно: кнопка с type="button" форму не отправит и валидацию не запустит.

Типы input для формы обратной связи

type Для чего Что дает браузер
text имя, тема обычная строка
email почта проверка формата, клавиатура с @ на телефоне
tel телефон цифровая клавиатура, формат не проверяет
url сайт проверка, что это URL
checkbox согласие флажок, отправляется только если отмечен

Формат телефона проверяйте атрибутом pattern или на сервере.

GET или POST

GET POST
Где данные в URL после ? в теле запроса
Видно в истории, логах, закладках да нет
Ограничение объема длина URL, зависит от браузера и сервера лимит задает сервер
Для формы обратной связи нет да

POST не шифрует данные: он лишь убирает их из адресной строки. От перехвата защищает только HTTPS на странице и в action.

Валидация HTML5: что проверяет и чего не проверяет

Атрибуты required, minlength, maxlength, pattern, min, max и тип поля включают встроенную проверку: браузер не отправит форму и покажет сообщение. Это удобство для честного пользователя, а не защита.

Граница: проверка живет в браузере. Ее отключает атрибут novalidate, ее обходит отправка запроса напрямую через curl или любой HTTP-клиент, а type="email" проверяет только общий вид адреса, не существование ящика. Поэтому сервер обязан заново проверить каждое поле: наличие, длину, формат, допустимые значения (например, что rating — число от 1 до 5).

Пример ошибки и исправления:

<!-- неверно: у поля нет name -->
<input id="email" type="email" required>

Результат: форма проходит валидацию, но email в запрос не попадает. Исправление — добавить name="email".

Стили на CSS

.feedback {
  max-width: 480px;
  margin: 40px auto;
  padding: 24px;
  display: grid;
  gap: 8px;
  font-family: system-ui, sans-serif;
}

.feedback input,
.feedback textarea {
  padding: 10px;
  font: inherit;
  border: 1px solid #8a8a8a;
  border-radius: 6px;
}

.feedback fieldset {
  display: flex;
  gap: 12px;
  border: 1px solid #8a8a8a;
  border-radius: 6px;
}

.feedback :focus-visible {
  outline: 3px solid #2563eb;
  outline-offset: 2px;
}

.feedback :user-invalid {
  border-color: #c0262d;
}

.feedback button {
  justify-self: start;
  padding: 10px 20px;
  font: inherit;
  cursor: pointer;
}

Что изменится: форма встанет колонкой по центру шириной до 480 px, поля получат рамки и отступы, у элемента в фокусе с клавиатуры появится синяя обводка. Красная рамка появится только у поля, с которым пользователь уже взаимодействовал и заполнил неверно. Псевдокласс :invalid красил бы пустые обязательные поля сразу при загрузке, поэтому здесь :user-invalid. В старых браузерах, где его нет, правило просто не применится, форма останется рабочей.

Не убирайте обводку фокуса через outline: none без замены: пользователь клавиатуры перестанет видеть, где он находится.

Доступность: короткий чек-лист

  • У каждого поля есть видимый label, связанный с ним.
  • Группы радиокнопок и флажков — в fieldset с legend.
  • Пояснение к полю привязано через aria-describedby, как у email в примере.
  • Атрибут autocomplete с правильным значением (name, email, tel) — браузер подставит данные.
  • Ошибка описана словами, а не только красным цветом.
  • Форма проходится клавишей Tab в логичном порядке.

Граница HTML и CSS: что делает сервер

HTML-форма без обработчика на сервере данные никуда не доставит. Письмо администратору, запись в базу, уведомление в мессенджер — это код на бэкенде (PHP, Python, Node.js и так далее) или сторонний сервис приема форм. Минимум, который должен сделать обработчик:

  1. Принимать только POST и заново проверить все поля.
  2. Защититься от спама: скрытое поле-ловушка (honeypot), ограничение частоты запросов, при необходимости CAPTCHA.
  3. Если на сайте есть авторизация и сессии — проверять CSRF-токен.
  4. Экранировать данные при выводе в HTML и не подставлять их в заголовки письма без очистки: иначе возможна подмена получателей.
  5. Показать понятный ответ: успех или конкретную ошибку.
  6. Обрабатывать персональные данные по правилам юрисдикции (в России — 152-ФЗ): согласие, политика, срок хранения.

Если не получилось

Симптом Причина Что сделать
Данные поля не приходят нет name добавить name каждому полю
Клик по подписи не ставит фокус for не совпадает с id сверить значения, id уникален на странице
Форма отправляется пустой без предупреждений novalidate у формы или нет required убрать novalidate, добавить атрибуты
Кнопка ничего не делает type="button" или кнопка вне form type="submit" внутри формы
Данные видны в адресной строке нет method="post" указать method="post"

Выводы

  • Форма обратной связи на HTML состоит из form, полей с name, подписей label и кнопки type="submit"; CSS отвечает только за вид.
  • Для отправки сообщений нужен method="post", а от перехвата защищает HTTPS, не метод.
  • Встроенная валидация HTML5 помогает пользователю, но обходится; сервер обязан проверять все заново.
  • Доступность закладывается в разметку: связанные label, fieldset с legend, видимый фокус, autocomplete.
  • Без серверного обработчика форма данные не доставит; спам-защита, CSRF и работа с персональными данными — задачи бэкенда.

Где применяется / связь с практикой

Освойте тему на практике

Формы — первая задача верстальщика, где разметка встречается с поведением: семантика, доступность, адаптивные стили, состояния полей. Эти навыки разбирают на курсе HTML/CSS — от разметки до готовых адаптивных интерфейсов. Если хочется сначала посмотреть на формат занятий, подойдут открытые уроки Otus.

FAQ

Можно ли отправить форму на почту без сервера, через mailto в action?
Технически action="mailto:..." открывает почтовый клиент пользователя, но поведение зависит от браузера и настроек, а у многих клиента нет. Для рабочей формы нужен серверный обработчик или сервис приема форм.

Как отправить форму без перезагрузки страницы?
Перехватить событие submit в JavaScript и отправить данные через fetch с FormData. Серверная часть и проверки при этом остаются теми же.

Нужен ли атрибут enctype для формы обратной связи?
Для текстовых полей нет: по умолчанию используется application/x-www-form-urlencoded. Значение multipart/form-data нужно, если в форме есть загрузка файлов.

OTUS Журнал