В этой статье рассмотрим различные варианты стилизации текстовых полей HTML-форм. Сначала создадим базовый вариант оформления input, а затем множество других, дополняя каждый из них небольшим количеством CSS.
Веб-формы являются неотъемлемой частью многих веб-сайтов. Они позволяют пользователю ввести те или иные данные, которые затем отправляются на сервер или обрабатываются на стороне клиента, например, для изменения интерфейса.
Веб-формы также часто называют HTML-формами. Их проектирование осуществляется с помощью элементов управления форм (текстовых полей, выпадающих списков, кнопок, чекбоксов и т.д.) и некоторых дополнительных элементов, которые используются для придание форме определённой структуры.
Стилизация формы выполняется через CSS. В этом руководстве остановимся и подробно рассмотрим различные варианты оформления её текстовых полей.
Исходные коды примеров расположены на GitHub в папке text-field проекта «ui-components».
1. Настройка box-sizing
.
Обычно хорошей практикой считается для всех элементов включая псевдоэлементы установить box-sizing: border-box
:
*, *::before, *::after {
box-sizing: border-box;
}
В этом случае браузер при рассчитывании ширины и высоты элементов будет включать в них поля (padding) и границы (border). Как правило, это сильно упрощает работу с размерами элементов, и избавляет нас от множества проблем при размещении контента.
2. Нормализация стилей <input>
.
Для того чтобы <input>
в разных браузерах отображался как можно более одинаково необходимо добавить следующее:
/* 1 – Изменим стили шрифтов */
/* 2 – Удалим margin в Firefox и Safari */
input[type="text"] {
font-family: inherit; /* 1 */
font-size: inherit; /* 1 */
line-height: inherit; /* 1 */
margin: 0; /* 2 */
}
Для удобного добавления к элементам стилей создадим следующую HTML-разметку:
<div class="text-field">
<label class="text-field__label" for="login">Логин</label>
<input class="text-field__input" type="text" name="login" id="login" placeholder="Login" value="itchief">
</div>
Т.е. добавим к <input>
с type="text"
класс text-field__input
, к <label>
– text-field__label
, а затем обернём их в элемент <div>
с классом text-field
.
Теперь напишем стили для этих элементов. А также сразу включим в них стили для нормализации, чтобы не добавлять их отдельно:
/* установим отступ 1rem от нижнего края элемента */
.text-field {
margin-bottom: 1rem;
}
/* стили для label */
.text-field__label {
display: block;
margin-bottom: 0.25rem;
}
/* стили для input */
.text-field__input {
display: block;
width: 100%;
height: calc(2.25rem + 2px);
padding: 0.375rem 0.75rem;
font-family: inherit;
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
color: #212529;
background-color: #fff;
background-clip: padding-box;
border: 1px solid #bdbdbd;
border-radius: 0.25rem;
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
}
Примененные CSS свойства к элементу <input>
, и то, что они делают:
display: block
– устанавливает блочное отображение;width: 100%
– занимает всю доступную ширину;height: calc(2.25rem + 2px)
– высота элемента определяется путём сложения 2.25rem (font-size * line-height + padding-top + padding-bottom
) и 2px (ширина верхней и нижней границы);margin: 0
– убирает margin
отступы;padding: 0.375rem 0.75rem
– внутренние поля: сверху и снизу – 0.375rem, а слева и справа – 0.75rem;font-family: inherit
– чтобы шрифт был такой как у родительского элемента, а не тот который браузер по умолчанию назначает для <input>
;font-size: 1rem
– устанавливает явный размер шрифта, иначе будет браться из стилей браузера для <input>
;font-weight: 400
– задаёт начертание шрифта;line-height: 1.5
– высота строки (1.5 * размер шрифта);color: #212529
– цвет шрифта;background-color: #fff
– цвет фона;background-clip: padding-box
– указывает, что фон (фоновое изображение) нужно рисовать только до внешнего края отступа (под границей не выводить);border: 1px solid #bdbdbd
– устанавливает границу, у которой: 1px (толщина), solid (тип линии) и #bdbdbd
(цвет);border-radius: 0.25rem
– радиус скругления углов;transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out
– выполняет изменение значений свойств border-color
и box-shadow
с анимацией длительностью 0.15 секунд посредством временной функцией ease-in-out
.В результате получили следующее оформление:
Стилизуем плейсхолдер. По умолчанию плейсхолдер отображается полупрозрачным или светло-серым цветом. Получить его можно с помощью ::placeholder
. Оформим его следующим образом:
.text-field__input::placeholder {
color: #212529;
opacity: 0.4;
}
Стили для <input>
в состоянии фокуса (получить это состояние можно с помощью псевдокласса :focus
):
.text-field__input:focus {
color: #212529;
background-color: #fff;
border-color: #bdbdbd;
outline: 0;
box-shadow: 0 0 0 0.2rem rgba(158, 158, 158, 0.25);
}
Оформление <input>
, когда он находится в состоянии disabled
и readonly
:
<style>
.text-field__input:disabled,
.text-field__input[readonly] {
background-color: #f5f5f5;
opacity: 1;
}
</style>
<!-- disabled -->
<div class="text-field">
<label for="firstname">Имя пользователя (disabled)</label>
<input type="text" name="firstname" id="firstname" placeholder="Alaxander" disabled>
</div>
<!-- readonly -->
<div class="text-field">
<label for="city">Город (readonly)</label>
<input class="text-field__input" type="text" name="city" id="city" placeholder="Moscow" value="Moscow" readonly>
</div>
Этот набор стилей будет у нас отправной точкой для создания других.
Рассмотрим пример вставки в input
иконки с помощью псевдоэлементов.
Для этого дополнительно обернём элемент <input>
в <div>
с классами text-field__icon text-field__icon_email
:
<div class="text-field">
<label class="text-field__label" for="email">Email</label>
<div class="text-field__icon text-field__icon_email">
<input class="text-field__input" type="email" placeholder="alexander@itchief.ru" value="alexander@itchief.ru">
</div>
</div>
<div class="text-field">
<label class="text-field__label" for="text">Найти</label>
<div class="text-field__icon text-field__icon_search">
<input class="text-field__input" type="text" placeholder="css" value="css уроки">
</div>
</div>
Первый класс (text-field__icon
) будем использовать для того, чтобы установить относительное позиционирование (position: relative
). Это действие позволит нам разместить иконку в нужном месте относительно input
, используя уже абсолютное позиционирование (position: absolute
). Второй класс (text-field__icon_email
) будет определять иконку, которую мы хотим вставить.
.text-field__icon {
position: relative;
}
.text-field__icon::before {
content: '';
color: #bdbdbd;
position: absolute;
display: flex;
align-items: center;
top: 0;
bottom: 0;
left: 0.625rem;
top: 50%;
transform: translateY(-50%);
}
.text-field__icon .text-field__input {
padding-left: 2rem;
}
/* email значок */
.text-field__icon_email::before {
content: '@';
}
/* иконка лупы */
.text-field__icon_search::before {
width: 1rem;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%23bdbdbd' viewBox='0 0 16 16'%3E%3Cpath d='M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z'%3E%3C/path%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: center;
}
Ещё один вариант оформления:
В этом примере поместим в input
иконку, на которую можно нажать.
<div class="text-field">
<label class="text-field__label" for="search">Найти</label>
<div class="text-field__icon">
<input class="text-field__input" type="search" name="search" id="search" placeholder="css" value="css уроки">
<span class="text-field__aicon">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"><path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z" /></svg>
</span>
</div>
</div>
Для этого мы также как и в предыдущем примере обернули <input>
в <div class="text-field__icon">...<div>
. Саму svg-иконку обернули в <span>
с классом text-field__aicon
и поместили рядом с <input>
.
Оформление выполнили так:
.text-field__icon {
position: relative;
}
.text-field__icon input {
padding-right: 2.5rem;
}
.text-field__aicon {
position: absolute;
display: flex;
align-items: center;
top: 0;
bottom: 0;
right: 0.875rem;
width: 1rem;
cursor: pointer;
color: #bdbdbd;
transition: color 0.15s ease-in-out;
}
.text-field__aicon:hover {
color: #212529;
}
Ещё пример вставки иконки в input
:
HTML-разметка input
с кнопкой:
<div class="text-field">
<label class="text-field__label" for="search">Найти</label>
<div class="text-field__group">
<input class="text-field__input" type="search" id="search" name="search">
<button class="text-field__btn" type="button">Найти</button>
</div>
</div>
Расположение кнопки справа от input
выполним с помощью флексов:
.text-field__group {
display: flex;
}
/* кнопка */
.text-field__btn {
display: inline-block;
font-weight: 400;
line-height: 1.5;
color: #212529;
text-align: center;
vertical-align: middle;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
background-color: #eee;
border: 1px solid #bdbdbd;
padding: .375rem .75rem;
font-size: 1rem;
border-radius: .25rem;
transition: background-color .15s ease-in-out;
}
.text-field__btn:hover {
background-color: #bdbdbd;
}
.text-field__group .text-field__input {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
position: relative;
z-index: 2;
}
.text-field__group .text-field__btn {
position: relative;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
border-left-width: 0;
}
Разметка input с плавающим label:
<div class="text-field text-field_floating">
<input class="text-field__input" type="email" id="email" placeholder="alexander@itchief.ru">
<label class="text-field__label" for="email">Email</label>
</div>
<p>Когда указано значение value:</p>
<div class="text-field text-field_floating">
<input class="text-field__input" type="name" id="name" value="Alexander">
<label class="text-field__label" for="name">Name</label>
</div>
CSS код:
.text-field_floating {
position: relative;
}
.text-field_floating .text-field__input {
height: calc(3.5rem + 2px);
line-height: 1.25;
padding: 1rem 0.75rem;
}
.text-field_floating .text-field__label {
position: absolute;
top: 0;
left: 0;
height: 100%;
padding: 1rem .75rem;
pointer-events: none;
border: 1px solid transparent;
transform-origin: 0 0;
transition: opacity .15s ease-in-out, transform .15s ease-in-out;
}
.text-field_floating .text-field__input::-moz-placeholder {
color: transparent;
}
.text-field_floating .text-field__input::placeholder {
color: transparent;
}
.text-field_floating .text-field__input:focus,
.text-field_floating .text-field__input:not(:placeholder-shown) {
padding-top: 1.625rem;
padding-bottom: .625rem;
}
.text-field_floating .text-field__input:focus~.text-field__label,
.text-field_floating .text-field__input:not(:placeholder-shown)~.text-field__label {
opacity: .65;
transform: scale(.85) translateY(-.75rem) translateX(.15rem);
}
Ещё один вариант с «плавающей» меткой:
Третий вариант:
Пример в котором под input отображается количество набранных символов и максимальная длина:
Это выполняется посредством следующего кода:
<div class="text-field">
<label class="text-field__label" for="login">Логин</label>
<input class="text-field__input" type="text" name="login" id="login" placeholder="Login" maxlength="20" required>
<div class="text-field__counter"></div>
</div>
<script>
const elemLogin = document.querySelector('#login');
const elemCounter = elemLogin.nextElementSibling;
const maxLength = elemLogin.maxLength;
const updateCounter = (e) => {
const len = e ? e.target.value.length : 0;
elemCounter.textContent = `${len} / ${maxLength}`;
}
updateCounter();
elemLogin.addEventListener('keyup', updateCounter);
elemLogin.addEventListener('keydown', updateCounter);
</script>
Применить стили в зависимости от состояния поля в CSS можно с помощью специальных псевдоклассов. Например, :valid
позволяет выбрать валидные элементы, а :invalid
— не валидные.
.text-field__input:invalid,
.text-field__input:valid {
border-color: #dc3545;
padding-right: 2.25rem;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");
background-repeat: no-repeat;
background-position: right 0.5625rem center;
background-size: 1.125rem 1.125rem;
}
.text-field__input:valid {
border-color: #198754;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");
}
.text-field__input:invalid:focus {
border-color: #dc3545;
box-shadow: 0 0 0 0.25rem rgba(220, 53, 69, 0.25);
}
.text-field__input:valid:focus {
border-color: #198754;
box-shadow: 0 0 0 0.25rem rgb(25 135 84 / 25%);
}
Но, если вы хотите контролировать этот процесс и добавлять стили с помощью JavaScript, то тогда лучше это делать через классы. Например, использовать класс text-field__input_valid
при успешной валидации, а text-field__input_invalid
— при не успешной. Их следует добавлять к <input>
.
<div class="text-field">
<label class="text-field__label" for="city">City</label>
<!-- text-field__input_invalid -->
<input class="text-field__input text-field__input_invalid" type="text" name="city" id="city">
<div class="text-field__message">Укажите город.</div>
</div>
<div class="text-field">
<label class="text-field__label" for="username">First name</label>
<!-- text-field__input_valid -->
<input class="text-field__input text-field__input_valid" type="text" name="firstname" id="firstname"
value="Alexander">
<div class="text-field__message">Отлично!</div>
</div>
Отображать сообщения пользователю или подсказки можно через <div class="text-field__message">...</div>
.
Для <input>
с плавающим <label>
:
Валидацию элементов формы будем осуществлять с помощью функции checkValidity()
. После этого, в зависимости от её результата, будем добавлять той или иной класс к <input>
, а также сообщение (input.validationMessage
) в элемент .text-field__message
.
// input - переменная, содержащая элемент <input>
if (input.checkValidity()) {
input.classList.add('text-field__input_valid');
input.nextElementSibling.textContent = 'Отлично!';
} else {
input.classList.add('text-field__input_invalid');
input.nextElementSibling.textContent = input.validationMessage;
}
Т.к. мы будем сами отображать сообщения, то необходимо отключить стандартные подсказки браузера. Для этого к тегу <form>
необходимо добавить атрибут novalidate
:
<form id="form" action="#" novalidate>
...
</form>
Клиентская проверка формы после нажатия «Отправить»:
Пример валидации формы в реальном времени:
Отображение только ошибок: