Тренажёр HTML-форм и валидации

Соберите форму, исследуйте проверку введённых данных и решайте задания настройками или HTML-кодом.

Для вашего удобства рекомендую использовать полноэкранный режим.

Формы и валидация

Как пользоваться

В режиме «Исследовать» начните с одного поля, регистрации, заказа или обратной связи. Добавляйте поля и меняйте их порядок, выбирайте настраиваемое поле, задавайте атрибуты и вводите пробные значения в превью. Настройки типов и ограничений применяются к HTML, а проверку выполняет браузер.

Кнопка «Проверить введённые данные» показывает ошибки и переводит фокус на первое ошибочное поле. «Пробная отправка» внутри превью демонстрирует встроенную проверку перед событием submit; отправка перехватывается локально. Сводка ошибок содержит ссылки на поля. Пройдите форму клавишами Tab и Shift+Tab, попробуйте нажимать подписи.

Откройте инспектор, чтобы увидеть флаги ValidityState, сообщение браузера и связь label с полем. Таблица FormData показывает текущие пары name — value. Разбор «Поле → данные формы» объясняет, почему каждое поле включено или исключено; нажмите его подпись, чтобы перейти к полю. «Очистить ввод» возвращает начальные значения; «Сбросить настройки» возвращает исходную форму. Ввод не сохраняется между посещениями.

Можно добавить оформление полей, изменить акцент, скругление, отступы и число колонок. HTML, CSS и JavaScript доступны с подсветкой и копированием; скачивание сохраняет работающий учебный пример целиком.

Что важно понимать о проверке форм
  • required запрещает отсутствие значения. Тип email и pattern сами по себе не делают поле обязательным.
  • pattern проверяет всё значение. В лаборатории доступны готовые учебные шаблоны; они не являются универсальными правилами для имён, телефонов или почтовых адресов.
  • minlength и maxlength зависят от пользовательского ввода. Программно заданное начальное значение может не давать ошибку длины; браузер обычно не позволяет набрать больше maxlength.
  • disabled исключает поле из проверки и данных формы. readonly для поддерживаемых типов исключает поле из проверки, но сохраняет значение в FormData.
  • novalidate пропускает автоматическую проверку при отправке, но не отключает ограничения или явный вызов проверки. Непустое сообщение setCustomValidity сохраняет ошибку, пока его не очистят.
  • Связка label[for] и id даёт полю подпись. Подсказки и ошибки подключаются через aria-describedby; aria-invalid появляется при показе ошибки.

Лаборатория работает с текстом, почтой, URL, телефоном, паролем, числом, датой, checkbox, radio, select, textarea, range и файловыми полями. В форме до восьми полей. Для файлов показываются только локальные сведения: имя, размер и MIME-тип. Содержимое не читается и не отправляется; сетевых запросов нет. На реальном сайте проверка в браузере дополняет обязательную проверку на сервере.

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

Начать с формы:

Вводите учебные данные в превью. Они не отправляются и не сохраняются. Экспорт содержит настройки и начальные значения, а не текущий ввод.

Подробная проверка поля

Результат проверки поля

Все флаги ValidityState

    Проверяется фактический ввод браузера. Minlength срабатывает при пользовательском вводе; программное начальное значение не всегда даёт tooShort. Maxlength обычно не позволяет ввести лишние символы.

    Поля формы

    До восьми полей. «Выше» и «Ниже» меняют также порядок обхода клавишей Tab.

    Настройки поля
    Ограничения и состояние
    Справочник: min
    Справочник: max
    Справочник: maxlength

    Пустое ограничение удаляет атрибут. Пользовательская ошибка блокирует поле, пока вы не очистите сообщение setCustomValidity. Неактуальные для выбранного типа атрибуты не попадают в HTML.

    Accept — подсказка диалогу выбора, а не проверка содержимого. Расширение и MIME-тип могут быть неверными или отсутствовать. Required проверяет только наличие файла. На сервере проверяют содержимое, размер и допустимый формат.

    Оформление и проверка при отправке

    Novalidate влияет на пробную отправку, но не отключает ValidityState и явную проверку кнопкой «Проверить введённые данные».

    Какие данные попадут в FormData
    Текущие пары name — value
    NameValue

    Почему поле входит в данные? Выберите поле в таблице, чтобы перейти к нему в превью. Проверка значения и включение в FormData — разные вещи: ошибочное поле тоже может присутствовать в данных.

    Поле → данные формы
    ПолеРезультатПричина

    Повторяющиеся name создают отдельные пары. Кнопки отправки не включаются в показанный FormData(form): для их данных нужен конкретный submitter.

    Отключённые поля, поля без name и неотмеченные checkbox/radio не включаются. Readonly включается. Повторяющиеся имена сохраняются отдельными строками. Пароли в этой таблице скрыты.

    HTML, CSS и JavaScript формы
    HTML
    <form id="learning-form">
    <div id="form-errors" role="alert" tabindex="-1" hidden></div>
    <div class="form-field" data-field="field-1">
    <label for="field-1">Электронная почта (обязательно)</label>
    <input id="field-1" type="email" name="email" autocomplete="email" required aria-describedby="field-1-hint">
    <small id="field-1-hint">Например, reader@example.com</small>
    <p id="field-1-error" class="field-error" hidden></p>
    </div>
    <button type="submit">Пробная отправка</button>
    <button type="reset">Очистить ввод</button>
    <p id="form-result" role="status"></p>
    </form>
    CSS
    :root { color-scheme: light dark; }
    body { margin: 16px; font: 16px/1.5 system-ui, sans-serif; }
    form { max-width: 680px; }
    .form-field { margin-block: 12px; min-width: 0; }
    label, small { display: block; }
    input:not([type="checkbox"]):not([type="radio"]), select, textarea { max-width: 100%; box-sizing: border-box; }
    input[type="checkbox"] + label, input[type="radio"] + label { display: inline; }
    fieldset { min-width: 0; }
    .field-error { color: light-dark(#a12622, #ffb0aa); }
    #form-errors { border: 2px solid light-dark(#a12622, #ffb0aa); padding: 12px; margin-bottom: 16px; }
    [hidden] { display: none !important; }
    :focus-visible { outline: 3px solid light-dark(#1759a2, #8ecaff); outline-offset: 3px; }
    JavaScript
    // Учебная отправка перехватывается: сервер не используется.
    function bindLearningForm(doc, customErrors = {}, changed = () => void 0) {
    	const form = doc.querySelector("#learning-form");
    	const summary = doc.querySelector("#form-errors");
    	const status = doc.querySelector("#form-result");
    	const touched = /* @__PURE__ */ new Set();
    	let submitted = false;
    	let requested = false;
    	const controls = () => [...doc.querySelectorAll("input, select, textarea")];
    	function message(control) {
    		const v = control.validity;
    		if (!control.willValidate || v.valid) return "";
    		if (v.customError) return control.validationMessage;
    		if (v.valueMissing) return "Заполните поле или выберите вариант.";
    		if (v.typeMismatch) return control.type === "email" ? "Введите адрес электронной почты, например reader@example.com." : "Введите полный URL, например https://example.com.";
    		if (v.patternMismatch) return "Значение не соответствует шаблону pattern.";
    		if (v.tooShort) return "Введите не менее " + control.getAttribute("minlength") + " символов.";
    		if (v.tooLong) return "Введите не более " + control.getAttribute("maxlength") + " символов.";
    		if (v.rangeUnderflow) return "Значение должно быть не меньше " + control.getAttribute("min") + ".";
    		if (v.rangeOverflow) return "Значение должно быть не больше " + control.getAttribute("max") + ".";
    		if (v.stepMismatch) return "Значение не попадает в шаг step от базы (min, начальное value или 0).";
    		if (v.badInput) return "Браузер не может преобразовать ввод в значение этого типа.";
    		return control.validationMessage;
    	}
    	function render(focus = false) {
    		for (const input of doc.querySelectorAll("input[type=\"file\"]")) {
    			const list = input.closest("[data-field]")?.querySelector("[data-file-list]");
    			if (!list) continue;
    			const files = [...input.files ?? []];
    			const signature = files.map((file) => [
    				file.name,
    				file.size,
    				file.type,
    				file.lastModified
    			].join(":")).join("|");
    			if (list.dataset.files === signature) continue;
    			list.dataset.files = signature;
    			list.replaceChildren();
    			if (!files.length) {
    				list.textContent = "Файлы не выбраны.";
    				continue;
    			}
    			const heading = doc.createElement("p");
    			heading.textContent = "Выбрано файлов: " + files.length;
    			const items = doc.createElement("ul");
    			for (const file of files) {
    				const item = doc.createElement("li");
    				item.textContent = file.name + " · " + file.size + " байт · " + (file.type || "MIME-тип не указан");
    				items.append(item);
    			}
    			list.append(heading, items);
    		}
    		const invalid = controls().filter((control) => control.willValidate && !control.validity.valid);
    		for (const wrapper of doc.querySelectorAll("[data-field]")) {
    			const local = [...wrapper.querySelectorAll("input,select,textarea")];
    			const failed = local.find((control) => control.willValidate && !control.validity.valid);
    			const show = !!failed && (submitted || local.some((control) => touched.has(control.id)));
    			const error = wrapper.querySelector(".field-error");
    			error.textContent = show ? message(failed) : "";
    			error.hidden = !show;
    			for (const control of local) {
    				if (show) control.setAttribute("aria-invalid", "true");
    				else control.removeAttribute("aria-invalid");
    				const ids = [wrapper.querySelector("small")?.id, show ? error.id : ""].filter(Boolean).join(" ");
    				if (ids) control.setAttribute("aria-describedby", ids);
    				else control.removeAttribute("aria-describedby");
    			}
    		}
    		summary.replaceChildren();
    		summary.hidden = !submitted || invalid.length === 0;
    		if (!summary.hidden) {
    			const heading = doc.createElement("strong");
    			heading.textContent = "Исправьте ошибки:";
    			summary.append(heading);
    			const list = doc.createElement("ul");
    			const seen = /* @__PURE__ */ new Set();
    			for (const control of invalid) {
    				const wrapper = control.closest("[data-field]");
    				if (seen.has(wrapper.dataset.field)) continue;
    				seen.add(wrapper.dataset.field);
    				const item = doc.createElement("li"), link = doc.createElement("a");
    				link.href = "#" + control.id;
    				link.textContent = (wrapper.querySelector("legend,label")?.textContent ?? control.name) + ": " + message(control);
    				link.addEventListener("click", (event) => {
    					event.preventDefault();
    					control.focus();
    				});
    				item.append(link);
    				list.append(item);
    			}
    			summary.append(list);
    		}
    		if (focus && invalid.length) invalid[0].focus();
    		changed();
    		return invalid.length === 0;
    	}
    	function setErrors(errors) {
    		customErrors = errors;
    		for (const control of controls()) {
    			const id = control.closest("[data-field]").dataset.field;
    			control.setCustomValidity(customErrors[id] ?? "");
    		}
    	}
    	EventTarget.prototype.addEventListener.call(form, "input", (event) => {
    		const control = event.target;
    		if (![
    			"INPUT",
    			"SELECT",
    			"TEXTAREA"
    		].includes(control.tagName)) return;
    		touched.add(control.id);
    		status.textContent = "";
    		render();
    	});
    	EventTarget.prototype.addEventListener.call(form, "change", () => render());
    	EventTarget.prototype.addEventListener.call(form, "click", (event) => {
    		if (event.target.closest("button[type=\"submit\"]")) requested = true;
    	});
    	EventTarget.prototype.addEventListener.call(form, "keydown", (event) => {
    		if (event.key === "Enter" && event.target.tagName === "INPUT") requested = true;
    	});
    	EventTarget.prototype.addEventListener.call(form, "invalid", (event) => {
    		event.preventDefault();
    		if (requested) {
    			submitted = true;
    			queueMicrotask(() => {
    				render(true);
    				requested = false;
    				status.textContent = "Отправка остановлена: исправьте отмеченные поля.";
    			});
    		}
    	}, true);
    	EventTarget.prototype.addEventListener.call(form, "submit", (event) => {
    		event.preventDefault();
    		requested = false;
    		submitted = true;
    		const valid = render();
    		status.textContent = valid ? "Проверка пройдена. Учебная отправка перехвачена: данные никуда не отправлены." : "Novalidate пропустил встроенную проверку при отправке. Ограничения полей по-прежнему нарушены; данные никуда не отправлены.";
    		changed();
    	});
    	EventTarget.prototype.addEventListener.call(form, "reset", () => {
    		setTimeout(() => {
    			touched.clear();
    			submitted = false;
    			requested = false;
    			setErrors(customErrors);
    			status.textContent = "Ввод сброшен к начальным значениям.";
    			render();
    		});
    	});
    	setErrors(customErrors);
    	return {
    		controls,
    		message,
    		refresh: () => {
    			setErrors(customErrors);
    			render();
    		},
    		setErrors,
    		check: () => {
    			submitted = true;
    			const valid = render(true);
    			status.textContent = valid ? "Все проверяемые поля корректны." : "Есть ошибки. Первое ошибочное поле получило фокус.";
    			return valid;
    		},
    		clear: () => {
    			submitted = false;
    			touched.clear();
    			status.textContent = "";
    			render();
    		}
    	};
    }
    
    (bindLearningForm)(document, {});

    Как пользоваться

    В режиме «Практиковаться» исправьте форму по условию и нажмите «Проверить решение». Подсказки и разбор не засчитывают решение. Галочки и прогресс сохраняются в браузере; сброс удаляет только отметки этого тренажёра. Ссылка на выбранное задание содержит его ID, без введённых значений.

    В заданиях «Исправь форму» сначала воспроизведите описанную неисправность в превью. Затем измените настройки, повторите действие и проверьте решение. «Сбросить настройки» позволяет снова увидеть исходную ошибку, сохраняя отметки решённых заданий.

    Начните с настройки отдельных полей или перейдите к заданиям «Исправь форму» : воспроизведите ошибку и найдите причину.

    1 из 18

    Обязательная почта

    Настройте обязательное поле электронной почты. Пустая строка и hello должны отклоняться, reader@example.com — приниматься. Поле должно оставаться доступным для ввода.

    Объяснение в учебнике и справочнике

    Подсказка

    Выберите email и включите required. Сам email не делает поле обязательным.

    Показать разбор

    Type=email проверяет формат непустого значения; required запрещает пустое. Проверка формата не подтверждает существование почтового ящика.

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

    Вводите учебные данные в превью. Они не отправляются и не сохраняются. Экспорт содержит настройки и начальные значения, а не текущий ввод.

    Подробная проверка поля

    Результат проверки поля

    Все флаги ValidityState

      Проверяется фактический ввод браузера. Minlength срабатывает при пользовательском вводе; программное начальное значение не всегда даёт tooShort. Maxlength обычно не позволяет ввести лишние символы.

        Поля формы

        До восьми полей. «Выше» и «Ниже» меняют также порядок обхода клавишей Tab.

        Настройки поля
        Ограничения и состояние
        Справочник: min
        Справочник: max
        Справочник: maxlength

        Пустое ограничение удаляет атрибут. Пользовательская ошибка блокирует поле, пока вы не очистите сообщение setCustomValidity. Неактуальные для выбранного типа атрибуты не попадают в HTML.

        Accept — подсказка диалогу выбора, а не проверка содержимого. Расширение и MIME-тип могут быть неверными или отсутствовать. Required проверяет только наличие файла. На сервере проверяют содержимое, размер и допустимый формат.

        Оформление и проверка при отправке

        Novalidate влияет на пробную отправку, но не отключает ValidityState и явную проверку кнопкой «Проверить введённые данные».

        Какие данные попадут в FormData
        Текущие пары name — value
        NameValue

        Почему поле входит в данные? Выберите поле в таблице, чтобы перейти к нему в превью. Проверка значения и включение в FormData — разные вещи: ошибочное поле тоже может присутствовать в данных.

        Поле → данные формы
        ПолеРезультатПричина

        Повторяющиеся name создают отдельные пары. Кнопки отправки не включаются в показанный FormData(form): для их данных нужен конкретный submitter.

        Отключённые поля, поля без name и неотмеченные checkbox/radio не включаются. Readonly включается. Повторяющиеся имена сохраняются отдельными строками. Пароли в этой таблице скрыты.

        HTML, CSS и JavaScript формы
        HTML
        <form id="learning-form">
        <div id="form-errors" role="alert" tabindex="-1" hidden></div>
        <div class="form-field" data-field="field-1">
        <label for="field-1">Почта</label>
        <input id="field-1" type="text" name="email">
        <p id="field-1-error" class="field-error" hidden></p>
        </div>
        <button type="submit">Пробная отправка</button>
        <button type="reset">Очистить ввод</button>
        <p id="form-result" role="status"></p>
        </form>
        CSS
        :root { color-scheme: light dark; }
        body { margin: 16px; font: 16px/1.5 system-ui, sans-serif; }
        form { max-width: 680px; }
        .form-field { margin-block: 12px; min-width: 0; }
        label, small { display: block; }
        input:not([type="checkbox"]):not([type="radio"]), select, textarea { max-width: 100%; box-sizing: border-box; }
        input[type="checkbox"] + label, input[type="radio"] + label { display: inline; }
        fieldset { min-width: 0; }
        .field-error { color: light-dark(#a12622, #ffb0aa); }
        #form-errors { border: 2px solid light-dark(#a12622, #ffb0aa); padding: 12px; margin-bottom: 16px; }
        [hidden] { display: none !important; }
        :focus-visible { outline: 3px solid light-dark(#1759a2, #8ecaff); outline-offset: 3px; }
        JavaScript
        // Учебная отправка перехватывается: сервер не используется.
        function bindLearningForm(doc, customErrors = {}, changed = () => void 0) {
        	const form = doc.querySelector("#learning-form");
        	const summary = doc.querySelector("#form-errors");
        	const status = doc.querySelector("#form-result");
        	const touched = /* @__PURE__ */ new Set();
        	let submitted = false;
        	let requested = false;
        	const controls = () => [...doc.querySelectorAll("input, select, textarea")];
        	function message(control) {
        		const v = control.validity;
        		if (!control.willValidate || v.valid) return "";
        		if (v.customError) return control.validationMessage;
        		if (v.valueMissing) return "Заполните поле или выберите вариант.";
        		if (v.typeMismatch) return control.type === "email" ? "Введите адрес электронной почты, например reader@example.com." : "Введите полный URL, например https://example.com.";
        		if (v.patternMismatch) return "Значение не соответствует шаблону pattern.";
        		if (v.tooShort) return "Введите не менее " + control.getAttribute("minlength") + " символов.";
        		if (v.tooLong) return "Введите не более " + control.getAttribute("maxlength") + " символов.";
        		if (v.rangeUnderflow) return "Значение должно быть не меньше " + control.getAttribute("min") + ".";
        		if (v.rangeOverflow) return "Значение должно быть не больше " + control.getAttribute("max") + ".";
        		if (v.stepMismatch) return "Значение не попадает в шаг step от базы (min, начальное value или 0).";
        		if (v.badInput) return "Браузер не может преобразовать ввод в значение этого типа.";
        		return control.validationMessage;
        	}
        	function render(focus = false) {
        		for (const input of doc.querySelectorAll("input[type=\"file\"]")) {
        			const list = input.closest("[data-field]")?.querySelector("[data-file-list]");
        			if (!list) continue;
        			const files = [...input.files ?? []];
        			const signature = files.map((file) => [
        				file.name,
        				file.size,
        				file.type,
        				file.lastModified
        			].join(":")).join("|");
        			if (list.dataset.files === signature) continue;
        			list.dataset.files = signature;
        			list.replaceChildren();
        			if (!files.length) {
        				list.textContent = "Файлы не выбраны.";
        				continue;
        			}
        			const heading = doc.createElement("p");
        			heading.textContent = "Выбрано файлов: " + files.length;
        			const items = doc.createElement("ul");
        			for (const file of files) {
        				const item = doc.createElement("li");
        				item.textContent = file.name + " · " + file.size + " байт · " + (file.type || "MIME-тип не указан");
        				items.append(item);
        			}
        			list.append(heading, items);
        		}
        		const invalid = controls().filter((control) => control.willValidate && !control.validity.valid);
        		for (const wrapper of doc.querySelectorAll("[data-field]")) {
        			const local = [...wrapper.querySelectorAll("input,select,textarea")];
        			const failed = local.find((control) => control.willValidate && !control.validity.valid);
        			const show = !!failed && (submitted || local.some((control) => touched.has(control.id)));
        			const error = wrapper.querySelector(".field-error");
        			error.textContent = show ? message(failed) : "";
        			error.hidden = !show;
        			for (const control of local) {
        				if (show) control.setAttribute("aria-invalid", "true");
        				else control.removeAttribute("aria-invalid");
        				const ids = [wrapper.querySelector("small")?.id, show ? error.id : ""].filter(Boolean).join(" ");
        				if (ids) control.setAttribute("aria-describedby", ids);
        				else control.removeAttribute("aria-describedby");
        			}
        		}
        		summary.replaceChildren();
        		summary.hidden = !submitted || invalid.length === 0;
        		if (!summary.hidden) {
        			const heading = doc.createElement("strong");
        			heading.textContent = "Исправьте ошибки:";
        			summary.append(heading);
        			const list = doc.createElement("ul");
        			const seen = /* @__PURE__ */ new Set();
        			for (const control of invalid) {
        				const wrapper = control.closest("[data-field]");
        				if (seen.has(wrapper.dataset.field)) continue;
        				seen.add(wrapper.dataset.field);
        				const item = doc.createElement("li"), link = doc.createElement("a");
        				link.href = "#" + control.id;
        				link.textContent = (wrapper.querySelector("legend,label")?.textContent ?? control.name) + ": " + message(control);
        				link.addEventListener("click", (event) => {
        					event.preventDefault();
        					control.focus();
        				});
        				item.append(link);
        				list.append(item);
        			}
        			summary.append(list);
        		}
        		if (focus && invalid.length) invalid[0].focus();
        		changed();
        		return invalid.length === 0;
        	}
        	function setErrors(errors) {
        		customErrors = errors;
        		for (const control of controls()) {
        			const id = control.closest("[data-field]").dataset.field;
        			control.setCustomValidity(customErrors[id] ?? "");
        		}
        	}
        	EventTarget.prototype.addEventListener.call(form, "input", (event) => {
        		const control = event.target;
        		if (![
        			"INPUT",
        			"SELECT",
        			"TEXTAREA"
        		].includes(control.tagName)) return;
        		touched.add(control.id);
        		status.textContent = "";
        		render();
        	});
        	EventTarget.prototype.addEventListener.call(form, "change", () => render());
        	EventTarget.prototype.addEventListener.call(form, "click", (event) => {
        		if (event.target.closest("button[type=\"submit\"]")) requested = true;
        	});
        	EventTarget.prototype.addEventListener.call(form, "keydown", (event) => {
        		if (event.key === "Enter" && event.target.tagName === "INPUT") requested = true;
        	});
        	EventTarget.prototype.addEventListener.call(form, "invalid", (event) => {
        		event.preventDefault();
        		if (requested) {
        			submitted = true;
        			queueMicrotask(() => {
        				render(true);
        				requested = false;
        				status.textContent = "Отправка остановлена: исправьте отмеченные поля.";
        			});
        		}
        	}, true);
        	EventTarget.prototype.addEventListener.call(form, "submit", (event) => {
        		event.preventDefault();
        		requested = false;
        		submitted = true;
        		const valid = render();
        		status.textContent = valid ? "Проверка пройдена. Учебная отправка перехвачена: данные никуда не отправлены." : "Novalidate пропустил встроенную проверку при отправке. Ограничения полей по-прежнему нарушены; данные никуда не отправлены.";
        		changed();
        	});
        	EventTarget.prototype.addEventListener.call(form, "reset", () => {
        		setTimeout(() => {
        			touched.clear();
        			submitted = false;
        			requested = false;
        			setErrors(customErrors);
        			status.textContent = "Ввод сброшен к начальным значениям.";
        			render();
        		});
        	});
        	setErrors(customErrors);
        	return {
        		controls,
        		message,
        		refresh: () => {
        			setErrors(customErrors);
        			render();
        		},
        		setErrors,
        		check: () => {
        			submitted = true;
        			const valid = render(true);
        			status.textContent = valid ? "Все проверяемые поля корректны." : "Есть ошибки. Первое ошибочное поле получило фокус.";
        			return valid;
        		},
        		clear: () => {
        			submitted = false;
        			touched.clear();
        			status.textContent = "";
        			render();
        		}
        	};
        }
        
        (bindLearningForm)(document, {});

        Как пользоваться

        Во вкладке «Практика создания HTML-форм» исправляйте разметку в редакторе с подсветкой. Нажмите «Обновить и проверить», чтобы применить HTML и проверить решение одним действием. Отдельное «Обновить превью» позволяет сначала попробовать заполнить форму. При ошибке разметки проверка не запускается. До обновления проверка недоступна. «Вернуть исходный HTML» начинает текущую задачу заново, сохраняя прогресс. В итоговых заданиях регистрации, оформления заказа и обратной связи начните с пустой формы, выполните список требований и раскрывайте подсказки постепенно; просмотр подсказок не засчитывает решение. Неподдерживаемая разметка отмечается в редакторе; последний результат остаётся в превью.

        В HTML-практике можно скачать самостоятельный HTML-файл с оформлением и перехватом отправки. Введённые в превью значения в файл не попадают. Блок «Браузер и сервер» имитирует задержку, отказ и успешный ответ: попробуйте отмену и повторную попытку. Реальных запросов нет. Подробная проверка раскрывается при ошибках и сворачивается после успешного решения.

        Исправьте HTML или создайте форму по требованиям, обновите превью и проверьте решение. Здесь можно писать разметку формы; CSS, JavaScript и внешние ресурсы не используются. Код и введённые данные остаются только на этой странице, сохраняются лишь отметки решений.

        1 из 10

        Свяжите подпись с полем

        Исправьте подпись «Имя»: нажатие на неё должно переводить фокус в доступное текстовое поле с name="name". Можно использовать for/id или вложить input в label.

        Объяснение в учебнике и справочнике
        Подсказка

        For должен совпадать с id поля. Другой вариант — обернуть поле элементом label.

        Показать разбор

        Оба способа связывают подпись с полем. Name определяет ключ данных, а id связывает элементы документа.

        Редактор загружается…

        HTML формы

        <form>
          <label for="user">Имя</label>
          <input id="name" name="name">
          <button>Отправить</button>
        </form>

        Результат

        Данные FormData

        Почему поле входит в данные? Выберите поле в таблице, чтобы перейти к нему в превью. Проверка значения и включение в FormData — разные вещи: ошибочное поле тоже может присутствовать в данных.

        Поле → данные формы
        ПолеРезультатПричина

        Повторяющиеся name создают отдельные пары. Кнопки отправки не включаются в показанный FormData(form): для их данных нужен конкретный submitter.

        Пары имя — значение текущего превью. Пароли скрыты. Данные никуда не отправляются.

        Браузер и сервер: пробная отправка

        Даже корректную форму сервер может отклонить. Выберите учебный ответ и отправьте текущее превью. Ответ появится с задержкой; это локальная имитация без сети. Изменение формы отменяет устаревший ответ.

        Оформление range и select: браузерные псевдоэлементы

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

        WebKit/Blink использует ::-webkit-slider-runnable-track и ::-webkit-slider-thumb; Gecko — ::-moz-range-track, ::-moz-range-thumb и ::-moz-range-progress. Это нестандартные расширения. Правила разделены, чтобы неподдерживаемый селектор не отменил остальные. Tab переводит фокус, стрелки меняют значение; границы min/max и шаг step остаются нативными.

        Range: дорожка и бегунок

        input[type="range"] {
          width: 100%; min-height: 44px; margin: 0;
          accent-color: #287047;
        }
        @supports selector(input::-webkit-slider-thumb) {
          input[type="range"] { appearance: none; background: transparent; }
          input[type="range"]::-webkit-slider-runnable-track {
            height: 8px; border-radius: 4px; background: CanvasText;
          }
          input[type="range"]::-webkit-slider-thumb {
            appearance: none; margin-top: -8px;
            width: 24px; height: 24px; border: 2px solid CanvasText;
            border-radius: 50%; background: Canvas;
          }
        }
        @supports selector(input::-moz-range-thumb) {
          input[type="range"] { appearance: none; background: transparent; }
          input[type="range"]::-moz-range-track {
            height: 8px; border-radius: 4px; background: CanvasText;
          }
          input[type="range"]::-moz-range-progress {
            height: 8px; background: Highlight;
          }
          input[type="range"]::-moz-range-thumb {
            width: 24px; height: 24px; box-sizing: border-box;
            border: 2px solid CanvasText; border-radius: 50%; background: Canvas;
          }
        }
        input:focus-visible { outline: 3px solid Highlight; outline-offset: 4px; }
        input:disabled { opacity: .6; cursor: not-allowed; }

        ::picker(select) оформляет раскрывающийся список, ::picker-icon — стрелку, option::checkmark — отметку выбранного пункта. Appearance: base-select включается только при поддержке. Иначе работает обычный select. Подпись, фокус и управление с клавиатуры сохраняются; конкретные клавиши открытия зависят от браузера и ОС. В режиме base-select мобильный системный диалог заменяется списком внутри страницы.

        Select: список, стрелка и отметка выбора

        select {
          font: inherit; max-width: 100%; min-height: 44px;
          padding: 8px; border: 1px solid CanvasText;
          border-radius: 8px; background: Canvas; color: CanvasText;
        }
        select:focus-visible { outline: 3px solid Highlight; outline-offset: 4px; }
        select:disabled { opacity: .6; cursor: not-allowed; }
        /* Без поддержки остаётся обычный нативный select со стрелкой. */
        @supports (appearance: base-select) and selector(select::picker(select)) {
          select { appearance: base-select; }
          select::picker(select) {
            appearance: base-select;
            border: 1px solid CanvasText; border-radius: 8px;
            background: Canvas; color: CanvasText;
          }
          select::picker-icon { color: currentColor; }
          select:open::picker-icon { rotate: 180deg; }
          option { padding: 8px 12px; }
          option::checkmark { color: currentColor; }
          option:checked { font-weight: bold; }
          option:focus-visible { outline: 2px solid Highlight; outline-offset: -2px; }
        }

        Подробнее: оформляемый select и файловые поля в HTML Standard .

        Для интерактивной части включите JavaScript. Required запрещает пустое значение, type задаёт формат, min и max — диапазон. Label с for, совпадающим с id поля, помогает понять назначение поля и перейти к нему нажатием. Учебная форма не отправляет данные.

        Учебник и справочник

        Начните с уроков «Формы» и «Элементы формы HTML5». К каждому заданию приложена ссылка на соответствующее объяснение.

        Нормативное описание: Constraint Validation API в стандарте HTML.

        Связанные элементы и атрибуты: label, input, form, select, textarea, fieldset, type, name, required, pattern, min, max, step, maxlength, readonly, disabled, autocomplete.