Готовая разметка формы обратной связи с полями имени, e-mail, сообщения, загрузкой файла и согласием на обработку персональных данных. Все элементы имеют связанные подписи, заметный клавиатурный фокус и понятные ограничения.
Когда не подходит: это пример клиентской части. Он не отправляет письма самостоятельно и не заменяет серверный обработчик, проверку данных, CSRF-защиту, антиспам и безопасную обработку файлов.
Готовое решение
HTML-разметка
<form method="post" action="/feedback/" class="feedbackForm" enctype="multipart/form-data" > <fieldset class="formFieldset"> <legend class="formLegend">Отправить сообщение</legend> <p class="formHint"> Поля со знаком <span aria-hidden="true">*</span> обязательны. </p> <div class="formField"> <label for="feedbackName"> Имя <span aria-hidden="true">*</span> </label> <input class="formControl" type="text" id="feedbackName" name="userName" autocomplete="name" minlength="2" maxlength="100" required > </div> <div class="formField"> <label for="feedbackEmail"> E-mail <span aria-hidden="true">*</span> </label> <input class="formControl" type="email" id="feedbackEmail" name="userEmail" autocomplete="email" inputmode="email" maxlength="254" required > </div> <div class="formField"> <label for="feedbackMessage"> Сообщение <span aria-hidden="true">*</span> </label> <textarea class="formControl" id="feedbackMessage" name="userMessage" rows="7" minlength="10" maxlength="5000" required ></textarea> <p class="fieldHint" id="feedbackMessageHint"> От 10 до 5000 символов. </p> </div> <div class="formField"> <label for="feedbackFile">Прикрепить файл</label> <input class="fileControl" type="file" id="feedbackFile" name="attachment" accept=".pdf,.jpg,.jpeg,.png,application/pdf,image/jpeg,image/png" aria-describedby="feedbackFileHint" > <p class="fieldHint" id="feedbackFileHint"> PDF, JPG или PNG, не более 5 МБ. </p> </div> <div class="checkboxField"> <input type="checkbox" id="privacyConsent" name="privacyConsent" value="1" required > <label for="privacyConsent"> Я согласен с <a href="/policy/">политикой обработки персональных данных</a>. <span aria-hidden="true">*</span> </label> </div> <button class="submitButton" type="submit"> Отправить </button> </fieldset> </form>
Атрибут enctype="multipart/form-data" нужен только потому, что форма содержит input type="file". Если загрузка файлов не требуется, поле файла и этот атрибут можно удалить.
CSS-стили
:root {
--formPrimary: #2563eb;
--formPrimaryHover: #1d4ed8;
--formBorder: #9ca3af;
--formText: #1f2937;
--formMuted: #6b7280;
--formBackground: #ffffff;
--formError: #b91c1c;
}
.feedbackForm,
.feedbackForm * {
box-sizing: border-box;
}
.feedbackForm {
width: min(100%, 640px);
margin-inline: auto;
padding: 24px;
color: var(--formText);
background: var(--formBackground);
border: 1px solid #e5e7eb;
border-radius: 8px;
box-shadow: 0 8px 24px rgb(15 23 42 / 8%);
}
.formFieldset {
min-width: 0;
margin: 0;
padding: 0;
border: 0;
}
.formLegend {
margin-bottom: 8px;
font-size: 1.5rem;
font-weight: 700;
}
.formHint,
.fieldHint {
margin: 0;
color: var(--formMuted);
font-size: 0.875rem;
line-height: 1.5;
}
.formHint {
margin-bottom: 24px;
}
.formField {
display: grid;
gap: 8px;
margin-bottom: 20px;
}
.formField label,
.checkboxField label {
font-weight: 600;
}
.formControl {
width: 100%;
min-height: 44px;
padding: 10px 12px;
color: var(--formText);
background: #fff;
border: 1px solid var(--formBorder);
border-radius: 6px;
font: inherit;
}
textarea.formControl {
min-height: 140px;
resize: vertical;
}
.formControl:focus,
.fileControl:focus-visible,
.checkboxField input:focus-visible,
.submitButton:focus-visible,
.checkboxField a:focus-visible {
outline: 3px solid rgb(37 99 235 / 35%);
outline-offset: 2px;
}
.formControl:focus {
border-color: var(--formPrimary);
}
.formControl[aria-invalid="true"] {
border-color: var(--formError);
}
.fieldError {
margin: 0;
color: var(--formError);
font-size: 0.875rem;
font-weight: 600;
}
.fileControl {
max-width: 100%;
font: inherit;
}
.fileControl::file-selector-button {
margin-right: 12px;
padding: 8px 12px;
color: var(--formText);
background: #f3f4f6;
border: 1px solid var(--formBorder);
border-radius: 6px;
cursor: pointer;
}
.fileControl::file-selector-button:hover {
background: #e5e7eb;
}
.checkboxField {
display: grid;
grid-template-columns: auto 1fr;
align-items: start;
gap: 10px;
margin-bottom: 24px;
}
.checkboxField input {
width: 20px;
height: 20px;
margin: 2px 0 0;
accent-color: var(--formPrimary);
}
.checkboxField label {
line-height: 1.5;
}
.submitButton {
min-height: 44px;
padding: 10px 20px;
color: #fff;
background: var(--formPrimary);
border: 0;
border-radius: 6px;
font: inherit;
font-weight: 700;
cursor: pointer;
}
.submitButton:hover {
background: var(--formPrimaryHover);
}
.submitButton:disabled {
cursor: not-allowed;
opacity: 0.65;
}
@media (max-width: 480px) {
.feedbackForm {
padding: 16px;
}
}
@media (prefers-reduced-motion: reduce) {
.feedbackForm *,
.feedbackForm *::before,
.feedbackForm *::after {
scroll-behavior: auto;
transition-duration: 0.01ms;
}
}
@media (forced-colors: active) {
.formControl:focus,
.fileControl:focus-visible,
.checkboxField input:focus-visible,
.submitButton:focus-visible,
.checkboxField a:focus-visible {
outline: 2px solid CanvasText;
}
}
Нативный чекбокс не скрывается и не заменяется псевдоэлементами. Свойство accent-color меняет его основной цвет, сохраняя привычное поведение в браузере и работу вспомогательных технологий.
Что отправится на сервер
userName— имя пользователя;userEmail— адрес электронной почты;userMessage— текст сообщения;attachment— загруженный файл, если он выбран;privacyConsent— значение1, если пользователь установил флажок.
Если чекбокс не установлен, браузер не отправляет его имя и значение. Сервер должен самостоятельно проверить наличие privacyConsent.
Вывод ошибки рядом с полем
После неудачной серверной проверки верните форму с введёнными безопасно экранированными значениями, добавьте полю aria-invalid="true" и свяжите его с текстом ошибки через aria-describedby:
<div class="formField"> <label for="feedbackEmail"> E-mail <span aria-hidden="true">*</span> </label> <input class="formControl" type="email" id="feedbackEmail" name="userEmail" value="user@example" autocomplete="email" aria-invalid="true" aria-describedby="feedbackEmailError" required > <p class="fieldError" id="feedbackEmailError"> Укажите корректный адрес электронной почты. </p> </div>
Одного красного цвета недостаточно: причина ошибки должна быть написана текстом.
CSRF-защита
Если отправка формы изменяет данные, работает в авторизованной области или использует сессию, добавьте одноразовый CSRF-токен. Его генерирует сервер или фреймворк — статическое значение использовать нельзя.
<input
type="hidden"
name="csrfToken"
value="{{ csrfToken }}"
>
При обработке запроса сервер должен сравнить токен с сохранённым значением безопасным способом и отклонить запрос при несовпадении.
Краевые случаи
- Атрибут
acceptтолько подсказывает браузеру допустимые типы файлов. Пользователь может обойти это ограничение. - HTML не ограничивает размер файла. Лимит 5 МБ необходимо проверить на сервере и согласовать с настройками веб-сервера и PHP.
- Значение
maxlengthулучшает интерфейс, но сервер всё равно должен проверять фактическую длину строки. - Поля с
requiredпроходят встроенную браузерную проверку, однако HTTP-запрос можно отправить в обход формы. - Не используйте placeholder вместо
label: подсказка исчезает после начала ввода и не заменяет постоянное название поля. - После серверной ошибки сохраняйте введённые имя, e-mail и сообщение, но никогда не пытайтесь восстановить выбранный файл.
- Кнопку можно временно отключить после отправки через JavaScript, однако сервер обязан самостоятельно защищаться от повторных запросов.
Безопасность
- Проверяйте все поля на сервере: обязательность, длину, формат и допустимые значения.
- Экранируйте данные в момент вывода в конкретный контекст. Для HTML-текста обычно используют экранирование специальных символов.
- Не подставляйте пользовательские значения напрямую в SQL, команды оболочки, HTTP-заголовки или шаблон письма.
- Для файлов проверяйте расширение, MIME-тип и содержимое; задавайте случайное имя и храните загрузки вне публичного web-root.
- Ограничьте размер файла, количество загрузок и частоту отправки формы.
- Не разрешайте исполняемые расширения:
.php,.phar,.phtml,.cgi,.shи аналогичные. - Для публичной формы добавьте rate limit, журналирование и антиспам. CAPTCHA должна быть дополнительным уровнем, а не единственной защитой.
- Не отправляйте пользовательский e-mail напрямую в заголовок
From. Используйте адрес своего домена, а пользовательский адрес помещайте в проверенныйReply-To.
Доступность
- Каждое поле связано с подписью через одинаковые значения
forиid. - Обязательные поля отмечены визуально и имеют атрибут
required. - Подсказки связаны с полями через
aria-describedby. - Фокус не скрывается: при навигации клавишей Tab активный элемент хорошо заметен.
- Минимальная высота текстовых полей и кнопки составляет 44 пикселя.
- Пользователь может увеличить высоту
textareaпо вертикали.
Производительность
Разметка и стили не требуют JavaScript и сторонних библиотек. Основная нагрузка появляется на сервере при отправке писем, проверке антивирусом и обработке файлов. Ограничивайте размер загрузки до начала ресурсоёмких операций.
Совместимость
Форма использует стандартные элементы HTML и работает в актуальных версиях Chrome, Edge, Firefox и Safari. Если браузер не поддерживает accent-color, чекбокс сохранит стандартный внешний вид. Псевдоэлемент ::file-selector-button влияет только на оформление кнопки выбора файла и не мешает работе самого поля.
Как проверить форму
- Откройте страницу и последовательно нажимайте Tab. Фокус должен быть виден на каждом поле, ссылке и кнопке.
- Нажмите «Отправить» с пустыми обязательными полями и проверьте встроенные сообщения браузера.
- Введите неправильный e-mail и убедитесь, что форма не отправляется.
- Проверьте минимальную и максимальную длину сообщения.
- Попробуйте выбрать разрешённый и запрещённый тип файла.
- Отправьте запрос напрямую через DevTools или HTTP-клиент и убедитесь, что сервер повторяет все проверки.
- Проверьте файл больше установленного лимита.
- Проверьте форму при масштабе страницы 200% и на экране шириной около 320 пикселей.
- После серверной ошибки убедитесь, что сообщение понятно, связано с нужным полем и не определяется только цветом.
Альтернативы
- Убрать загрузку файлов и оставить более простую форму имени, e-mail и сообщения.
- Отправлять данные через
fetch(), если нужен интерфейс без перезагрузки. Серверные проверки при этом остаются обязательными. - Использовать готовый компонент формы из дизайн-системы, если в проекте много однотипных форм.
- Для MODX Revolution подключить серверный обработчик через FormIt с собственными хуками проверки, антиспама и загрузки файлов.
Комментарии
0Нашли ошибку или хотите дополнить материал?