44 Модуль 9 Качество и производительность 17 мин урок 19 из 25

Формы, которые не бесят

Восемь состояний одного поля, момент показа ошибки, стилизация чекбоксов без обмана и мобильная клавиатура, которая открывается правильной.

Что ты научишься делать

  • Оформишь все состояния поля, а не только покой и фокус
  • Покажешь ошибку тогда, когда она помогает, а не мешает
  • Настроишь мобильную клавиатуру и автозаполнение

#Форма — место, где люди уходят

До формы человек уже дошёл: нашёл сайт, выбрал товар, решился. Форма — последний шаг, и именно на нём отваливаются.

Причины почти всегда одни и те же: непонятно, что не так; ошибка появилась раньше, чем закончил печатать; поле сбросилось; на телефоне открылась буквенная клавиатура для номера телефона.

Всё это чинится вёрсткой, без единой строчки серверного кода.

Интерактивный разбор доступен при включённом JavaScript.

#Момент показа ошибки

Самая раздражающая вещь в формах — поле, которое краснеет, пока ты ещё печатаешь. Ты набрал первую букву почты, а сайт уже кричит, что адрес неверный.

Причина в псевдоклассе :invalid: он срабатывает сразу, ещё до того, как человек что-либо ввёл. Пустое обязательное поле недействительно с момента загрузки страницы.

Так не надо

/* Краснеет сразу при загрузке страницы */
input:invalid {
  border-color: #c8394a;
}

Пустое обязательное поле недействительно с самого начала. Человек открыл форму и увидел красное поле, ничего не сделав.

Так надо

/* Краснеет только после попытки ввода */
input:user-invalid {
  border-color: #c8394a;
  background: #fff5f6;
}

<code>:user-invalid</code> срабатывает после того, как человек взаимодействовал с полем и ушёл из него. Ровно тот момент, когда подсказка уместна.

#Полезные псевдоклассы

ПсевдоклассКогда срабатываетЗачем
:user-invalidПосле взаимодействия и ухода из поляПоказ ошибки в правильный момент
:user-validТо же, но когда всё верноЗелёная галочка после заполнения
:placeholder-shownПока поле пустоеОтличить пустое от заполненного без скрипта
:autofillЗначение подставил браузерУбрать жёлтый фон автозаполнения
:has(:focus)Внутри есть элемент в фокусеПодсветить всю группу полей
:disabled, :read-onlyСоответствующее состояниеПриглушить, но оставить читаемым
Плавающая подпись без скриптов
В песочницу

#Сообщения об ошибках

Плохое сообщение говорит, что не так. Хорошее — что сделать.

ПлохоХорошо
Некорректное значениеНужен адрес вида имя@почта.ру
Ошибка валидацииТелефон должен начинаться с +7 или 8
Поле обязательноБез имени мы не поймём, к кому обращаться
Неверный форматДата в формате 31.12.2026

Сообщение должно стоять рядом с полем, а не общим списком сверху. И быть связано с полем через aria-describedby, иначе читалка его не свяжет.

HTMLДоступная ошибка
<div class="field">  <label for="mail">Почта</label>  <input type="email" id="mail" name="mail" required         aria-describedby="mail-error"         aria-invalid="true">  <!-- role="alert" объявит текст сразу, как только он появится -->  <p class="field__error" id="mail-error" role="alert">    Похоже, здесь опечатка — нужен адрес вида имя@почта.ру  </p></div>

#Мобильная клавиатура и автозаполнение

Два атрибута, которые экономят человеку минуты, а стоят пары слов.

inputmode выбирает раскладку клавиатуры на телефоне. autocomplete разрешает браузеру подставить сохранённые данные — адрес, имя, карту.

HTMLОформление заказа
<input type="text" name="name"       autocomplete="name" autocapitalize="words"><input type="email" name="email"       inputmode="email" autocomplete="email"       autocapitalize="off" spellcheck="false"><!-- type="tel" даёт телефонную клавиатуру,     inputmode уточняет её вид --><input type="tel" name="phone"       inputmode="tel" autocomplete="tel"><!-- Индекс: только цифры, но type="number" здесь не годится —     он режет ведущие нули и показывает стрелки --><input type="text" name="zip"       inputmode="numeric" pattern="[0-9]*"       autocomplete="postal-code"><input type="text" name="card"       inputmode="numeric" autocomplete="cc-number">

#Стилизация без обмана

Долгое время чекбоксы и переключатели «стилизовали» так: настоящий элемент прятали, а рядом рисовали свой из div. Это ломает доступность: пропадает фокус, состояние не объявляется, клавиатура не работает.

Сегодня половина задач решается свойством accent-color, а остальное — стилизацией самого элемента через appearance: none.

CSSНастоящий чекбокс, свой вид
/* Самый простой путь: перекрасить встроенный вид одной строкой */:root {  accent-color: #6544e0;}/* Полный контроль — но элемент остаётся настоящим */.check {  appearance: none;  inline-size: 20px;  block-size: 20px;  border: 2px solid #ccd;  border-radius: 6px;  display: grid;  place-content: center;}.check::before {  content: "";  inline-size: 10px;  block-size: 10px;  clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0, 43% 62%);  background: #fff;  transform: scale(0);  transition: transform .12s ease;}.check:checked {  background: #6544e0;  border-color: #6544e0;}.check:checked::before { transform: scale(1); }/* Фокус обязан остаться видимым */.check:focus-visible {  outline: 3px solid #6544e0;  outline-offset: 2px;}

#Длинные формы

Форма из двадцати полей отпугивает одним видом. Приёмы, которые действительно помогают.

  • Разбить на шаги. Три экрана по пять полей проходят охотнее, чем один на пятнадцать. Обязательно показывай, сколько шагов всего.
  • Сгруппировать через fieldset. «Контакты», «Доставка», «Оплата» — человек видит структуру и понимает, сколько осталось.
  • Убрать необязательное. Каждое поле должно оправдывать своё существование. Отчество нужно налоговой, а не форме подписки.
  • Помечать необязательные, а не обязательные. Если из десяти полей девять обязательны, девять звёздочек — это шум. Помечай исключения.
  • Не сбрасывать введённое. Ошибка на четвёртом шаге не должна стирать первые три.

#Как это выглядит в реальном проекте

CSSfield.css
@layer components {  .field { display: grid; gap: 4px; }  .field__input {    padding: 10px 12px;    border: 1px solid var(--color-border);    border-radius: var(--radius-sm);  }  /* Подсвечиваем всю группу, когда фокус внутри */  .field:has(:focus-visible) .field__label {    color: var(--color-accent);  }  /* Ошибка — после попытки ввода, не раньше */  .field__input:user-invalid {    border-color: var(--color-danger);    background: color-mix(in oklab, var(--color-danger) 8%, transparent);  }  /* Сообщение показываем только когда поле реально ошибочно */  .field__error { display: none; color: var(--color-danger); }  .field:has(:user-invalid) .field__error { display: block; }  /* Браузер подставил значение — убираем жёлтый фон,     но оставляем признак автозаполнения */  .field__input:autofill {    box-shadow: inset 0 0 0 100px var(--color-surface);    -webkit-text-fill-color: var(--color-text);  }  .field__input:disabled {    opacity: .6;    cursor: not-allowed;  }}

#Когда НЕ надо

  • Прятать настоящий элемент ради красоты. Пропадает фокус, клавиатура и объявление состояния. Стилизуй сам элемент через appearance: none.
  • Отключать вставку из буфера. Запрет вставки в поле подтверждения почты не повышает надёжность, а мешает людям с менеджерами паролей.
  • Ставить maxlength вслепую. Обрезание на лету путает: человек печатает, а символы не появляются. Лучше показать счётчик.
  • Проверять формат телефона строгой маской. Форматы разные в разных странах, а на границах масок люди застревают. Принимай как есть, нормализуй на сервере.

#Поддержка браузерами

ВозможностьСостояние
:placeholder-shownВо всех современных браузерах
:user-invalid, :user-validВо всех современных браузерах; в старых сборках был префикс :-moz-ui-invalid
accent-colorВо всех современных браузерах
appearance: noneВезде
:autofillПоддерживается; в WebKit нужен префикс :-webkit-autofill
inputmodeВо всех мобильных браузерах

Где спотыкаются почти все

Использован <code>:invalid</code> вместо <code>:user-invalid</code>

Почему Пустое обязательное поле недействительно с загрузки страницы — форма краснеет до того, как человек к ней прикоснулся.

Как надо Проверяй через :user-invalid: он ждёт взаимодействия.

<code>type="number"</code> для телефона или индекса

Почему Обрезаются ведущие нули, появляются стрелки, прокрутка колесом меняет значение.

Как надо type="text" плюс inputmode="numeric".

Настоящий чекбокс спрятан, рядом нарисован свой

Почему Теряется фокус и объявление состояния. С клавиатуры такой чекбокс не переключить.

Как надо Стилизуй сам элемент: appearance: none плюс :checked и :focus-visible.

Сообщение об ошибке не связано с полем

Почему Читалка озвучит поле без объяснения, что с ним не так.

Как надо Свяжи через aria-describedby и добавь role="alert".

Состояние поля field state
Покой, наведение, фокус, заполнено, ошибка, недоступно, только чтение, проверка.
Отложенная проверка :user-invalid
Псевдокласс, срабатывающий только после взаимодействия человека с полем.
Режим ввода inputmode
Атрибут, задающий вид клавиатуры на сенсорных устройствах.
Автозаполнение autocomplete
Подсказка браузеру, какие сохранённые данные подставить.
Акцентный цвет accent-color
Свойство, перекрашивающее встроенный вид чекбоксов, переключателей и ползунков.

Проверь себя

  1. Почему <code>input:invalid</code> красит поле сразу при загрузке?

    Псевдокласс не ждёт взаимодействия. Для правильного момента есть <code>:user-invalid</code>.

  2. Зачем в плавающей подписи ставят <code>placeholder=" "</code>?

    Пробел невидим, но включает псевдокласс — и подпись уезжает без единой строчки скрипта.

  3. Какой тип поля выбрать для почтового индекса?

    <code>number</code> обрезает ведущие нули и добавляет стрелки. Нужны цифры на клавиатуре, а не арифметика.

  4. Что плохого в спрятанном чекбоксе с нарисованным рядом div?

    Стилизовать нужно сам элемент через <code>appearance: none</code> — тогда он остаётся настоящим.

  5. Когда показывать ошибку и когда её убирать?

    Так подсказка ощущается помощью: она не мешает печатать и исчезает, как только человек начал исправлять.

Задание

Форма со всеми состояниями

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

Почини: замени :invalid на :user-invalid, верни видимый фокус через :focus-visible, задай accent-color, добавь полям autocomplete и inputmode, а сообщение об ошибке свяжи с полем через aria-describedby.

Открыть в песочнице
    Подсказка 1

    Замени .field__input:invalid на .field__input:user-invalid — остальное правило оставь как есть.

    Подсказка 2

    Фокус: замени :focus { outline: none; } на :focus-visible { outline: 3px solid #6544e0; outline-offset: 1px; }

    Подсказка 3

    Чекбокс: добавь в начало CSS правило :root { accent-color: #6544e0; }

    Подсказка 4

    Полям: <input ... autocomplete="email"> и <input ... inputmode="tel" autocomplete="tel">, а почте ещё aria-describedby="mail-error".

    Показать решение
    HTMLindex.html
    <form class="form">  <div class="field">    <label for="mail">Почта</label>    <input class="field__input" type="email" id="mail" name="mail" required           inputmode="email" autocomplete="email"           autocapitalize="off" spellcheck="false"           aria-describedby="mail-error">    <p class="field__error" id="mail-error">Нужен адрес вида имя@почта.ру</p>  </div>  <div class="field">    <label for="phone">Телефон</label>    <input class="field__input" type="tel" id="phone" name="phone"           inputmode="tel" autocomplete="tel">  </div>  <label class="agree">    <input type="checkbox" checked>    <span>Согласен на обработку данных</span>  </label>  <button type="submit">Отправить</button></form>
    CSSstyle.css
    :root { accent-color: #6544e0; }.form { display: grid; gap: 14px; max-width: 320px; }.field { display: grid; gap: 4px; }label { font-size: 14px; font-weight: 600; }.field__input {  padding: 9px 12px;  font: inherit;  border: 1px solid #ccd;  border-radius: 8px;}/* Только после того, как человек поработал с полем */.field__input:user-invalid {  border-color: #c8394a;  background: #fff5f6;}.field__error { margin: 0; font-size: 13px; color: #c8394a; }.agree { display: flex; align-items: center; gap: 8px; font-weight: 400; }button {  padding: 11px;  font: inherit;  font-weight: 600;  color: #fff;  background: #6544e0;  border: 0;  border-radius: 999px;}:focus-visible {  outline: 3px solid #6544e0;  outline-offset: 1px;}

    Проверь себя