HTML / Доступность
Textarea в HTML: многострочное поле ввода без типичных ошибок
Как создать доступное textarea, настроить ограничения и стили, отличить readonly от disabled и добавить автоподстройку высоты.
Содержание статьи
<textarea> — элемент формы для многострочного текста: комментария, сообщения или описания. В отличие от <input>, начальное значение записывается между открывающим и закрывающим тегами.
<label for="message">Сообщение</label><textarea id="message" name="message" rows="6"></textarea>Атрибут name нужен для отправки значения на сервер. Связанный <label> даёт полю доступное имя и увеличивает область, по которой можно нажать для установки фокуса.
Поле в составе формы
<form action="/feedback" method="post"> <div class="form-field"> <label for="feedback">Ваш отзыв</label> <textarea id="feedback" name="feedback" rows="6" minlength="10" maxlength="1000" aria-describedby="feedback-hint" required ></textarea> <p id="feedback-hint">От 10 до 1000 символов.</p> </div> <button type="submit">Отправить отзыв</button></form>Браузер проверит required, minlength и maxlength перед обычной отправкой. Эта проверка улучшает интерфейс, но сервер всё равно обязан валидировать данные: HTTP-запрос можно отправить в обход HTML.
rows, cols и размеры через CSS
rows задаёт начальную высоту в строках, cols — ожидаемую ширину в символах. Для адаптивного интерфейса ширину удобнее контролировать CSS, сохранив rows как полезную исходную высоту.
.form-field textarea { box-sizing: border-box; display: block; inline-size: 100%; min-block-size: 8rem; padding: 0.75rem; font: inherit; line-height: 1.5; color: inherit; background-color: #ffffff; border: 1px solid #8b93a1; border-radius: 0.5rem; resize: vertical;}.form-field textarea:focus-visible { outline: 3px solid #6d5dfc; outline-offset: 2px;}resize: vertical позволяет менять высоту, не ломая горизонтальную сетку. Полностью запрещать изменение размера без необходимости не стоит.
Placeholder не заменяет label
placeholder исчезает после ввода и часто имеет низкий контраст. Используйте его только как пример формата, а постоянное название оставляйте в <label>.
<label for="project-description">Описание проекта</label><textarea id="project-description" name="description" rows="8" placeholder="Например: лендинг для онлайн-курса"></textarea>Не добавляйте пробелы и переносы между тегами, если поле должно быть пустым: они становятся начальным значением.
<!-- Пустое значение --><textarea name="note"></textarea><!-- Значение содержит пробелы и перенос --><textarea name="note"></textarea>readonly и disabled — разные состояния
<textarea name="terms" readonly>Текст можно выделить и скопировать.</textarea><textarea name="archived-note" disabled>Поле полностью недоступно.</textarea>readonlyзапрещает редактирование, но поле доступно для фокуса и его значение отправляется с формой;disabledисключает элемент из фокуса и отправки формы.
Поэтому disabled нельзя описывать как универсальный режим «только чтение».
Полезные атрибуты
autocompleteподсказывает браузеру назначение данных, когда для поля есть подходящее значение;spellcheck="true"включает проверку орфографии там, где она полезна;wrap="soft"управляет переносом при отображении, не добавляя переносы в отправляемое значение;dirnameпозволяет передать направление введённого текста, что важно для многоязычных интерфейсов;formсвязывает поле с формой поid, даже если элемент находится вне неё.
Поддержка и поведение некоторых подсказок зависят от браузера, поэтому критические требования должны проверяться на сервере.
Счётчик символов
HTML ограничит длину, а небольшой скрипт покажет остаток:
<label for="bio">О себе</label><textarea id="bio" name="bio" rows="5" maxlength="300" aria-describedby="bio-count"></textarea><p id="bio-count" aria-live="polite">Осталось 300 символов</p>const textarea = document.querySelector('#bio');const counter = document.querySelector('#bio-count');if (textarea && counter) { const updateCounter = () => { const remaining = textarea.maxLength - textarea.value.length; counter.textContent = `Осталось ${remaining} символов`; }; textarea.addEventListener('input', updateCounter); updateCounter();}Автоматическая высота
Inline-обработчик oninput смешивает разметку и поведение. Лучше подключить модуль и подписаться на событие:
<label for="autosize-message">Сообщение</label><textarea id="autosize-message" data-autosize name="message" rows="3"></textarea><script type="module" src="/assets/js/autosize.js"></script>const fields = document.querySelectorAll('[data-autosize]');function resizeField(field) { field.style.height = 'auto'; field.style.height = `${field.scrollHeight}px`;}for (const field of fields) { field.addEventListener('input', () => resizeField(field)); resizeField(field);}Не задавайте одновременно жёсткую высоту и автоподстройку. Проверьте вставку длинного текста, изменение ширины окна и увеличение масштаба.
Безопасный вывод сохранённого значения
Если сервер возвращает текст пользователя в форму, значение необходимо экранировать. В PHP:
<textarea name="message"><?= htmlspecialchars($message, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?></textarea>Нельзя вставлять недоверенный текст как HTML. Клиентские ограничения длины также не отменяют серверную валидацию.
Что запомнить
У <textarea> должны быть постоянный <label> и name. Начальную высоту задавайте через rows, адаптивную ширину — через CSS. Различайте readonly и disabled, не заменяйте подпись placeholder-текстом и всегда повторяйте валидацию на сервере.