Skip to content

Latest commit

 

History

History
913 lines (705 loc) · 42.5 KB

File metadata and controls

913 lines (705 loc) · 42.5 KB

Урок 13. Стилизация input: типы полей, псевдоклассы :focus/:invalid, базовая доступность

Почему input — это отдельная большая тема

До сих пор мы стилизовали блоки, текст, изображения, сетки — элементы, которые ведут себя более или менее предсказуемо. input — другое дело: это интерактивный элемент управления, у которого:

  • есть собственное поведение браузера "из коробки" (фокус, ввод текста, встроенная валидация);
  • внешний вид по умолчанию сильно отличается между браузерами и операционными системами — тот же <input type="checkbox"> в Chrome на Windows, Safari на macOS и Firefox на Android выглядит и ведёт себя по-разному;
  • это точка прямого взаимодействия пользователя с сайтом — здесь особенно важна доступность (accessibility): не все посетители водят мышкой, кто-то использует клавиатуру или экранный диктор.

Мы разберём стилизацию input отдельно от самой формы — про <form> целиком, отправку данных на сервер и логику форм речь пойдёт в Уроке 15. Здесь наша задача — научиться делать сами поля ввода аккуратными, понятными и доступными.


Типы input и когда какой использовать

Атрибут type полностью меняет поведение input — это не просто "разные виды рамочек", а разная логика ввода, разная клавиатура на мобильных устройствах и разная встроенная валидация.

type Назначение Особое поведение браузера
text Обычный однострочный текст Ничего особенного, базовый вариант
email Email-адрес Встроенная проверка формата (@, домен); на мобильных — клавиатура с @
password Пароль Символы скрываются точками/звёздочками
number Числовое значение Стрелочки увеличения/уменьшения; на мобильных — цифровая клавиатура
tel Телефон Не валидирует формат сам (номера слишком разные в мире), но вызывает цифровую клавиатуру на мобильных
url Ссылка Встроенная проверка на наличие протокола и структуры URL
search Поле поиска В некоторых браузерах — крестик очистки поля "из коробки"
date Дата Нативный календарь-пикер браузера
checkbox Логический выбор (да/нет, вкл/выкл) Можно выбрать несколько одновременно
radio Выбор одного варианта из группы Только один активен внутри группы с одинаковым name
range Числовое значение через ползунок Визуальный слайдер вместо текстового поля
file Загрузка файла Открывает системный диалог выбора файла
submit Кнопка отправки формы Визуально похож на <button>, разберём подробно в Уроке 14

Важное практическое следствие: правильный type — это не мелочь ради "красоты", а первая линия валидации и главный помощник для мобильных пользователей. Если для номера телефона поставить type="text" вместо type="tel" — пользователь на телефоне получит обычную буквенную клавиатуру вместо цифровой, и наберёт номер куда медленнее и с большей вероятностью ошибки.

<div class="demo-form">
  <input type="text" placeholder="Ваше имя">

  <input type="email" placeholder="you@example.com">

  <input type="password" placeholder="Пароль">

  <input type="tel" placeholder="+7 900 000-00-00">

  <input type="number" min="1" max="10" placeholder="Возраст">

  <input type="date">

  <input type="search" placeholder="Поиск">

  <input type="range" min="0" max="100">

  <input type="file">
</div>
.demo-form {
  display: flex;
  flex-direction: column;
  gap: 12px;

  max-width: 400px;
}

Базовая стилизация текстовых полей

По умолчанию input выглядит по-разному в каждом браузере — со своей рамкой, отступами, шрифтом. Первый шаг — привести это к общему, предсказуемому виду.

<div class="field-group">
  <input
    type="text"
    class="field"
    placeholder="Введите имя"
  >
</div>
.field-group {
  max-width: 400px;
}

.field {
  width: 100%;
  padding: 10px 14px;
  font-size: 16px;
  font-family: inherit;
  color: #1a1a1a;
  background: #fff;
  border: 1px solid #d1d5db;
  border-radius: 6px;
  outline: none; /* уберём стандартную обводку — но обязательно заменим её ниже через :focus! */
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

Разберём построчно:

  • width: 100% + box-sizing: border-box (обычно задаётся глобально, * { box-sizing: border-box; }) — без border-box ширина 100% плюс padding и border может вылезти за пределы родительского контейнера. Это одна из самых частых причин "поле не помещается в форму" у новичков.
  • font-family: inherit — по умолчанию у input в некоторых браузерах свой системный шрифт, отличный от остального текста страницы. inherit подтягивает шрифт от родителя, чтобы поле визуально не выбивалось.
  • outline: none — убирает стандартную синюю обводку браузера при фокусе. Это опасная строчка — подробнее разберём её в разделе "Подводные камни": убирать outline можно только если тут же ставится собственная, не менее заметная альтернатива через :focus.

::placeholder — псевдоэлемент подсказки

Текст-подсказка внутри пустого поля (placeholder="...") — отдельный псевдоэлемент, который стилизуется через ::placeholder:

<div class="field-group">
  <input
    type="text"
    class="field"
    placeholder="Например, Иван Иванов"
  >
</div>
.field::placeholder {
  color: #9ca3af;
  font-style: italic;
}

Важно: placeholder — это подсказка о формате ввода ("например, +7 900 000-00-00"), а не замена подписи к полю. Подробнее — в разделе про доступность ниже.


Псевдокласс :focus — состояние активного поля

:focus срабатывает, когда поле получило фокус — пользователь кликнул в него или перешёл клавишей Tab.

<div class="field-group">
  <label for="name">Имя</label>

  <input
    id="name"
    type="text"
    class="field"
    placeholder="Введите имя"
  >
</div>
.field-group {
  max-width: 400px;
}

.field {
  width: 100%;
  padding: 10px 14px;

  font: inherit;

  border: 1px solid #d1d5db;
  border-radius: 6px;

  outline: none;

  transition:
    border-color 0.15s ease,
    box-shadow 0.15s ease;
}

.field:focus {
  border-color: #2563eb;
  box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.15);
}

Кликните в поле, а затем щёлкните в любое место страницы. Обратите внимание, как при получении фокуса изменяется оформление поля.

Здесь мы заменили убранный outline собственным решением: более насыщенный цвет рамки плюс мягкое "свечение" через box-shadow. Это даёт даже более заметный и стилистически согласованный со страницей эффект, чем стандартная обводка браузера — но, в отличие от простого outline: none без замены, не оставляет пользователя без визуальной подсказки, где сейчас находится фокус.

:focus-visible — фокус только для клавиатуры

Современный псевдокласс :focus-visible решает конкретную проблему: браузер сам определяет, был ли фокус получен через клавиатуру (когда обводка действительно нужна) или через клик мышью (когда многие дизайнеры считают обводку визуально излишней).

<div class="field-group">
  <label for="email">Email</label>

  <input
    id="email"
    type="email"
    class="field"
    placeholder="you@example.com"
  >
</div>
.field:focus-visible {
  outline: 2px solid #2563eb;
  outline-offset: 2px;
}

Попробуйте перейти к полю с помощью клавиши Tab. Обратите внимание на появление обводки вокруг активного элемента.

Разница на практике: клик мышью в поле — :focus срабатывает, :focus-visible в большинстве браузеров тоже сработает для input (в отличие, например, от кнопок, где поведение отличается сильнее). Но общий принцип стоит запомнить: :focus-visible — более "умный" вариант :focus, специально спроектированный так, чтобы не мешать визуально там, где в обводке нет необходимости, и не убирать её там, где она нужна для навигации с клавиатуры.


Встроенная HTML5-валидация и псевдоклассы :valid / :invalid

Браузер умеет валидировать некоторые поля сам, без единой строчки JavaScript — через атрибуты required, pattern, type="email", min/max и подобные. Псевдоклассы :valid и :invalid отражают текущий результат этой встроенной проверки прямо в CSS.

<div class="field-group">
  <label for="email">Email</label>

  <input
    id="email"
    type="email"
    required
    class="field"
    placeholder="you@example.com"
  >
</div>

CSS оставил бы практически без изменений.

.field:invalid {
  border-color: #dc2626;
}

.field:valid {
  border-color: #16a34a;
}

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

Атрибуты валидации

Атрибут Что проверяет
required Поле не должно быть пустым
minlength / maxlength Минимальная/максимальная длина текста
min / max Минимальное/максимальное числовое значение (для type="number", date, range)
pattern Регулярное выражение, которому должно соответствовать значение
step Шаг для числовых значений (например, step="0.01" для копеек)
<div class="field-group">
  <label for="username">Имя пользователя</label>

  <input
    id="username"
    type="text"
    class="field"
    placeholder="Например, Иван"
    pattern="[A-Za-zА-Яа-я]{2,30}"
    required
    title="Только буквы, от 2 до 30 символов"
  >
</div>

Атрибут title в связке с pattern — это текст, который браузер покажет во всплывающей подсказке, если введённое значение не соответствует регулярному выражению.

Проблема "голого" :invalid — и как её решать через CSS

Если применить :invalid буквально как в примере выше, поле с required окажется отмечено красным сразу при загрузке страницы — ещё до того, как пользователь вообще успел что-либо ввести. Технически поле действительно невалидно (оно пустое, а обязательно), но с точки зрения пользовательского опыта это выглядит так, будто он уже совершил ошибку, хотя только открыл форму.

Частое CSS-решение — использовать комбинацию с :placeholder-shown: подсказка-placeholder показывается только тогда, когда поле пустое, значит красную рамку нужно применять к невалидному состоянию только если placeholder уже не отображается (то есть пользователь что-то ввёл и удалил, либо ввёл некорректное значение):

<div class="field-group">
  <label for="city">Город</label>

  <input
    id="city"
    type="text"
    class="field"
    placeholder="Введите город"
    required
  >
</div>
.field:invalid:not(:placeholder-shown) {
  border-color: #dc2626;
}

Попробуйте открыть страницу и ничего не вводить — поле останется нейтральным. Затем введите текст и удалите его. После этого сработает правило :invalid:not(:placeholder-shown), и поле получит красную рамку.

Так поле остаётся нейтральным, пока пользователь ещё не начал с ним взаимодействовать, и подсвечивается красным только после того, как он ввёл (и, например, стёр или ввёл некорректно) какое-то значение. Это простое и надёжное решение, которое работает без единой строчки JavaScript.

Более новый псевдокласс :user-invalid решает эту же задачу ещё точнее (срабатывает после того, как пользователь взаимодействовал с полем, а не по признаку "показан ли placeholder"), но на момент написания курса поддержка в браузерах у него всё ещё неполная, поэтому в качестве базового и надёжного варианта мы используем связку :invalid:not(:placeholder-shown).


Псевдоклассы состояния: :disabled, :required, :hover

<div class="field-group">
  <label for="name">Имя *</label>
  <input
    id="name"
    type="text"
    class="field"
    required
    placeholder="Введите имя"
  >
</div>

<div class="field-group">
  <label for="city">Город</label>
  <input
    id="city"
    type="text"
    class="field"
    placeholder="Недоступно"
    disabled
  >
</div>
.field:disabled {
  background: #f3f4f6;
  color: #9ca3af;
  cursor: not-allowed;
}

.field:required {
  border-left: 3px solid #f59e0b;
}

.field:hover:not(:disabled) {
  border-color: #9ca3af;
}

Попробуйте навести курсор на оба поля. Активное поле изменит цвет рамки, а отключённое (disabled) никак не отреагирует.

:hover:not(:disabled) — полезный приём: эффект при наведении мышью применяется, только если поле активно. Незачем показывать пользователю "живую" реакцию на элемент, с которым сейчас нельзя взаимодействовать.


Стилизация checkbox и radio

Чекбоксы и радиокнопки устроены иначе, чем текстовые поля — у них нет "текста внутри", есть только состояние вкл/выкл, и стандартный внешний вид у браузеров отличается сильнее всего.

Быстрый путь: accent-color

