Форми в HTML
Форми в HTML
Як форми з'єднують людей із сервером
2004 рік. Марк Цукерберг запускає Facebook. Перше, що бачить новий користувач — форма реєстрації: ім'я, прізвище, пошта, пароль. Чотири поля і кнопка «Sign Up». Саме ця форма, написана на HTML, стала точкою входу для мільярдів людей.
Форми — єдиний нативний механізм HTML для двостороннього обміну даними між користувачем і сервером. Без форм веб залишився б лише читанкою: ви б не могли ні замовити піцу, ні написати коментар, ні авторизуватися.
За специфікацією HTML Living Standard (WHATWG), форми — це складна екосистема взаємопов'язаних елементів. Розберемо кожен із них.
Елемент <form> — контейнер форми
<form> (form — форма) представляє гіпертекстову форму з полями, які користувач може заповнити і відправити.
За специфікацією WHATWG:
- Content model: flow content, але без вкладеного
<form> - Permitted parents: будь-який елемент, що приймає flow content
Атрибути <form>
get або post. За замовчуванням get.method="post". Значення: application/x-www-form-urlencoded (за замовч.), multipart/form-data (для файлів), text/plain (для debug).on або off — дозволяти браузеру автодоповнення полів. За замовч. on._self, _blank, _parent, _top або назва фрейму.document.forms. Не впливає на відправку.GET vs POST — ключова різниця
<!-- Дані передаються в URL: /search?q=html+форми&lang=uk -->
<form action="/search" method="get">
<label for="q">Пошук:</label>
<input type="text" id="q" name="q" value="html форми" />
<label for="lang">Мова:</label>
<select id="lang" name="lang">
<option value="uk">Українська</option>
<option value="en">English</option>
</select>
<button type="submit">Знайти</button>
</form>
Коли GET:
- Пошукові запити
- Фільтрація та сортування
- Будь-що, що можна закладати в браузер або ділитися лінком
- Ідемпотентні операції (повторний запит = той самий результат)
<!-- Дані передаються в тілі запиту, URL не змінюється -->
<form action="/login" method="post">
<label for="email">Email:</label>
<input type="email" id="email" name="email" required />
<label for="password">Пароль:</label>
<input type="password" id="password" name="password" required />
<button type="submit">Увійти</button>
</form>
Коли POST:
- Авторизація, реєстрація
- Створення або зміна даних
- Відправка файлів
- Будь-що, що не має бути видимим в URL або кешованим
<!-- Для завантаження файлів ОБОВ'ЯЗКОВО enctype="multipart/form-data" -->
<form action="/upload" method="post" enctype="multipart/form-data">
<label for="avatar">Аватар:</label>
<input type="file" id="avatar" name="avatar" accept="image/jpeg,image/png,image/webp" />
<label for="resume">Резюме (PDF):</label>
<input type="file" id="resume" name="resume" accept=".pdf" />
<button type="submit">Завантажити</button>
</form>
Елемент <label> — підпис поля
<label> (label — підпис) пов'язує текстовий опис з елементом керування форми. Це критично для доступності: скринрідер оголошує підпис при фокусуванні на полі.
Два способи зв'язку
<!-- Спосіб 1: явне зв'язування через for/id (рекомендований) -->
<label for="username">Ім'я користувача</label>
<input type="text" id="username" name="username" />
<!-- Спосіб 2: неявне — <input> всередині <label> -->
<label>
Ім'я користувача
<input type="text" name="username" />
</label>
for/id) — кращий: він дозволяє більшу гнучкість у верстці, бо <label> і <input> не зобов'язані бути поруч у DOM.Клік на <label> фокусує поле
Це важлива UX-перевага: клікаючи на текст підпису, користувач переходить у поле. Особливо критично для чекбоксів та радіокнопок — їхні мітки є дуже маленькими за замовчуванням:
<!-- ✅ Клік на "Погоджуюсь з умовами" активує checkbox -->
<input type="checkbox" id="agree" name="agree" />
<label for="agree">Погоджуюсь з умовами використання</label>
<!-- ❌ Немає label — користувач мусить влучити точно в checkbox -->
<input type="checkbox" name="agree" />
Погоджуюсь з умовами використання
Елемент <input> — поле вводу
<input> (input — ввід) — void element, найбільш багатофункціональний елемент форми. Атрибут type визначає, яким є поле.
Типи <input>
Текстові поля
<!-- Базовий текстовий ввід -->
<label for="name">Повне ім'я</label>
<input type="text" id="name" name="name" placeholder="Іван Петренко" autocomplete="name" maxlength="100" required />
Базовий однорядковий текстовий ввід. Найуніверсальніший тип.
<!-- Браузер валідує формат email -->
<label for="email">Email-адреса</label>
<input type="email" id="email" name="email" placeholder="ivan@example.com" autocomplete="email" required />
На мобільних пристроях відображає клавіатуру з символами @ та .. Браузер автоматично перевіряє базовий формат.
<!-- Символи приховані (•••) -->
<label for="password">Пароль</label>
<input type="password" id="password" name="password" autocomplete="new-password" minlength="8" required />
<!-- Підтвердження пароля -->
<label for="confirm-password">Підтвердіть пароль</label>
<input
type="password"
id="confirm-password"
name="confirm_password"
autocomplete="new-password"
minlength="8"
required
/>
Символи замінюються на •. autocomplete="new-password" — підказка браузеру запропонувати генератор паролів.
<!-- tel: мобільна клавіатура з цифрами -->
<label for="phone">Телефон</label>
<input
type="tel"
id="phone"
name="phone"
placeholder="+38 (067) 123-45-67"
autocomplete="tel"
pattern="[\+]?[0-9\s\-\(\)]{10,20}"
/>
<!-- url: клавіатура зі слешем та .com -->
<label for="website">Сайт</label>
<input type="url" id="website" name="website" placeholder="https://example.com" autocomplete="url" />
<!-- search: стилізується браузером (хрестик очищення) -->
<label for="search">Пошук</label>
<input type="search" id="search" name="q" placeholder="Введіть запит..." autocomplete="off" />
Числові та діапазон
<!-- number: стрілки вгору/вниз, валідація числа -->
<label for="quantity">Кількість (1–99)</label>
<input type="number" id="quantity" name="quantity" min="1" max="99" step="1" value="1" />
<!-- range: слайдер -->
<label for="volume">Гучність: <output id="vol-output">50</output>%</label>
<input type="range" id="volume" name="volume" min="0" max="100" step="5" value="50" />
Дата та час
<!-- date: вибір дати (рік-місяць-день) -->
<label for="birthday">Дата народження</label>
<input type="date" id="birthday" name="birthday" min="1900-01-01" max="2010-12-31" />
<!-- time: вибір часу -->
<label for="appointment">Час запису</label>
<input type="time" id="appointment" name="appointment" min="09:00" max="18:00" step="1800" />
<!-- datetime-local: дата + час без часового поясу -->
<label for="event-start">Початок події</label>
<input type="datetime-local" id="event-start" name="event_start" min="2024-01-01T00:00" />
<!-- month: місяць та рік -->
<label for="exp-date">Термін дії картки</label>
<input type="month" id="exp-date" name="exp_date" />
<!-- week: номер тижня -->
<label for="delivery-week">Тиждень доставки</label>
<input type="week" id="delivery-week" name="delivery_week" />
Checkbox та Radio
<!-- CHECKBOX: незалежний вибір (можна вибрати кілька) -->
<fieldset>
<legend>Улюблені мови програмування</legend>
<input type="checkbox" id="lang-js" name="languages" value="javascript" />
<label for="lang-js">JavaScript</label>
<input type="checkbox" id="lang-ts" name="languages" value="typescript" />
<label for="lang-ts">TypeScript</label>
<input type="checkbox" id="lang-py" name="languages" value="python" checked />
<label for="lang-py">Python</label>
<input type="checkbox" id="lang-go" name="languages" value="go" />
<label for="lang-go">Go</label>
</fieldset>
<!-- RADIO: взаємовиключний вибір (тільки один з групи) -->
<!-- Група radio визначається однаковим name -->
<fieldset>
<legend>Рівень кваліфікації</legend>
<input type="radio" id="level-junior" name="level" value="junior" />
<label for="level-junior">Junior</label>
<input type="radio" id="level-mid" name="level" value="mid" checked />
<label for="level-mid">Middle</label>
<input type="radio" id="level-senior" name="level" value="senior" />
<label for="level-senior">Senior</label>
</fieldset>
fieldset {
border: 1px solid #e2e8f0;
border-radius: 8px;
padding: 1rem 1.5rem;
margin-bottom: 1rem;
}
legend {
font-weight: 600;
padding: 0 0.5rem;
color: #1e293b;
}
input[type='checkbox'],
input[type='radio'] {
margin-right: 0.4rem;
accent-color: #3b82f6;
}
label {
margin-right: 1rem;
cursor: pointer;
}
name у <input type="radio"> — це те, що робить їх групою. Браузер автоматично знімає вибір попередньої кнопки при виборі нової.Колір та файл
<!-- color: колірний пікер -->
<label for="brand-color">Колір бренду</label>
<input type="color" id="brand-color" name="brand_color" value="#3b82f6" />
<!-- file: вибір файлів -->
<label for="documents">Документи</label>
<input type="file" id="documents" name="documents" accept=".pdf,.doc,.docx" multiple />
<!-- multiple: дозволяє вибрати кілька файлів -->
<label for="photos">Фотографії (до 5 штук)</label>
<input type="file" id="photos" name="photos" accept="image/*" multiple />
Приховані та кнопкові
<!-- hidden: дані, які відправляються разом з формою, але невидимі -->
<input type="hidden" name="csrf_token" value="abc123xyz" />
<input type="hidden" name="product_id" value="42" />
<!-- submit: кнопка відправки -->
<input type="submit" value="Зареєструватися" />
<!-- reset: скидання всіх полів до значень за замовч. -->
<input type="reset" value="Очистити форму" />
<!-- button: кнопка без дефолтної дії -->
<input type="button" value="Перевірити" id="check-btn" />
<!-- image: кнопка-зображення (відправляє координати кліку) -->
<input type="image" src="/images/submit-arrow.png" alt="Відправити" width="40" height="40" />
Спільні атрибути <input>
| Атрибут | Тип | Опис |
|---|---|---|
name | string | Ім'я поля — ключ у парі ключ=значення при відправці |
id | string | Унікальний ідентифікатор для <label for> |
value | string | Початкове значення (або відправлюване для checkbox/radio/hidden) |
placeholder | string | Підказка всередині поля (зникає при введенні) |
required | boolean | Поле обов'язкове для заповнення |
disabled | boolean | Поле заблоковане, не відправляється з формою |
readonly | boolean | Поле лише для читання, але відправляється з формою |
autofocus | boolean | Автоматичний фокус при завантаженні сторінки |
autocomplete | string | Підказка браузеру для автодоповнення (значень дуже багато) |
min / max | varies | Мінімальне/максимальне значення для number, date, range |
minlength / maxlength | number | Мінімальна/максимальна довжина тексту |
step | number/string | Крок для number, range, date, time |
pattern | string | Регулярний вираз для валідації значення |
multiple | boolean | Дозволяє кілька значень (email, file) |
accept | string | Дозволені типи файлів для type="file" |
Елемент <textarea> — багаторядковий текст
<textarea> (text area — текстова область) — поле для багаторядкового тексту. На відміну від <input>, має закриваючий тег і може містити текст за замовчуванням:
<label for="message">Ваше повідомлення</label>
<textarea
id="message"
name="message"
rows="6"
cols="40"
placeholder="Опишіть ваш запит детально..."
maxlength="1000"
required
>
Тут може бути початковий текст</textarea
>
2. Рекомендовано вказувати явно.20. На практиці краще контролювати через CSS.soft (за замовч.) — перенос лише для відображення; hard — перенос вставляється в значення при відправці.resize: both, horizontal, vertical, none.Елемент <select> — випадаючий список
<label for="country">Країна</label>
<select id="country" name="country" required>
<!-- Перший варіант — підказка (не реальний вибір) -->
<option value="" disabled selected>Оберіть країну</option>
<!-- optgroup для групування -->
<optgroup label="Східна Європа">
<option value="UA">🇺🇦 Україна</option>
<option value="PL">🇵🇱 Польща</option>
<option value="CZ">🇨🇿 Чехія</option>
</optgroup>
<optgroup label="Захід Європи">
<option value="DE">🇩🇪 Німеччина</option>
<option value="FR">🇫🇷 Франція</option>
<option value="NL">🇳🇱 Нідерланди</option>
</optgroup>
</select>
<!-- multiple: список з множинним вибором -->
<label for="skills">Ваші навички</label>
<select id="skills" name="skills" multiple size="5">
<option value="html">HTML</option>
<option value="css">CSS</option>
<option value="js" selected>JavaScript</option>
<option value="ts" selected>TypeScript</option>
<option value="react">React</option>
<option value="vue">Vue.js</option>
</select>
<option> (Ctrl+Click або Shift+Click).multiple рекомендовано вказувати.Елемент <button> — кнопка
<button> — більш потужна альтернатива <input type="submit">. На відміну від <input>, <button> може містити будь-який phrasing content — текст, зображення, іконки:
<!-- type="submit" — відправляє форму (за замовчуванням всередині <form>) -->
<button type="submit">
<img src="/icons/send.svg" alt="" width="16" height="16" />
Надіслати заявку
</button>
<!-- type="reset" — скидає всі поля до початкових значень -->
<button type="reset">Очистити</button>
<!-- type="button" — без дефолтної дії, лише для JS -->
<button type="button" id="preview-btn">Попередній перегляд</button>
<button> без атрибута type всередині <form> має тип submit за замовчуванням — це може призвести до ненавмисного відправлення форми при кліку. Завжди вказуйте type="button" для кнопок без функції відправки.<fieldset> та <legend> — групування полів
<fieldset> (field set — набір полів) групує пов'язані елементи форми. <legend> (legend — легенда) — підпис групи, перший дочірній елемент <fieldset>:
<form action="/register" method="post">
<fieldset>
<legend>Особисті дані</legend>
<label for="first-name">Ім'я</label>
<input type="text" id="first-name" name="first_name" required autocomplete="given-name" />
<label for="last-name">Прізвище</label>
<input type="text" id="last-name" name="last_name" required autocomplete="family-name" />
<label for="birth-date">Дата народження</label>
<input type="date" id="birth-date" name="birth_date" autocomplete="bday" />
</fieldset>
<fieldset>
<legend>Контактна інформація</legend>
<label for="reg-email">Email</label>
<input type="email" id="reg-email" name="email" required autocomplete="email" />
<label for="reg-phone">Телефон</label>
<input type="tel" id="reg-phone" name="phone" autocomplete="tel" />
</fieldset>
<fieldset>
<legend>Налаштування сповіщень</legend>
<input type="checkbox" id="notify-email" name="notify" value="email" checked />
<label for="notify-email">На Email</label>
<input type="checkbox" id="notify-sms" name="notify" value="sms" />
<label for="notify-sms">SMS</label>
<input type="checkbox" id="notify-push" name="notify" value="push" />
<label for="notify-push">Push-сповіщення</label>
</fieldset>
<!-- disabled fieldset — відключає всі поля всередині -->
<fieldset disabled>
<legend>Платіжна інформація (недоступно на цьому кроці)</legend>
<label for="card">Номер картки</label>
<input type="text" id="card" name="card_number" />
</fieldset>
<button type="submit">Зареєструватися</button>
</form>
<fieldset disabled> — елегантний спосіб заблокувати одразу всю групу полів. Браузер ігнорує всі елементи всередині при відправці форми.<datalist> — список підказок
<datalist> надає попередньо визначені варіанти для <input>, не обмежуючи введення користувача (на відміну від <select>):
<label for="framework">Фреймворк</label>
<input type="text" id="framework" name="framework" list="frameworks-list" placeholder="Введіть або оберіть..." />
<datalist id="frameworks-list">
<option value="React"></option>
<option value="Vue.js"></option>
<option value="Angular"></option>
<option value="Svelte"></option>
<option value="Solid.js"></option>
<option value="Nuxt.js"></option>
<option value="Next.js"></option>
</datalist>
<!-- datalist працює і для числового вводу -->
<label for="font-size">Розмір шрифту (px)</label>
<input type="number" id="font-size" name="font_size" list="font-sizes" min="8" max="72" />
<datalist id="font-sizes">
<option value="12"></option>
<option value="14"></option>
<option value="16"></option>
<option value="18"></option>
<option value="24"></option>
<option value="32"></option>
</datalist>
<output> — результат обчислення
<output> (output — результат) представляє результат обчислення. Семантично пов'язується з полями через атрибут for:
<form oninput="result.value = parseInt(a.value) + parseInt(b.value)">
<label for="a">Перше число:</label>
<input type="number" id="a" name="a" value="0" />
<label for="b">Друге число:</label>
<input type="number" id="b" name="b" value="0" />
<label for="result">Сума:</label>
<!-- for вказує на id полів, які є джерелом -->
<output id="result" name="result" for="a b">0</output>
</form>
<progress> та <meter> — прогрес і вимірювання
<!-- progress: прогрес виконання завдання -->
<label for="upload-progress">Завантаження файлу:</label>
<progress id="upload-progress" value="65" max="100">65%</progress>
<!-- Невизначений прогрес (без value) — анімований індикатор -->
<progress>Завантаження...</progress>
<!-- meter: вимірювання в межах діапазону -->
<!-- Семантика: низький/нормальний/високий рівень -->
<label for="disk-usage">Використання диску:</label>
<meter id="disk-usage" value="75" min="0" max="100" low="50" high="80" optimum="30">75%</meter>
<!-- value < low = добре (зелений), low..high = нормально (жовтий), >high = погано (червоний) -->
label {
display: block;
margin-bottom: 0.3rem;
font-weight: 500;
font-size: 0.9rem;
}
progress, meter {
width: 100%;
height: 1.25rem;
margin-bottom: 1.5rem;
}
Вбудована валідація HTML5
Браузери виконують базову валідацію до відправки форми. Атрибути, що беруть участь у валідації:
Атрибути валідації
<form action="/register" method="post">
<!-- required: поле не може бути порожнім -->
<input type="text" name="name" required />
<!-- minlength / maxlength: довжина тексту -->
<input type="text" name="username" minlength="3" maxlength="20" required />
<!-- min / max: числовий діапазон або дати -->
<input type="number" name="age" min="18" max="120" required />
<!-- pattern: регулярний вираз -->
<!-- \+? — опціональний +, [0-9] — цифри, {11,13} — від 11 до 13 символів -->
<input
type="tel"
name="phone"
pattern="\+?[0-9]{11,13}"
title="Номер телефону: 11-13 цифр, опціонально з +"
required
/>
<!-- type="email" автоматично перевіряє формат -->
<input type="email" name="email" required />
<!-- type="url" перевіряє формат URL -->
<input type="url" name="website" />
<button type="submit">Відправити</button>
</form>
novalidate — відключення валідації
<!-- Відключає браузерну валідацію (для власної JS-валідації) -->
<form action="/submit" method="post" novalidate>
<input type="email" name="email" required />
<button type="submit">Відправити</button>
</form>
CSS-псевдокласи валідації
<form novalidate>
<label for="demo-email">Email (спробуйте ввести неправильний):</label>
<input type="email" id="demo-email" value="not-an-email" required />
<label for="demo-name">Ім'я (обов'язкове, порожнє):</label>
<input type="text" id="demo-name" required />
<label for="demo-age">Вік (18–120):</label>
<input type="number" id="demo-age" value="25" min="18" max="120" />
</form>
form {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
label {
font-size: 0.875rem;
font-weight: 500;
color: #374151;
}
input {
display: block;
width: 100%;
padding: 0.5rem 0.75rem;
border: 1px solid #d1d5db;
border-radius: 6px;
font-size: 1rem;
outline: none;
transition: border-color 0.2s;
}
/* Поле валідне (заповнено правильно) */
input:valid {
border-color: #10b981;
background: #f0fdf4;
}
/* Поле невалідне */
input:invalid {
border-color: #ef4444;
background: #fef2f2;
}
/* Обов'язкове поле */
input:required {
border-left: 3px solid #f59e0b;
}
/* Поле в межах min/max */
input:in-range {
border-color: #10b981;
}
/* Поле поза межами min/max */
input:out-of-range {
border-color: #ef4444;
background: #fef2f2;
}
required через DevTools або надіслати запит напряму, оминаючи форму.Autocomplete — підказки браузеру
Атрибут autocomplete стандартизований: правильні значення дозволяють браузеру та менеджерам паролів точно знати, яке поле для чого:
<form autocomplete="on">
<!-- Особисті дані -->
<input autocomplete="name" name="name" />
<!-- Повне ім'я -->
<input autocomplete="given-name" name="first" />
<!-- Ім'я -->
<input autocomplete="family-name" name="last" />
<!-- Прізвище -->
<input autocomplete="email" name="email" />
<!-- Email -->
<input autocomplete="tel" name="phone" />
<!-- Телефон -->
<!-- Адреса -->
<input autocomplete="street-address" name="street" />
<input autocomplete="postal-code" name="zip" />
<input autocomplete="country-name" name="country" />
<!-- Платіж -->
<input autocomplete="cc-number" name="card" />
<!-- Номер картки -->
<input autocomplete="cc-exp" name="expiry" />
<!-- Термін -->
<input autocomplete="cc-csc" name="cvv" />
<!-- CVV -->
<!-- Авторизація -->
<input autocomplete="username" name="login" />
<input autocomplete="current-password" name="password" />
<input autocomplete="new-password" name="new_pass" />
</form>
Практичні завдання
Створіть сторінку з двома формами:
- Форма пошуку (
method="get"):<input type="search">зplaceholder<select>для фільтра категорій (5 опцій з<optgroup>)<button type="submit">
- Форма підписки на розсилку (
method="post"):<input type="email">зrequired,autocomplete="email",placeholder<input type="checkbox">— згода на отримання листів з<label><button type="submit">та<button type="reset">
Реалізуйте повну форму реєстрації з групуванням через <fieldset>:
- Особисті дані: ім'я, прізвище (
autocomplete), дата народження (type="date") - Контакти: email (
required), телефон (pattern), сайт (type="url") - Акаунт: нікнейм (
minlength="3",maxlength="20",pattern="[a-zA-Z0-9_]+"), пароль × 2 (type="password",autocomplete="new-password") - Налаштування:
<select>мови з<optgroup>,<datalist>для міста,<input type="range">для сповіщень <button type="submit">та<button type="reset">
Реалізуйте HTML-структуру опитувальника зі складними елементами:
- Крок 1: Особисті дані (fieldset) — прихований
<input type="hidden" name="step" value="1"> - Крок 2: Досвід роботи — кілька груп
<input type="radio" name="experience">, checkbox для технологій - Крок 3: Вільна відповідь —
<textarea>зmaxlength, лічильник символів через<output>,<meter>для показу заповненості - Підсумок:
<fieldset disabled>для перегляду всіх введених даних (поля readonly) - Всі поля мають коректні
autocomplete,required,name,id, зв'язані<label>
Комплексний приклад від А до Я
Тема: Форма заявки на вакансію — Full Stack Developer у стартапі TechUA.
<!DOCTYPE html>
<html lang="uk">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="Залишити заявку на вакансію Full Stack Developer у компанії TechUA." />
<title>Заявка на вакансію — TechUA</title>
</head>
<body>
<header>
<h1>TechUA — Заявка на вакансію</h1>
<nav aria-label="Кроки заявки">
<ol>
<li><a href="#personal">Особисті дані</a></li>
<li><a href="#experience">Досвід</a></li>
<li><a href="#skills">Навички</a></li>
<li><a href="#documents">Документи</a></li>
</ol>
</nav>
</header>
<main>
<form action="/careers/apply" method="post" enctype="multipart/form-data" novalidate id="job-application">
<!-- CSRF-захист (прихований токен) -->
<input type="hidden" name="csrf_token" value="a1b2c3d4e5f6" />
<input type="hidden" name="vacancy_id" value="fsd-2024-11" />
<!-- ===== КРОК 1: ОСОБИСТІ ДАНІ ===== -->
<fieldset id="personal">
<legend>Крок 1: Особисті дані</legend>
<div>
<label for="first-name">Ім'я <abbr title="обов'язкове">*</abbr></label>
<input
type="text"
id="first-name"
name="first_name"
autocomplete="given-name"
required
minlength="2"
maxlength="50"
placeholder="Іван"
/>
</div>
<div>
<label for="last-name">Прізвище <abbr title="обов'язкове">*</abbr></label>
<input
type="text"
id="last-name"
name="last_name"
autocomplete="family-name"
required
minlength="2"
maxlength="50"
placeholder="Коваленко"
/>
</div>
<div>
<label for="email">Email <abbr title="обов'язкове">*</abbr></label>
<input
type="email"
id="email"
name="email"
autocomplete="email"
required
placeholder="ivan@example.com"
/>
</div>
<div>
<label for="phone">Телефон</label>
<input
type="tel"
id="phone"
name="phone"
autocomplete="tel"
pattern="\+380[0-9]{9}"
title="Формат: +380XXXXXXXXX"
placeholder="+380671234567"
/>
</div>
<div>
<label for="city">Місто</label>
<input
type="text"
id="city"
name="city"
list="ua-cities"
autocomplete="address-level2"
placeholder="Оберіть або введіть місто"
/>
<datalist id="ua-cities">
<option value="Київ"></option>
<option value="Харків"></option>
<option value="Одеса"></option>
<option value="Дніпро"></option>
<option value="Запоріжжя"></option>
<option value="Львів"></option>
</datalist>
</div>
<div>
<label for="website">Ваш GitHub / LinkedIn / Portfolio</label>
<input
type="url"
id="website"
name="website"
autocomplete="url"
placeholder="https://github.com/username"
/>
</div>
</fieldset>
<!-- ===== КРОК 2: ДОСВІД ===== -->
<fieldset id="experience">
<legend>Крок 2: Досвід роботи</legend>
<!-- Radio group: рівень -->
<fieldset>
<legend>Рівень <abbr title="обов'язкове">*</abbr></legend>
<input type="radio" id="level-junior" name="level" value="junior" />
<label for="level-junior">Junior (до 1 року)</label>
<input type="radio" id="level-middle" name="level" value="middle" />
<label for="level-middle">Middle (1–3 роки)</label>
<input type="radio" id="level-senior" name="level" value="senior" checked />
<label for="level-senior">Senior (3+ роки)</label>
<input type="radio" id="level-lead" name="level" value="lead" />
<label for="level-lead">Tech Lead / Principal</label>
</fieldset>
<div>
<label for="years-exp">Роки досвіду в розробці</label>
<input
type="number"
id="years-exp"
name="years_experience"
min="0"
max="40"
step="1"
value="0"
/>
</div>
<div>
<label for="salary">Очікувана зарплата (USD/місяць)</label>
<input
type="range"
id="salary"
name="expected_salary"
min="500"
max="10000"
step="500"
value="3000"
/>
<output for="salary" id="salary-output">3000</output> USD
</div>
<div>
<label for="availability">Дата готовності до початку роботи</label>
<input type="date" id="availability" name="available_from" min="2024-12-01" />
</div>
<div>
<label for="work-format">Формат роботи</label>
<select id="work-format" name="work_format" required>
<option value="" disabled selected>Оберіть формат</option>
<optgroup label="Дистанційно">
<option value="remote-full">Повністю дистанційно</option>
<option value="remote-partial">Гібрид (1–2 дні в офісі)</option>
</optgroup>
<optgroup label="В офісі">
<option value="office-kyiv">Офіс Київ</option>
<option value="office-lviv">Офіс Львів</option>
</optgroup>
</select>
</div>
</fieldset>
<!-- ===== КРОК 3: НАВИЧКИ ===== -->
<fieldset id="skills">
<legend>Крок 3: Технічні навички</legend>
<!-- Checkbox group: технології -->
<fieldset>
<legend>Frontend (оберіть всі, якими володієте)</legend>
<input type="checkbox" id="skill-html" name="skills" value="html" checked />
<label for="skill-html">HTML</label>
<input type="checkbox" id="skill-css" name="skills" value="css" checked />
<label for="skill-css">CSS</label>
<input type="checkbox" id="skill-js" name="skills" value="javascript" checked />
<label for="skill-js">JavaScript</label>
<input type="checkbox" id="skill-ts" name="skills" value="typescript" />
<label for="skill-ts">TypeScript</label>
<input type="checkbox" id="skill-react" name="skills" value="react" />
<label for="skill-react">React</label>
<input type="checkbox" id="skill-vue" name="skills" value="vue" />
<label for="skill-vue">Vue.js</label>
</fieldset>
<fieldset>
<legend>Backend</legend>
<input type="checkbox" id="skill-node" name="skills" value="nodejs" />
<label for="skill-node">Node.js</label>
<input type="checkbox" id="skill-python" name="skills" value="python" />
<label for="skill-python">Python</label>
<input type="checkbox" id="skill-go" name="skills" value="go" />
<label for="skill-go">Go</label>
</fieldset>
<div>
<label for="cover-letter">
Супровідний лист
<small>(розкажіть чому хочете until to TechUA)</small>
</label>
<textarea
id="cover-letter"
name="cover_letter"
rows="8"
maxlength="3000"
placeholder="Чому мене зацікавила ця позиція..."
></textarea>
<!-- output показує кількість символів -->
<output for="cover-letter">0</output> / 3000 символів
</div>
</fieldset>
<!-- ===== КРОК 4: ДОКУМЕНТИ ===== -->
<fieldset id="documents">
<legend>Крок 4: Документи</legend>
<div>
<label for="resume">
Резюме <abbr title="обов'язкове">*</abbr>
<small>(PDF або DOCX, до 5 МБ)</small>
</label>
<input type="file" id="resume" name="resume" accept=".pdf,.doc,.docx" required />
</div>
<div>
<label for="portfolio">
Портфоліо / Приклади роботи
<small>(зображення або архів, до 20 МБ)</small>
</label>
<input type="file" id="portfolio" name="portfolio_files" accept="image/*,.zip,.pdf" multiple />
</div>
<div>
<label for="linkedin-color">Колір LinkedIn-банера (для профілю)</label>
<input type="color" id="linkedin-color" name="banner_color" value="#0077b5" />
</div>
</fieldset>
<!-- ===== ПРОГРЕС ЗАВЕРШЕНОСТІ ===== -->
<div aria-live="polite">
<label for="completion">Заповненість форми:</label>
<progress id="completion" value="75" max="100">75%</progress>
<meter value="75" min="0" max="100" low="30" high="80" optimum="100">75%</meter>
</div>
<!-- ===== ЗГОДИ ===== -->
<fieldset>
<legend>Згоди</legend>
<input type="checkbox" id="privacy" name="privacy_policy" value="agreed" required />
<label for="privacy">
Погоджуюсь з
<a href="/privacy" target="_blank" rel="noopener noreferrer"> Політикою конфіденційності </a>
<abbr title="обов'язкове">*</abbr>
</label>
<input type="checkbox" id="newsletter" name="newsletter" value="subscribed" />
<label for="newsletter">
Хочу отримувати розсилку з новинами TechUA та відкритими вакансіями
</label>
</fieldset>
<!-- ===== КНОПКИ ===== -->
<div>
<button type="submit">Надіслати заявку →</button>
<button type="reset">Очистити форму</button>
<button type="button" id="save-draft">Зберегти чернетку</button>
</div>
</form>
</main>
<footer>
<address>
<p>Питання щодо вакансії: <a href="mailto:hr@techua.com">hr@techua.com</a></p>
<p>HR-відділ TechUA: <a href="tel:+380441234567">+38 (044) 123-45-67</a></p>
</address>
</footer>
</body>
</html>
Що демонструє цей приклад:
🏗️ Структура форми
<form> з method="post", enctype="multipart/form-data", novalidate. Прихований CSRF-токен. Чотири <fieldset> з <legend> для логічного групування.📝 Поля вводу
<input>: text, email, tel з pattern, url, number, range + <output>, date, radio (група), checkbox (кілька груп), file (одиночний та multiple), color, hidden.🎛️ Складні елементи
<select> з <optgroup>, <datalist> для міст, <textarea> з maxlength, <output> для лічильника, <progress> + <meter> для прогресу.🌐 Доступність
<label for>. Групи <radio> і <checkbox> загорнуті у вкладені <fieldset>. autocomplete атрибути. <abbr title> для позначення обов'язкових полів. aria-live для динамічного прогресу.Корисні посилання
- 📖 HTML Living Standard: Forms — офіційна специфікація
- 📖 HTML Living Standard:
<input>— всі типи та атрибути - 📖 Autocomplete values — стандартні значення autocomplete
- 🔧 WebAIM: Creating Accessible Forms — форми та доступність
- 🔧 Can I Use: input types — підтримка типів полів браузерами
- ✅ W3C Nu HTML Checker — валідація HTML
Списки та таблиці в HTML
Детальний розбір елементів <ul>, <ol>, <dl>, <table>, <caption>, <colgroup> за специфікацією HTML Living Standard: семантика, доступність, colspan/rowspan та коли використовувати таблиці.
Семантичні елементи HTML5
Повний розбір семантичних елементів HTML5 за специфікацією WHATWG: <article>, <section>, <nav>, <aside>, <header>, <footer>, <main>, <address>, <time>, <details>, <summary>, <dialog> та інші. Різниця між div і семантичними елементами, доступність та ARIA.