HTML & CSS

CSS для форм та інтерактивних станів

Повне керівництво зі стилізації HTML-форм: псевдокласи :focus-visible, :user-valid, :has(), accent-color, кастомні checkbox/radio/select, floating label патерн, валідація без JavaScript та нативний CSS-only дизайн форм.

CSS для форм та інтерактивних станів

Форми — найстресовіша частина верстки

«Зробіть форму гарною» — це речення, яке розробники та дизайнери трактують діаметрально протилежно. Дизайнер бачить чисті, мінімалістичні поля з плавними переходами. Розробник знає правду: браузери мають глибоко вкорінені стилі форм, які активно опираються CSS. Checkbox на macOS виглядає інакше, ніж на Windows. <select> майже неможливо стилізувати. <input type="range"> — окремий кошмар.

До 2021 року єдиним шляхом була повна заміна нативних елементів на div-обгортки з JavaScript. Тепер CSS надає справжні нативні інструменти: accent-color, appearance, field-sizing, псевдокласи :user-valid/:user-invalid, потужний :has() для реактивних форм.

Розберемо все по порядку — від базових станів до просунутих патернів.

Loading diagram...
graph TD
    A["HTML Form Element"] --> B["Базові стани\n:focus :hover :disabled"]
    A --> C["Валідаційні стани\n:valid :invalid :user-valid"]
    A --> D["Стани вмісту\n:checked :placeholder-shown\n:read-only :required"]
    A --> E[":has() — реакція батька\nна стан дочірнього"]
    B --> F["Кастомний дизайн\nappearance: none\naccent-color"]
    C --> G["CSS-only валідація\nбез JavaScript"]
    style A fill:#6366f1,color:#fff
    style F fill:#10b981,color:#fff
    style G fill:#ec4899,color:#fff

Скидання стилів браузера: appearance і accent-color

appearance: none — чистий аркуш

Кожен браузер має власний User Agent Stylesheet — набір дефолтних стилів для всіх HTML-елементів. Для форм ці стилі особливо агресивні та важко перевизначаються. appearance: none каже браузеру: «анулюй всі свої нативні стилі для цього елемента».

/* Скидання для кнопок */
button {
  appearance: none;
  border: none;
  background: none;
  cursor: pointer;
  font: inherit;
}

/* Скидання для inputs */
input, textarea, select {
  appearance: none;
  border: none;
  outline: none; /* потім додамо власний outline! */
  font: inherit;
}
Після appearance: none не забудьте додати власний:focus-visible стиль. Видалення дефолтного outline без заміни — порушення доступності. Людям, що навігують клавіатурою, потрібен видимий індикатор фокусу.

accent-color — найпростіший спосіб стилізувати форми

accent-color — однорядкова властивість, яка змінює колір нативних форм-елементів: checkbox, radio, range, progress. Не ідеально, але буквально одна рядок замінює сотні рядків CSS з appearance: none.

🔒localhost:3000

Псевдокласи стану форм

CSS надає багатий набір псевдокласів, що реагують на стан форм-елементів без жодного JavaScript.

Базові стани: :focus, :focus-visible, :focus-within

ПсевдокласКоли активний
:focusБудь-який фокус (клавіатура і миша)
:focus-visibleЛише коли фокус «видимий» (зазвичай — тільки клавіатура)
:focus-withinЯкщо будь-який нащадок має фокус

:focus-visible — ключовий для доступності: він показує outline при навігації клавіатурою, але не при кліку мишею (що часто дратує).

🔒localhost:3000

Стани вмісту: :checked, :placeholder-shown, :read-only

🔒localhost:3000

Валідаційні псевдокласи: :valid, :invalid, :user-valid, :user-invalid

Ключова різниця: :invalid спрацьовує одразу при завантаженні сторінки, навіть до того, як користувач щось ввів. :user-invalid — лише після першої взаємодії. Це critical UX-деталь.

🔒localhost:3000

:has() для розумних форм

:has() відкриває нову еру реактивних форм без JavaScript. Батьківський елемент може реагувати на стан дочірнього.

🔒localhost:3000

Кастомний Checkbox та Radio

appearance: none + ::before/::after + :checked — класична техніка для повністю кастомних форм-елементів.

🔒localhost:3000

Кастомний Select

<select> — один із найскладніших елементів для стилізації. appearance: none прибирає нативну стрілку, дозволяючи додати кастомну через background-image.

🔒localhost:3000

Floating Label патерн (CSS-only)

Floating Label — поле введення, де заповнювач «зависає» над полем при введенні тексту. Класичний патерн Material Design, реалізований лише на CSS через :placeholder-shown та :not(:placeholder-shown).

🔒localhost:3000

Кастомний Range Slider

🔒localhost:3000

Практика

Рівень 1 — Базовий: стилізація стану форм

Надано HTML форми реєстрації. Додайте CSS, що:

  • Підсвічує поля зеленим при :user-valid та червоним при :user-invalid
  • Показує та іконки через ::after на обгортці поля залежно від стану
  • Прибирає нативний outline та додає власний box-shadow при :focus-visible

Рівень 2 — Логіка/Інтерактивність: реактивна форма через ()

Реалізуйте форму замовлення із smart-поведінкою лише на CSS:

  • Якщо вибрано <input type="radio" value="delivery"> — показати блок з адресою доставки (:has(:checked[value="delivery"]))
  • Якщо всі обов'язкові поля валідні — кнопка «Оформити» стає яскравою (:has(input:required:valid))
  • Label підсвічується рожевим при :user-invalid вкладеного input

Рівень 3 — Архітектура: Design System форм

Створіть повну систему стилів форм:

  1. Токени: --input-border, --input-focus-ring, --input-error, --input-success через CSS Custom Properties
  2. Компонент .field: floating label + error message + success state через Nesting
  3. Варіанти: --outlined (border), --filled (background), --underlined (лише нижній border)
  4. Теми: dark mode через @media (prefers-color-scheme: dark) що перевизначає токени
  5. Весь CSS — через @layer components + @property для анімованих токенів

Підсумок

🎨 accent-color

Одна властивість для нативного кольору checkbox, radio, range, progress. Мінімум CSS — максимум результату.

👁️ :focus-visible

Показує outline лише при навігації клавіатурою. Використовуйте замість :focus для кращого UX та доступності.

✅ :user-valid / :user-invalid

Валідація лише після взаємодії — без стресу від червоних полів одразу при завантаженні. Chrome 119+, Safari 16.5+, FF 88+.

🧠 :has() для форм

:has(input:focus) на label, :has(:checked) для умовного показу блоків, :has(:user-invalid) для підсвітки всієї форми — без JavaScript.

🔘 appearance: none

Скидає нативні стилі. Відправна точка для кастомних checkbox, radio, select. Завжди додавайте :focus-visible після скидання!

🏷️ Floating Label

:placeholder-shown + :not(:placeholder-shown) — чистий CSS-патерн без JavaScript. Placeholder повинен бути пробілом " " для коректної роботи.