Самый простой современный способ подкрасить чекбоксы, радиокнопки и range под цветовую схему сайта — свойство accent-color. Оно не меняет форму элемента, но красит его в нужный цвет, сохраняя нативное поведение и доступность браузера "из коробки":

<div class="field-group">
  <label>
    <input type="checkbox">
    Запомнить меня
  </label>
</div>

<div class="field-group">
  <label>
    <input type="radio" name="theme" checked>
    Светлая тема
  </label>

  <label>
    <input type="radio" name="theme">
    Тёмная тема
  </label>
</div>
input[type="checkbox"],
input[type="radio"] {
  accent-color: #2563eb;
  width: 18px;
  height: 18px;
}

Для большинства учебных и даже вполне серьёзных прикладных задач этого достаточно — доступность полностью сохраняется (это по-прежнему нативный элемент со всем его поведением), а внешний вид уже не выглядит как "стандартный браузерный чекбокс, забытый дизайнером".

Более глубокая кастомизация: appearance: none

Если нужен полностью нестандартный вид (например, чекбокс-переключатель), браузерный вид можно убрать полностью через appearance: none, а затем нарисовать элемент заново вручную — через background, border и псевдоэлемент ::before для галочки. Такой подход требует больше кода и внимательности к доступности (важно не потерять фокус-стили и не сломать поведение для скринридеров), поэтому в рамках этого курса мы им подробно не занимаемся — достаточно знать, что такой путь существует, и что accent-color в большинстве практических случаев — оптимальный баланс простоты и результата.


Базовая доступность (accessibility) для полей ввода

Стилизация — это не только "красиво", но и "можно ли этим пользоваться всем". Несколько базовых правил, которые стоит соблюдать всегда, а не только "для галочки".

<label> — обязательная подпись к полю

<!-- Плохо: только placeholder, подписи нет -->
<div class="field-group">
  <input type="text" placeholder="Имя">
</div>

<!-- Правильно: есть настоящая подпись -->
<div class="field-group">
  <label for="name">Имя</label>
  <input
    id="name"
    type="text"
    placeholder="Например, Мария"
  >
</div>

Атрибут for у <label> должен совпадать с id соответствующего input. Это делает две вещи одновременно: клик по тексту подписи переносит фокус в поле (удобно на мобильных — попасть пальцем в текст легче, чем в узкое поле), и экранные дикторы озвучивают эту подпись, когда пользователь переходит к полю.

Почему placeholder не заменяет label: как только пользователь начинает печатать, placeholder исчезает — и если он был единственным указанием, что это за поле, пользователь (особенно при возврате к форме или при использовании автозаполнения) теряет контекст. Кроме того, экранные дикторы не всегда озвучивают placeholder так же надёжно, как настоящий <label>.

Когда видимого label нет — используйте aria-label

Иногда по дизайну поле не должно иметь видимой подписи (например, компактное поле поиска с одной лишь иконкой лупы). В этом случае подпись всё равно должна существовать — просто не визуально, а для вспомогательных технологий:

<div class="field-group">
  <input
    type="search"
    class="field"
    aria-label="Поиск по сайту"
    placeholder="Найти фильм..."
  >
</div>

Не убирайте фокус без замены

Мы уже касались этого выше: outline: none без альтернативы (например, через :focus или :focus-visible) делает страницу непригодной для навигации с клавиатуры — пользователь просто не видит, где сейчас находится фокус, и не понимает, в какое поле он печатает.

Достаточная область для нажатия

На мобильных устройствах рекомендуемая минимальная область нажатия — около 44×44px. Слишком тонкие чекбоксы или радиокнопки (например, 12px) сложно точно нажать пальцем.

<label>
  <input type="checkbox">
  Я принимаю условия использования
</label>

<br>

<label>
  <input type="radio" name="plan">
  Базовый тариф
</label>

<label>
  <input type="radio" name="plan">
  Премиум тариф
</label>
input[type="checkbox"],
input[type="radio"] {
  width: 20px;
  height: 20px;
}

label {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 0;
}

Практика: поиск в шапке и фильтры в aside (страница фильмов)

Вернёмся к нашему сквозному примеру — странице фильмов из Уроков 11–12 — и добавим два реальных элемента с формами: поле поиска в шапке и чекбоксы фильтров в aside.

Раметка и стилизация из финальной практики урока 11:
<!DOCTYPE html>
<html lang="ru">
<head>
  <meta charset="UTF-8" />
  <title>MovieGrid</title>
  <link rel="stylesheet" href="style.css" />
</head>
<body class="page">
  <header class="header">
    <div class="header__logo">🎬 MovieGrid</div>
    <nav class="header__nav">
      <a href="#">Главная</a>
      <a href="#">Фильмы</a>
      <a href="#">Контакты</a>
    </nav>
  </header>

  <aside class="filters">
    <div>Фильмы</div>
    <div>Сериалы</div>
    <div>Мультики</div>
  </aside>

  <main class="movies">
    <article class="movie-card">
      <img src="https://picsum.photos/300/200?1" alt="Фильм 1" />
      <h3>Название фильма 1</h3>
      <p>Краткое описание фильма для примера карточки.</p>
      <a href="#">Смотреть</a>
    </article>
    <article class="movie-card">
      <img src="https://picsum.photos/300/200?2" alt="Фильм 2" />
      <h3>Название фильма 2</h3>
      <p>Краткое описание фильма для примера карточки.</p>
      <a href="#">Смотреть</a>
    </article>
    <article class="movie-card">
      <img src="https://picsum.photos/300/200?3" alt="Фильм 3" />
      <h3>Название фильма 3</h3>
      <p>Краткое описание фильма для примера карточки.</p>
      <a href="#">Смотреть</a>
    </article>
    <article class="movie-card">
      <img src="https://picsum.photos/300/200?4" alt="Фильм 4" />
      <h3>Название фильма 4</h3>
      <p>Краткое описание фильма для примера карточки.</p>
      <a href="#">Смотреть</a>
    </article>
    <article class="movie-card">
      <img src="https://picsum.photos/300/200?5" alt="Фильм 5" />
      <h3>Название фильма 5</h3>
      <p>Краткое описание фильма для примера карточки.</p>
      <a href="#">Смотреть</a>
    </article>
    <article class="movie-card">
      <img src="https://picsum.photos/300/200?6" alt="Фильм 6" />
      <h3>Название фильма 6</h3>
      <p>Краткое описание фильма для примера карточки.</p>
      <a href="#">Смотреть</a>
    </article>
  </main>

  <footer class="footer">
    <div>Связаться с нами: info@moviegrid.ru</div>
    <div>© 2025 MovieGrid</div>
  </footer>
</body>
</html>
* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: Arial, sans-serif;
}

.page {
  display: grid;
  grid-template-columns: 220px 1fr;
  grid-template-rows: auto 1fr auto;
  grid-template-areas:
    "header header"
    "aside main"
    "footer footer";
  min-height: 100vh;
}

.header {
  grid-area: header;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 16px 24px;
  background: #2d3436;
  color: white;
}

.header__nav {
  display: flex;
  gap: 16px;
}

.header__nav a {
  color: white;
  text-decoration: none;
}

.filters {
  grid-area: aside;
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 16px;
  background: #f5f5f5;
}

.movies {
  grid-area: main;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 16px;
  padding: 16px;
}

.movie-card {
  display: flex;
  flex-direction: column;
  background: white;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
  overflow: hidden;
}

.movie-card img {
  width: 100%;
  height: 140px;
  object-fit: cover;
}

.movie-card h3,
.movie-card p {
  padding: 0 12px;
}

.movie-card a {
  margin: 12px;
  padding: 8px 12px;
  background: #2563eb;
  color: white;
  text-decoration: none;
  border-radius: 6px;
  text-align: center;
}

.footer {
  grid-area: footer;
  display: flex;
  justify-content: space-between;
  padding: 16px 24px;
  background: #333;
  color: white;
}

Поле поиска в header

Добавляем form search в header между .logo и .nav:

<header class="header">
  <div class="logo">MovieGrid</div>


  <form class="search" role="search">
    <label for="site-search" class="visually-hidden">Поиск по сайту</label>
    <input
      type="search"
      id="site-search"
      class="search__input"
      placeholder="Найти фильм..."
      aria-label="Поиск по сайту"
    />
  </form>


  <nav class="nav">
    <a href="#">Главная</a>
    <a href="#">Фильмы</a>
    <a href="#">Контакты</a>
  </nav>
</header>

Добавляем стилизацию для search__input (сразу после .header):

.search__input {
  width: 100%;
  max-width: 280px;
  padding: 8px 14px;
  border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: 20px;
  background: rgba(255, 255, 255, 0.1);
  color: white;
  font-size: 14px;
  outline: none;
  transition: border-color 0.15s ease, background 0.15s ease;
}

.search__input::placeholder {
  color: rgba(255, 255, 255, 0.6);
}

.search__input:focus-visible {
  border-color: #ffffff;
  background: rgba(255, 255, 255, 0.2);
}

/* Класс для визуально скрытых, но доступных экранным дикторам подписей */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

Разбор .visually-hidden: здесь label физически присутствует в разметке — это важно для доступности и для связи for/id — но не занимает видимого места на странице. В отличие от display: none, такой приём не убирает элемент из "дерева доступности", и экранный диктор по-прежнему озвучит подпись при переходе к полю.

Фильтры в aside

Теперь нужно заменить старую разметку aside на новую:

<aside class="filters">
  <h3>Категория</h3>

  <label class="filter-option">
    <input type="checkbox" name="category" value="films" checked />
    Фильмы
  </label>

  <label class="filter-option">
    <input type="checkbox" name="category" value="series" />
    Сериалы
  </label>

  <label class="filter-option">
    <input type="checkbox" name="category" value="cartoons" />
    Мультики
  </label>
</aside>

И так же заменить стилизацию к ней:

.filters {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.filter-option {
  display: flex;
  align-items: center;
  gap: 10px;
  cursor: pointer;
  padding: 6px 0;
}

.filter-option input[type="checkbox"] {
  width: 18px;
  height: 18px;
  accent-color: #2d3436;
  cursor: pointer;
}

Обратите внимание, что весь текст пункта фильтра обёрнут внутрь <label> вместе с самим input — благодаря этому кликабельна вся строка "Фильмы", а не только маленький квадратик чекбокса. Это одновременно и вопрос удобства, и вопрос доступности (увеличенная область нажатия).


Подводные камни

outline: none без визуальной замены

/* Плохо: убрали фокус, ничего не поставили взамен */
input:focus {
  outline: none;
}
/* Хорошо: убрали стандартный outline, но заменили собственным явным стилем */
input:focus {
  outline: none;
  border-color: #2563eb;
  box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.15);
}

Без какой-либо замены пользователь, работающий с клавиатуры (или из-за двигательных ограничений вынужденный работать только ей), не может понять, в каком поле сейчас находится фокус.

Забытый box-sizing: border-box

/* Проблема: при width: 100% и padding поле вылезет за пределы родителя */
.field {
  width: 100%;
  padding: 12px;
  border: 1px solid #ccc;
}

Без глобального box-sizing: border-box (обычно задаётся один раз для всего проекта через * { box-sizing: border-box; }) итоговая ширина элемента равна width + padding + border, а не просто width. На узких мобильных экранах это часто проявляется как поле, которое "вылезает" за пределы формы или контейнера.

placeholder вместо label

<!-- Проблема: единственная подсказка исчезает при вводе текста -->
<input type="text" placeholder="Ваше имя" />

Как только пользователь начинает печатать, единственный источник информации о том, что за поле перед ним, исчезает. Всегда должен быть либо видимый <label>, либо (если по дизайну подпись не нужна визуально) aria-label.

:invalid без учёта состояния "поле ещё не тронуто"

/* Проблема: поле помечается красным сразу при загрузке страницы */
input:invalid {
  border-color: red;
}

Разобрано подробно в разделе 4 — решение — комбинация :invalid:not(:placeholder-shown).

Слишком маленькая область нажатия у чекбоксов/радиокнопок на мобильных

Стандартный размер чекбокса браузера (обычно около 13–16px) неудобен для нажатия пальцем. Решение — увеличить сам элемент через width/height и, что ещё важнее, оборачивать текст пункта в <label>, чтобы кликабельной была вся строка, а не только маленький квадратик.


Итоги урока

Тип input — это не декоративный атрибут, а часть логики поля: он определяет встроенную валидацию браузера и тип клавиатуры на мобильных устройствах. Базовая стилизация текстовых полей требует внимания к box-sizing, чтобы padding не ломал ширину поля, заданную в процентах.

:focus и более современный :focus-visible дают визуальную обратную связь о том, где сейчас находится фокус — убирать стандартный outline можно только вместе с добавлением собственной, не менее заметной замены. Псевдоклассы :valid/:invalid отражают встроенную HTML5-валидацию (required, pattern, type="email" и другие), а связка :invalid:not(:placeholder-shown) решает проблему преждевременной подсветки ошибок ещё до взаимодействия пользователя с полем.

Для чекбоксов и радиокнопок самый практичный способ подкрасить внешний вид без потери нативного поведения — свойство accent-color; более глубокая кастомизация через appearance: none возможна, но требует большего внимания к деталям и не входит в базовый набор этого курса.

Доступность держится на нескольких простых правилах: настоящий <label> (или aria-label, если подпись не видна визуально), сохранённый видимый фокус, и достаточная область нажатия для мобильных устройств.

В следующем уроке разберём стилизацию button — второй ключевой элемент управления, без которого не обходится ни одна форма.


Вопросы для проверки

  1. Почему выбор правильного значения атрибута type у input важнее, чем просто "чтобы поле выглядело иначе"?
  2. Почему outline: none при :focus считается опасной практикой и как её нужно применять правильно?
  3. В чём разница между :focus и :focus-visible?
  4. Почему поле с атрибутом required не стоит стилизовать через голое правило input:invalid { border-color: red; }?
  5. Почему placeholder не может полностью заменить <label>?
  6. Чем accent-color удобнее, чем appearance: none для стилизации чекбоксов, и когда стоит выбрать второй вариант?

Практические задания

Задание 1. Текстовое поле с полным набором состояний

Создайте текстовое поле логина (type="text" или type="email") с оформлением для всех ключевых состояний:

  1. Базовое состояние: рамка, отступы, скруглённые углы, placeholder приглушённого серого цвета.
  2. :focus-visible — изменение цвета рамки и добавление мягкой тени (box-shadow), без использования голого outline: none без замены.
  3. :disabled — серый фон, курсор not-allowed.
  4. required, с корректной обработкой :invalid через связку с :placeholder-shown, чтобы ошибка не показывалась сразу при загрузке страницы.
  5. Добавьте настоящий <label>, связанный через for/id.

Задание 2. Стилизованные чекбоксы фильтра

Создайте блок фильтра с тремя чекбоксами ("Фильмы", "Сериалы", "Мультики"):

  1. Используйте accent-color, чтобы подкрасить чекбоксы под общую цветовую схему.
  2. Каждый пункт — это <label>, оборачивающий и input, и текст, чтобы кликабельной была вся строка.
  3. Увеличьте область нажатия каждого пункта минимум до 40px по высоте.
  4. При наведении на пункт (:hover) — лёгкая подсветка фона всей строки.

Задание 3. Объединяющая практика — поиск и фильтры на странице фильмов

Возьмите страницу фильмов (Уроки 11–12) и добавьте на неё:

  1. Поле поиска в header, стилизованное под тёмный фон шапки (полупрозрачная светлая рамка и фон, светлый текст), с aria-label, так как видимого <label> в дизайне шапки нет.
  2. Блок фильтров в aside из Задания 2.
  3. Убедитесь, что оба элемента корректно проходят проверку клавиатурой: переход по Tab должен явно показывать, какой элемент сейчас в фокусе, без единого пропущенного outline/замены.
  4. Проверьте, что при уменьшении ширины экрана (используя медиа-запросы из Урока 12) поле поиска и фильтры не теряют читаемость и не вылезают за пределы контейнеров.

Предыдущий урок | Следующий урок